Việc hiểu rõ các câu hỏi core Java thông dụng là một phần quan trọng trong quá trình chuẩn bị cho các buổi phỏng vấn, dù bạn là ứng viên mới vào nghề hay đã dày dạn kinh nghiệm. Thành thạo các chủ đề như lập trình hướng đối tượng (OOP), xử lý exception, collection, và các tính năng mới của Java, sẽ ảnh hưởng rất nhiều đến kết quả phỏng vấn.
Bài viết này tổng hợp và cập nhật những câu hỏi core Java thường gặp, bao gồm cả những thay đổi mới nhất của ngôn ngữ này. Hy vọng nó sẽ là một nguồn tài liệu tham khảo hữu ích giúp bạn chinh phục thành công các buổi phỏng vấn Java.
Các câu hỏi và trả lời phỏng vấn Java cơ bản
1. Kể tên một số tính năng quan trọng của Java phiên bản 14?
Java 14 được phát hành vào ngày 17 tháng 3 năm 2020. Đây là phiên bản Non-LTS (không hỗ trợ dài hạn). Một số tính năng thêm vào ở phiên bản này mà các lập trình viên Java nên biết là:
- Switch Expressions – JEP 361
- Cải tiến pattern matching cho toán tử instanceof – Tính năng Preview, JEP 305
- NullPointerException cho thông tin hữu ích hơn – JEP 358
- Text Blocks – Preview thứ hai, JEP 368
- Records – lớp thông tin, tính năng preview, JEP 359.
2. Kể tên một số tính năng quan trọng của bản Java 13?
Java 13 ra mắt vào ngày 17 tháng 9 năm 2019. Đây cũng là phiên bản Non-LTS. Một số tính năng đáng chú ý ở Java 13 bao gồm:
- Text Blocks – Tính năng Preview, JEP 355
- Switch Expressions – Tính năng preview, JEP 354
- Java Sockets API được viết lại – JEP 353
- Dynamic CDS Archive – JEP 350
- Phương thức FileSystems.newFileSystem()
- DOM và SAX Factory cóhỗ trợ Namespace
- Hỗ trợ Unicode 12.1
- Cải tiến ZGC để trả lại bộ nhớ không sử dụng – JEP 351
3. Kể tên một số tính năng quan trọng của bản phát hành Java 12?
Java 12 ra mắt vào ngày 19 tháng 3 năm 2019. Đây là phiên bản Non-LTS. Một số tính năng quan trọng là:
- Thay đổi JVM – JEP 189, JEP 346, JEP 344, và JEP 230.
- Switch Expressions
- Phương thức File mismatch()
- Định dạng số compact (Compact Number Formatting)
- Teeing Collector trong Stream API
- Các phương thức mới trong lớp String
- JEP 334: JVM Constants API
- JEP 305: Pattern Matching cho instanceof
- Loại bỏ Raw String Literals khỏi JDK 12.
4. Các tính năng quan trọng của bản Java 11 là gì?
Java 11 là bản phát hành LTS thứ hai sau Java 8. Oracle đã thay đổi giấy phép và hỗ trợ dành cho phiên bản này. Nghĩa là bạn sẽ phải trả phí khi tải xuống Java 11 của Oracle JDK cho các mục đích thương mại. Nếu muốn sử dụng phiên bản miễn phí, bạn có thể tải xuống từ trang web OpenJDK.
Một số tính năng quan trọng của Java 11 là:
- Chúng ta có thể chạy chương trình Java trực tiếp thông qua lệnh java. File mã nguồn sẽ được biên dịch và thực thi ngầm. Tính năng này là một phần của việc triển khai JEP 330.
- Có 6 phương thức mới được thêm vào lớp String: isBlank(), lines(), strip(), stripLeading(), stripTrailing(), và repeat(). Bạn có thể tim hiểu thêm về chúng trong bài viết của chúng tôi về Java String.
- Lớp Files có hai phương thức mới để đọc/ghi dữ liệu chuỗi là readString() và writeString().
- Chúng ta cũng có thể sử dụng “var” với lambda expression. Tính năng này đến từ việc triển khai JEP 323.
- Epsilon: Một garbage collector dạng no-op (không có hành động gôm rác thật nào) dành cho môi trường kiểm thử. Đây là một tính năng thử nghiệm và là một phần của JEP 318.
- Các module Java EE và CORBA đã bị bỏ khỏi bản JDK tiêu chuẩn. Nashorn JavaScript Engine cũng được vào danh sách chuẩn bị loại bỏ.
- HTTP Client mới (JEP 321) và Flight Recorder (JEP 328)
5. Các tính năng quan trọng của bản Java 10 là gì?
Java 10 là phiên bản đầu tiên đi theo chu kỳ phát hành sáu tháng một lần của Oracle, vì vậy nó không phải là một bản phát hành lớn như các phiên bản trước đó. Tuy nhiên, nó cũng có một số tính năng quan trọng cần chú ý:
- Interface kiểu biến cục bộ (Local-Variable Type Inference)
- Nâng cấp java.util.Locale và các API liên quan để triển khai các phần mở rộng Unicode bổ sung của tag ngôn ngữ BCP 47.
- Cho phép HotSpot VM cấp phát heap cho đối tượng Java trên các bộ nhớ thay thế (như NV-DIMM) do người dùng chỉ định.
- Cung cấp một bộ chứng chỉ gốc Certification Authority (CA) mặc định trong JDK.
6. Các tính năng quan trọng của Java 9 là gì?
Java 9 là một bản phát hành lớn và đi kèm với rất nhiều tính năng, trong đó có:
- Java 9 REPL (JShell)
- Hệ thống module Java 9
- Các phương thức Factory cho Immutable List, Set, Map, và Map.Entry
- Phương thức private trong Interface
- Reactive Streams
- Các cập nhật cải tiến cho GC (Garbage Collector)
7. Các tính năng quan trọng của Java 8 là gì?
Java 8 được phát hành vào tháng 3 năm 2014, vì vậy đây là một trong những chủ đề nóng trong các cuộc phỏng vấn java. Nếu bạn trả lời câu hỏi này một cách rõ ràng, điều đó cho thấy bạn hay theo dõi cập nhật các công nghệ mới nhất.
Java 8 là một trong những bản phát hành lớn nhất sau Java 5 annotations và generics. Một số tính năng quan trọng của Java 8 là:
- Thay đổi Interface với các phương thức default và static
- Functional interface và biểu thức Lambda
- Java Stream API cho các lớp collection
- API cho Java Date Time
8. Kể tên một số khái niệm OOP trong Java?
Java sử dụng các khái niệm của lập trình hướng đối tượng (Object Oriented Programming). Sau đây là một số khái niệm OOP hay được triển khai trong lập trình Java:
- Abstraction (Trừu tượng hóa)
- Encapsulation (Đóng gói)
- Polymorphism (Đa hình)
- Inheritance (Kế thừa)
- Association (Liên kết)
- Aggregation (Tổng hợp)
- Composition (Thành phần)
9. Bạn hiểu thế nào về tính độc lập nền tảng của Java?
Tính độc lập nền tảng (platform independence) có nghĩa là ta có thể chạy cùng một chương trình Java trên bất kỳ hệ điều hành nào. Ví dụ, ta có thể viết một chương trình Java trên Windows và vẫn có thể chạy nó trên Mac OS.
10. JVM là gì và nó có mang tính độc lập nền tảng không?
Máy ảo Java (Java Virtual Machine – JVM) là trái tim của ngôn ngữ lập trình Java. JVM chịu trách nhiệm chuyển đổi byte code thành code máy tính hiểu được. JVM không có tính độc lập nền tảng, đó là lý do tại sao có các bản JVM khác nhau cho các hệ điều hành khác nhau.
Chúng ta có thể tùy chỉnh JVM bằng các tùy chọn Java, chẳng hạn như giới hạn cấp phát bộ nhớ tối thiểu và tối đa cho JVM. Nó được gọi là ảo vì nó cung cấp một giao diện không phụ thuộc vào hệ điều hành thật bên dưới để chạy chương trình Java.
11. Sự khác biệt giữa JDK và JVM là gì?
Bộ công cụ phát triển Java (Java Development Kit – JDK) giúp lập trình viên phát triển các chương trình Java, trong khi đó JVM giúp chạy các chương trình đó.
JDK cung cấp tất cả các công cụ, file thực thi và file nhị phân cần thiết để biên dịch, debug và chạy một chương trình Java. Phần thực thi được xử lý bởi JVM để đảm bảo tính độc lập về nền tảng.
12. Sự khác biệt giữa JVM và JRE là gì?
Môi trường thực thi Java (Java Runtime Environment – JRE) là bản triển khai thực tế của JVM. JRE bao gồm JVM, các file nhị phân Java và các lớp khác để chạy thành công bất kỳ chương trình nào.
JRE không chứa bất kỳ công cụ phát triển nào như trình biên dịch Java, debugger, v.v. Nếu bạn muốn chạy bất kỳ chương trình java nào, bạn nên cài đặt JRE.
13. Lớp nào là superclass của tất cả các lớp?
java.lang.Object là lớp gốc cho tất cả các lớp Java và chúng ta không cần phải kế thừa (extend) nó.
14. Tại sao Java không hỗ trợ đa kế thừa?
Java không hỗ trợ đa kế thừa (multiple inheritance) trong các lớp vì “vấn đề kim cương” (Diamond Problem).
Tuy nhiên, Java cho phép đa kế thừa giữa các interface. Một interface con có thể kế thừa nhiều interface khác vì chúng chỉ khai báo các phương thức, còn việc triển khai sẽ nằm trong lớp riêng. Do đó, vấn đề kim cương không xảy ra với interface.
15. Tại sao Java không phải là ngôn ngữ hướng đối tượng thuần túy?
Java không được coi là ngôn ngữ hướng đối tượng thuần túy vì nó vẫn hỗ trợ các kiểu dữ liệu nguyên thủy (primitive) như int, byte, short, long, v.v. Điều này giúp đơn giản hóa việc viết code. Java có thể có các đối tượng bao (wrapper) cho các kiểu dữ liệu primitive nhưng nếu chỉ để biểu diễn thì chúng sẽ không mang lại bất kỳ lợi ích nào.
Như chúng ta đã biết, đối với tất cả các kiểu dữ liệu primitive, chúng ta có các lớp wrapper như Integer, Long, v.v. cung cấp một số phương thức bổ sung.
16. Sự khác biệt giữa biến PATH và classpath là gì?
PATH là một biến môi trường được hệ điều hành sử dụng để định vị các file thực thi. Đó là lý do tại sao ta cần thêm vị trí thư mục vào biến PATH khi cài đặt Java hoặc muốn bất kỳ file thực thi nào để hệ điều hànhcó thể tìm thấy chúng.
Classpath chỉ có trong Java và được các chương trình Java sử dụng để định vị các file class. Chúng ta có thể cung cấp vị trí classpath khi khởi chạy ứng dụng Java và nó có thể là một thư mục, tệp ZIP, tệp JAR, v.v.
17. Tầm quan trọng của phương thức main trong Java là gì?
Phương thức main() là điểm vào (entry point) của bất kỳ ứng dụng Java độc lập nào. Cú pháp của phương thức main là public static void main(String args[]).
Phương thức main của Java có tính public và static để môi trường thực thi Java có thể truy cập nó mà không cần khởi tạo lớp. Tham số đầu vào là một mảng String qua đó chúng ta có thể truyền các đối số runtime cho chương trình Java.
18. Overloading và overriding trong Java là gì?
Khi chúng ta có nhiều hơn một phương thức cùng tên trong một lớp nhưng khác tham số, thì đó được gọi là nạp chồng phương thức (method overloading).
Khái niệm ghi đè (overriding) liên quan đến tính kế thừa khi chúng ta có hai phương thức có cùng signature, một ở lớp cha và một trong lớp con. Chúng ta có thể sử dụng annotation @Override trong phương thức được ghi đè ở lớp con để đảm bảo nếu phương thức lớp cha thay đổi, thì lớp con cũng vậy.
19. Chúng ta có thể overload phương thức main() không?
Chúng ta có thể có nhiều phương thức có tên “main” trong một lớp. Tuy nhiên, nếu chúng ta chạy lớp đó, môi trường runtime của Java sẽ tìm kiếm phương thức main có cú pháp là public static void main(String args[]).
20. Một file code Java có thể chứa nhiều lớp public không?
Chúng ta không thể có nhiều hơn một lớp public trong một file mã nguồn Java. Nó chỉ có thể chứa nhiều lớp không public.
21. Java package là gì và package nào được import mặc định?
Java package là cơ chế để tổ chức các lớp Java bằng cách nhóm chúng lại với nhau. Việc nhóm này có thể dựa trên chức năng hoặc theo module. Tên đầy đủ của một lớp Java bao gồm tên package và tên lớp. Ví dụ: java.lang.Object là tên đầy đủ của lớp Object thuộc package java.lang.
Package java.lang được import mặc định và ta không cần phải chỉ ra rõ ràng để import bất kỳ lớp nào từ nó.
22. Access modifier trong Java là gì?
Java cho ta cơ chế kiểm soát truy cập thông qua các từ khóa access modifier kiểu public, private và protected. Khi không sử dụng từ khóa nào, ta gọi là access modifier mặc định. Một lớp Java chỉ có thể có access modifier là public hoặc default.
23. Từ khóa final là gì?
Từ khóa final được sử dụng với một lớp để đảm bảo không có lớp nào khác có thể kế thừa (extend) nó. Ví dụ, lớp String là một lớp final và chúng ta không thể extend nó.
Chúng ta có thể sử dụng từ khóa final với các phương thức để đảm bảo các lớp con không thể override (ghi đè) nó.
Từ khóa final của Java có thể được sử dụng với các biến để đảm bảo rằng nó chỉ có thể được gán giá trị một lần. Tuy nhiên, trạng thái bên trong của biến có thể thay đổi. Ví dụ, chúng ta chỉ có thể gán một biến final cho một đối tượng một lần, nhưng các biến của đối tượng đó có thể thay đổi sau này.
Các biến trong interface của Java mặc định là final và static.
24. Từ khóa static là gì?
Từ khóa static có thể được sử dụng với các biến ở cấp độ lớp (class-level variables) để làm cho chúng trở thành biến toàn cục (global), tức là tất cả các đối tượng được chia sẻ biến đó.
Chúng ta cũng có thể sử dụng từ khóa static với phương thức. Một phương thức static chỉ có thể truy cập các biến static của lớp và chỉ gọi được các phương thức static của lớp đó.
25. finally và finalize trong Java là gì?
Khối finally được sử dụng cùng với try-catch để chứa đoạn code mà ta muốn luôn được thực thi, ngay cả khi một exception được ném ra bởi khối try-catch. Khối finally chủ yếu được sử dụng để giải phóng tài nguyên được tạo trong khối try.
Finalize() là một phương thức đặc biệt trong lớp Object mà chúng ta có thể override trong các lớp của mình. Phương thức này được gọi bởi garbage collector khi đối tượng đang được thu gom rác. Nó thường được override để giải phóng tài nguyên hệ thống khi đối tượng bị loại bỏ khỏi bộ nhớ.
26. Chúng ta có thể khai báo một lớp là static không?
Chúng ta không thể khai báo một lớp ở cấp cao nhất (top-level class) là static, tuy nhiên một lớp bên trong (inner class) có thể được khai báo là static. Trong trường hợp đó, nó được gọi là static nested class.
Static nested class cũng giống như bất kỳ lớp top-level nào khác và được lồng vào chỉ để tiện cho việc đóng gói.
27. Static import là gì?
Nếu ta cần sử dụng bất kỳ biến hoặc phương thức static nào từ lớp khác, thông thường ta sẽ import lớp đó và sau đó sử dụng phương thức/biến với tên lớp.
import java.lang.Math;
//inside class
double test = Math.PI * 5;
Ta cũng có thể chỉ import phương thức hoặc biến static đó và sau đó sử dụng nó trong lớp như thể nó thuộc về lớp đó.
import static java.lang.Math.PI;
//no need to refer class now
double test = PI * 5;
Việc sử dụng static import có thể gây nhiều nhầm lẫn, vì vậy tốt nhất là nên tránh nó. Lạm dụng tính năng này có thể làm cho chương trình của bạn khó đọc và khó bảo trì.
28. Try-with-resources trong Java là gì?
Một trong những tính năng của Java 7 là câu lệnh try-with-resources để quản lý tài nguyên tự động.
Trước Java 7, không có cơ chế quản lý tài nguyên tự động và chúng ta phải đóng tài nguyên bằng cách chỉ định rõ ràng. Thông thường, việc này được thực hiện trong khối finally của câu lệnh try-catch. Cách tiếp cận này thường gây ra rò rỉ bộ nhớ nếu ta quên đóng tài nguyên.
Từ Java 7, chúng ta có thể tạo tài nguyên bên trong khối try để sử dụng nó. Java sẽ tự động đóng tài nguyên đó ngay khi khối try-catch kết thúc.
29. Multi-catch block trong Java là gì?
Một cải tiến đáng kể của Java 7 là khối nhiều catch (multi-catch block), cho phép chúng ta bắt nhiều exception trong một khối catch duy nhất. Điều này làm cho code của chúng ta ngắn gọn và sạch sẽ hơn khi mọi khối catch đều viết tương tự nhau.
Nếu một khối catch xử lý nhiều exception, bạn có thể tách chúng bằng dấu gạch đứng (|). Trong trường hợp này, tham số exception (ex) là final, vì vậy bạn không thể thay đổi nó.
30. Static block là gì?
Static block là một đoạn code được chạy khi lớp được nạp vào bộ nhớ bởi Java ClassLoader. Nó được sử dụng để khởi tạo các biến static của lớp, thông thường là để tạo các tài nguyên static khi lớp được nạp.
31. Interface là gì?
Interface là khái niệm cốt lõi của ngôn ngữ lập trình Java và được sử dụng rất nhiều không chỉ trong JDK mà còn trong các design pattern của Java, hầu hết các framework và công cụ.
Interface cung cấp một cách để đạt được tính trừu tượng (abstraction) trong Java và được dùng để định nghĩa hợp đồng (contract) cho các lớp con phải triển khai.
Interface là điểm khởi đầu tốt để định nghĩa Type và tạo hệ thống phân cấp top-level trong code chương trình. Vì một lớp Java có thể định nghĩa nhiều interface, nên trong hầu hết các trường hợp, ta nên sử dụng interface làm superclass.
32. Abstract class là gì?
Abstract class (lớp trừu tượng) được sử dụng trong Java để tạo ra một lớp kèm với một số phương thức triển khai mặc định cho các lớp con. Một lớp trừu tượng có thể có cả phương thức trừu tượng (không có phần thân) và phương thức đã được triển khai hoàn chỉnh.
Từ khóa abstract được dùng để tạo một lớp trừu tượng. Lớp trừu tượng không thể được khởi tạo. Nó chủ yếu được dùng để làm nền tảng cho các lớp con kế thừa nó để triển khai các phương thức trừu tượng và để override hoặc sử dụng các phương thức đã triển khai trong lớp đó.
33. Sự khác biệt giữa abstract class và interface là gì?
- Từ khóa abstract được dùng để tạo abstract class, trong khi interface là từ khóa cho interface.
- Abstract class có thể có các phương thức đã được triển khai, còn interface thì không thể (trước Java 8).
- Một lớp chỉ có thể kế thừa một abstract class nhưng có thể triển khai (implement) nhiều interface.
- Chúng ta có thể chạy một abstract class nếu nó có phương thức main(), trong khi chúng ta không thể chạy một interface.
34. Một interface có thể implement hoặc extend một interface khác không?
Interface không implement một interface khác, mà extend nó. Vì (trước Java 8) interface ta không thể triển khai phương thức trực tiếp nên không gặp vấn đề như diamond problem. Đó là lý do tại sao chúng ta có đa kế thừa trong interface, tức là một interface có thể extend nhiều interface khác.
Từ Java 8 trở đi, ta có thể triển khai các phương thức trong interface với từ khóa default (default method – phương thức mặc định). Vì vậy, để xử lý diamond problem khi một phương thức mặc định chung có mặt trong nhiều interface, ta bắt buộc phải cung cấp việc triển khai phương thức đó trong lớp implement các interface này.
35. Marker interface là gì?
Marker interface là một interface rỗng, không có phương thức nào, nhưng được Java sử dụng để ép buộc một số chức năng nhất định trong các lớp implement nó. Các marker interface phổ biến là Serializable và Cloneable.
36. Wrapper class là gì?
Lớp wrapper là đại diện dạng Object của tám kiểu dữ liệu nguyên thủy (primitive) trong Java. Tất cả các lớp wrapper trong Java đều là immutable (bất biến) và final.
Tính năng autoboxing và unboxing của Java 5 cho phép chuyển đổi dễ dàng giữa các kiểu nguyên thủy và lớp wrapper tương ứng của chúng.
37. Enum trong Java là gì?
Enum là một kiểu dữ liệu mới được đưa vào Java 1.5 mà các trường (field) của nó bao gồm một tập hợp các hằng số cố định. Ví dụ, trong Java, chúng ta có thể tạo Direction như một enum với các trường cố định là EAST, WEST, NORTH, SOUTH.
Tương tự như class, enum là từ khóa để tạo kiểu enum. Các hằng số enum ngầm định là static và final.
38. Java Annotation là gì?
Java annotation chỉ có tác dụng bổ sung thông tin (chú thích – annotate) về một đoạn code và không có ảnh hưởng trực tiếp nào khác đến đoạn code đó.
Tính năng annotation được thêm vào từ Java 5. Đó là metadata về chương trình được nhúng thẳng vào code của nó đó. Nó có thể được phân tích bởi công cụ phân tích annotation hoặc trình biên dịch.
Chúng ta cũng có thể chỉ định sự tồn tại của annotation chỉ trong thời gian biên dịch hoặc cho đến lúc chạy (runtime). Các annotation tích hợp sẵn của Java là @Override, @Deprecated và @SuppressWarnings.
39. Java Reflection API là gì và tại sao nó lại có vai trò quan trọng?
Java Reflection API cung cấp khả năng kiểm tra (inspect) và sửa đổi (modify) hành vi lúc chạy (runtime) của ứng dụng Java. Chúng ta có thể kiểm tra một lớp, interface, enum và lấy thông tin chi tiết về phương thức và trường của chúng.
API này là một tính năng nâng cao và chúng ta nên tránh sử dụng nó trong lập trình thông thường. Việc sử dụng Reflection API có thể phá vỡ các design pattern như singleton bằng cách gọi constructor private (vi phạm các quy tắc của access modifier).
Mặc dù bình thường ta không nên sử dụng Reflection API, nó có nhiều vai trò rất quan trọng. Chúng ta không thể có các framework như Spring, Hibernate hay các server như Tomcat, JBoss nếu không có API này. Các công cụ trên gọi các phương thức thích hợp và khởi tạo các lớp thông qua Reflection API và sử dụng nó rất nhiều cho các tác vụ xử lý khác.
40. Composition trong Java là gì?
Composition là kỹ thuật thiết kế để triển khai mối quan hệ “has-a” (có một) trong các class. Ta có thể sử dụng Object composition để tái sử dụng code dễ dàng.
Ta dùng composition trong Java bằng cách sử dụng các biến instance cho một class chính là tham chiếu đến các đối tượng của class khác. Lợi ích của việc sử dụng composition là ta có thể kiểm soát visibility (mức độ truy cập) từ các lớp bên ngoài (client class) với các đối tượng bên trong, đồng thời chỉ tái sử dụng những gì thật sự cần thiết.
41. Lợi ích của composition so với kế thừa là gì?
Một trong những best practice trong lập trình Java là ưu tiên composition so với inheritance (kế thừa), với các lý do có thể là:
- Bất kỳ thay đổi nào trong superclass (lớp cha) đều có thể ảnh hưởng đến subclass (lớp con) ngay cả khi ta không sử dụng các method của superclass. Ví dụ, nếu ta có một method test() trong subclass và đột nhiên ai đó thêm một method test() trong superclass, ta sẽ gặp lỗi biên dịch trong subclass. Với composition ta không gặp vấn đề này vì chỉ sử dụng những method cần thiết.
- Inheritance sẽ expose tất cả các method và biến của superclass cho client, và việc thiếu bảo đảm quyền kiểm soát trong thiết kế superclass có thể dẫn đến các lỗ hổng bảo mật. Composition cho phép ta cung cấp quyền truy cập hạn chế vào các method, do đó nó là cách an toàn hơn.
- Ta có thể đạt được runtime binding (liên kết động) trong composition, trong khi inheritance liên kết các class tại compile time (thời gian biên dịch). Do đó, composition mang nhiều sự linh hoạt hơn trong việc gọi method.
42. Làm thế nào để sắp xếp một collection gồm các đối tượng tùy chỉnh (custom Object) trong Java?
Ta cần sử dụng interface Comparable để hỗ trợ việc sắp xếp các đối tượng tùy chỉnh trong một collection. Interface này có method compareTo(T obj), vốn được các method sắp xếp sử dụng. Bằng cách triển khai method này, ta có thể đưa ra cách sắp xếp mặc định cho một collection các đối tượng tùy chỉnh.
Tuy nhiên, khi muốn sắp xếp dựa trên các tiêu chí khác nhau (chẳng hạn như sắp xếp một collection Employees dựa trên lương hoặc tuổi), thì ta có thể tạo các instance Comparator và truyền chúng vào làm phương pháp sắp xếp.
43. Inner class trong Java là gì?
Ta có thể định nghĩa một class bên trong một class khác, chúng được gọi là nested class (lớp lồng nhau). Bất kỳ nested class nào mà không phải là static đều được gọi là inner class.
Inner class được liên kết với đối tượng của class bên ngoài và chúng có thể truy cập tất cả các biến và method ở ngoài. Vì inner class được liên kết với instance, ta không thể có bất kỳ biến static nào trong chúng.
Ta có thể có local inner class hoặc anonymous inner class bên trong một class.
44. Anonymous inner class là gì?
Một local inner class không có tên được gọi là anonymous inner class. Một anonymous class được định nghĩa và khởi tạo trong một statement duy nhất. Anonymous inner class luôn extend một class hoặc implement một interface.
Vì anonymous class không có tên, ta không thể định nghĩa constructor cho nó. Ta chỉ có thể truy cập anonymous inner class tại điểm mà nó được định nghĩa.
45. Classloader trong Java là gì?
Classloader là chương trình tải bytecode vào bộ nhớ khi ta muốn truy cập class nào đó. Ta có thể tạo classloader của riêng mình bằng cách extend lớp ClassLoader và override phương thức loadClass(String name).
46. Các loại classloader khác nhau là gì?
Có ba loại classloader tích hợp sẵn trong Java:
- Bootstrap ClassLoader: Tải các class nội bộ của JDK, thường là rt.jar và các core class khác.
- Extensions Class Loader: Tải các class từ thư mục extensions của JDK, thường là thư mục $JAVA_HOME/lib/ext.
- System Class Loader: Tải các class từ classpath hiện tại mà có thể được thiết lập khi gọi chương trình (với các tùy chọn -cp hoặc -classpath).
47. Toán tử ba ngôi (ternary) trong Java là gì?
Toán tử ba ngôi của Java là toán tử điều kiện duy nhất nhận ba toán hạng. Đó là một cách thay thế gọn gàng cho câu lệnh if-then-else và được sử dụng rất nhiều trong lập trình Java.
Ta có thể sử dụng chúng cho các điều kiện if-else hoặc thậm chí các điều kiện switch bằng cách sử dụng các toán tử ba ngôi lồng nhau.
48. Từ khóa super có tác dụng gì?
Từ khóa super có thể được sử dụng để truy cập method của superclass khi ta đã override method đó trong child class (lớp con).
Ta có thể sử dụng nó để gọi constructor của superclass trong constructor của child class. Tuy nhiên, trong trường hợp này, nó phải là câu lệnh đầu tiên trong phương thức constructor.
package com.journaldev.access;
public class SuperClass {
public SuperClass(){
}
public SuperClass(int i){}
public void test(){
System.out.println("super class test method");
}
}
Dưới đây là một ví dụ của việc sử dụng từ khóa super khi triển khai child class:
package com.journaldev.access;
public class ChildClass extends SuperClass {
public ChildClass(String str){
//access super class constructor with super keyword
super();
//access child class method
test();
//use super to access super class method
super.test();
}
@Override
public void test(){
System.out.println("child class test method");
}
}
49. Câu lệnh break và continue là gì?
Ta có thể sử dụng câu lệnh break để chấm dứt vòng lặp for, while, hoặc do-while. Nó cũng giúp ta thoát khỏi case của switch. Ngoài ra, ta có thể kết hợp break với label để chấm dứt các vòng lặp lồng nhau.
Câu lệnh continue bỏ qua lượt lặp hiện tại của for, while, hoặc do-while. Ta có thể sử dụng nó với label để bỏ qua lượt lặp hiện tại của vòng lặp ngoài cùng.
50. Từ khóa this trong Java là gì?
Từ khóa this cung cấp tham chiếu đến object hiện tại và nó chủ yếu được sử dụng để đảm bảo ta sử dụng đúng các biến của object được sử dụng, chứ không phải các biến cục bộ có cùng tên.
//constructor
public Point(int x, int y) {
this.x = x;
this.y = y;
}
Ta cũng có thể sử dụng từ khóa this để gọi các constructor khác từ một constructor:
public Rectangle() {
this(0, 0, 0, 0);
}
public Rectangle(int width, int height) {
this(0, 0, width, height);
}
public Rectangle(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
51. Default constructor là gì?
Constructor không có tham số của một class được gọi là default constructor. Khi ta không định nghĩa bất kỳ constructor nào cho một class, trình biên dịch Java sẽ tự động tạo default constructor không có đối số cho class đó. Nếu có các constructor khác được định nghĩa, thì Java sẽ không tạo default constructor cho chúng ta.
52. Chúng ta có thể có try mà không có block catch không?
Ta có thể dùng câu lệnh try-finally mà không cần dùng khối catch.
53. Garbage collection là gì?
Garbage collection (thu gom rác) là quá trình xem xét bộ nhớ heap, xác định các đối tượng có đang được sử dụng hay không, và xóa các đối tượng không sử dụng. Trong Java, quá trình giải phóng bộ nhớ được xử lý tự động bởi garbage collector.
Ta có thể chạy garbage collector bằng code Runtime.getRuntime().gc() hoặc sử dụng phương thức System.gc().
54. Serialization và Deserialization là gì?
Quá trình chuyển đổi một đối tượng Java thành một Stream được gọi là Serialization. Khi một đối tượng được chuyển đổi thành Stream, nó có thể được lưu vào file, gửi qua mạng hoặc sử dụng trong các kết nối socket.
Đối tượng nên có interface Serializable và ta có thể sử dụng java.io.ObjectOutputStream để ghi đối tượng vào file hoặc bất kỳ object OutputStream nào.
Quá trình chuyển đổi dữ liệu stream được tạo ra thông qua serialization ngược trở lại thành object được gọi là deserialization.
55. Làm thế nào để chạy một file JAR ở dòng lệnh?
Ta có thể chạy một file JAR bằng lệnh java nhưng phải có mục Main-Class trong file manifest của JAR. Main-Class là điểm bắt đầu (entry point) của JAR và được lệnh java sử dụng để thực thi class đó.
56. Công dụng của class System là gì?
System là một trong những class chính của Java. Một trong những cách dễ nhất để ghi log cho việc debug là sử dụng phương thức System.out.print().
System được chỉ định là lớp final để ta không thể kế thừa (subclass) và override hành vi của nó thông qua inheritance. Nó không có bất kỳ constructor public nào, vì vậy ta không thể khởi tạo class này và tất cả các method của nó đều là static.
Một số phương thức của class System dùng để sao chép mảng, lấy thời gian hiện tại, đọc các biến môi trường.
57. Từ khóa instanceof là gì?
Ta có thể sử dụng từ khóa instanceof để kiểm tra xem một đối tượng có thuộc về một class hay không. Ta nên sử dụng nó càng ít càng tốt. Dưới đây là một ví dụ sử dụng mẫu:
public static void main(String args[]){
Object str = new String("abc");
if(str instanceof String){
System.out.println("String value:"+str);
}
if(str instanceof Integer){
System.out.println("Integer value:"+str);
}
}
Vì str có kiểu String tại runtime, câu lệnh if đầu tiên có kết quả là true còn if thứ hai là false.
58. Chúng ta có thể sử dụng String với switch case không?
Một trong những khả năng của Java 7 là cho phép switch case sử dụng String. Vì vậy, với phiên bản 7 hoặc mới hơn, ta có thể sử dụng String trong các câu lệnh switch-case.
59. Java là truyền tham trị (pass by value) hay tham chiếu (pass by reference)?
Đây là một câu hỏi rất dễ gây nhầm lẫn. Ta biết rằng các biến đối tượng chứa tham chiếu đến các đối tượng trong không gian heap. Khi ta gọi bất kỳ method nào, một bản sao của các biến này được truyền đi và lưu trữ trong bộ nhớ stack của method đó.
Chúng ta có thể kiểm tra bất kỳ ngôn ngữ nào là pass by reference hay pass by value thông qua một phương thức hoán đổi (swap) đơn giản.
60. Sự khác biệt giữa bộ nhớ heap và stack là gì?
Bộ nhớ heap được sử dụng bởi tất cả các thành phần của ứng dụng trong khi bộ nhớ stack chỉ được sử dụng bởi một thread (luồng thực thi).
Bất cứ khi nào một đối tượng được tạo, nó luôn được lưu trữ trong không gian heap còn bộ nhớ stack sẽ chứa tham chiếu đến nó. Bộ nhớ stack chỉ chứa các biến nguyên thủy cục bộ (local primitive variable) và các biến tham chiếu (reference variable) đến các đối tượng trong không gian heap.
Quản lý bộ nhớ trong stack được thực hiện theo kiểu LIFO (Last-In, First-Out), trong khi việc quản lý bộ nhớ heap phức tạp hơn vì nó có tính toàn cục.
61. Trình biên dịch Java được lưu trữ trong JDK, JRE hay JVM?
Nhiệm vụ của trình biên dịch Java là chuyển đổi chương trình Java thành bytecode, ta có file thực thi javac cho việc đó. Vì vậy, nó phải được lưu trữ trong JDK. Ta không cần nó trong JRE, và JVM chỉ là các đặc tả kỹ thuật.
62. Output của các chương trình sau sẽ là gì?
Trường hợp static method trong class
package com.journaldev.util;
public class Test {
public static String toString(){
System.out.println("Test toString called");
return "";
}
public static void main(String args[]){
System.out.println(toString());
}
}
Trả lời: Đoạn code sẽ không biên dịch được vì ta không thể có một method của lớp Object với từ khóa static.
Lưu ý rằng lớp Object đã có sẵn một method là toString(). Bạn sẽ nhận được lỗi biên dịch là “This static method cannot hide the instance method from Object”. Lý do là phương thức static thuộc về class, và hơn nữa mọi class đều kế thừa từ Object, nên ta không thể có method giống tên trong cả instance và class.
Ta sẽ không còn gặp lỗi này nếu đổi tên method từ toString() thành một tên khác không có trong superclass Object.
Trường hợp gọi static method
package com.journaldev.util;
public class Test {
public static String foo(){
System.out.println("Test foo called");
return "";
}
public static void main(String args[]){
Test obj = null;
System.out.println(obj.foo());
}
}
Trả lời: Đây là câu hỏi mẹo. Ta biết NullPointerException sẽ xuất hiện khi gọi một method trên một đối tượng là NULL. Nhưng ở đây chương trình này vẫn chạy bình thường và in ra “Test foo called”.
Lý do cho điều này là khả năng tối ưu hóa code của Java compiler. Khi code Java được biên dịch để tạo ra bytecode, nó phát hiện ra rằng foo() là một static method và nên được gọi bằng cách sử dụng class.
Vì vậy, nó thay đổi lệnh gọi method obj.foo() thành Test.foo() và do đó ta tránh được NullPointerException.
Tổng kết
Vậy là chúng ta đã cùng điểm qua một số câu hỏi phỏng vấn thường gặp về core Java cùng với gợi ý cách trả lời. Chúng tôi sẽ cố gắng tiếp tục cập nhật thêm nhiều câu hỏi hữu ích khác vào danh sách này. Nếu các bạn thấy bài còn bỏ sót câu nào quan trọng, đừng ngần ngại góp ý cho chúng tôi ở phần bình luận bên dưới nhé.