- Sản phẩm & Dịch vụSản phẩm & Dịch vụ
- Giải phápGiải pháp
- Bảng giáBảng giá
- Công tyCông ty
- Tài liệuTài liệu
vi
vi
Chuyên mục với các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.
Web Developer
I’m passionate about web development and sharing my insights through articles, with over 8 years of experience. I hope these sharings inspire you and help build a strong web development community. @#@ Tôi đam mê phát triển web và chia sẻ những hiểu biết của mình thông qua các bài viết, với hơn 8 năm kinh nghiệm. Tôi hy vọng những chia sẻ này sẽ truyền cảm hứng cho các bạn và giúp xây dựng một cộng đồng phát triển web mạnh mẽ.
Thảo luận (0)
Đăng nhập để thảo luận
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.
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à:
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:
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à:
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à:
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ú ý:
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 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à:
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:
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.
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.
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.
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.
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ó.
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.
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.
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.
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.
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.
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[]).
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.
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ó.
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.
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.
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 đó.
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ớ.
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.
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ì.
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.
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ó.
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.
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.
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 đó.
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.
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.
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.
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.
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.
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.
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.
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à:
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.
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.
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.
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).
Có ba loại classloader tích hợp sẵn trong Java:
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.
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"); }}
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.
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;
}
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.
Ta có thể dùng câu lệnh try-finally mà không cần dùng khối catch.
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().
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.
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 đó.
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.
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.
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.
Đâ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.
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.
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.
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.
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é.