JavaRush /Blog Java /Random-VI /50 câu hỏi và câu trả lời phỏng vấn cốt lõi về Java hàng ...
Roman Beekeeper
Mức độ

50 câu hỏi và câu trả lời phỏng vấn cốt lõi về Java hàng đầu. Phần 1

Xuất bản trong nhóm
Xin chào tất cả các bạn Kỹ sư phần mềm! Hãy nói về các câu hỏi phỏng vấn. Về những gì bạn cần chuẩn bị và những gì bạn cần biết. Đây là lý do tuyệt vời để lặp lại hoặc nghiên cứu những điểm này từ đầu. 50 câu hỏi và câu trả lời phỏng vấn cốt lõi về Java hàng đầu.  Phần 1 - 1Tôi có một bộ sưu tập khá phong phú các câu hỏi thường gặp về OOP, Cú pháp Java, các ngoại lệ trong Java, các bộ sưu tập và đa luồng, để thuận tiện, tôi sẽ chia thành nhiều phần. Quan trọng:chúng ta sẽ chỉ nói về các phiên bản Java tối đa 8. Tất cả những đổi mới từ 9, 10, 11, 12, 13 sẽ không được tính đến ở đây. Mọi ý tưởng/nhận xét về cách cải thiện câu trả lời đều được chào đón . Chúc bạn đọc vui vẻ, đi thôi!

Phỏng vấn Java: Câu hỏi OOP

1. Java có những tính năng gì?

Trả lời:
  1. Khái niệm OOP:

    1. hướng đối tượng;
    2. di sản;
    3. đóng gói;
    4. đa hình;
    5. trừu tượng.
  2. Đa nền tảng: Một chương trình Java có thể chạy trên mọi nền tảng mà không cần bất kỳ sửa đổi nào. Điều duy nhất bạn cần là một JVM (máy ảo java) đã cài đặt.

  3. Hiệu suất cao: JIT (Trình biên dịch Just In Time) cho phép hiệu suất cao. JIT chuyển đổi mã byte thành mã máy và sau đó JVM bắt đầu thực thi.

  4. Đa luồng: Một luồng thực thi được gọi là Thread. JVM tạo ra một luồng có tên main thread. Một lập trình viên có thể tạo nhiều luồng bằng cách kế thừa từ lớp Thread hoặc bằng cách triển khai một giao diện Runnable.

2. Thừa kế là gì?

Kế thừa có nghĩa là một lớp có thể kế thừa (“ mở rộng ”) một lớp khác. Bằng cách này, bạn có thể sử dụng lại mã từ lớp mà bạn kế thừa. Lớp hiện có được gọi là superclass, và lớp đang được tạo được gọi là subclass. Họ cũng nói parentchild.
public class Animal {
   private int age;
}

public class Dog extends Animal {

}
Animalở đâu parent, và Dog- child.

3. Đóng gói là gì?

Câu hỏi này thường xuất hiện trong các cuộc phỏng vấn nhà phát triển Java. Đóng gói đang ẩn việc triển khai bằng cách sử dụng công cụ sửa đổi truy cập, sử dụng getters và setters. Điều này được thực hiện để đóng quyền truy cập cho việc sử dụng bên ngoài ở những nơi mà các nhà phát triển thấy cần thiết. Một ví dụ dễ tiếp cận từ cuộc sống là một chiếc ô tô. Chúng tôi không có quyền truy cập trực tiếp vào hoạt động của động cơ. Đối với chúng tôi, công việc là tra chìa khóa vào ổ điện và nổ máy. Và những quy trình nào sẽ diễn ra ngầm không phải việc của chúng tôi. Hơn nữa, sự can thiệp của chúng ta vào hoạt động này có thể dẫn đến những tình huống khó lường, do đó chúng ta có thể làm hỏng xe và gây hại cho chính mình. Chính xác điều tương tự cũng xảy ra trong lập trình. Được mô tả tốt trên Wikipedia . Ngoài ra còn có một bài viết về đóng gói trên JavaRush .

4. Đa hình là gì?

Tính đa hình là khả năng của một chương trình sử dụng giống hệt các đối tượng có cùng giao diện mà không cần thông tin về loại đối tượng cụ thể. Như họ nói, một giao diện - nhiều cách triển khai. Với tính đa hình, bạn có thể kết hợp và sử dụng các loại đối tượng khác nhau dựa trên hành vi chung của chúng. Ví dụ: chúng ta có một lớp Động vật, có hai con cháu - Chó và Mèo. Lớp Động vật chung có một hành vi chung cho tất cả - tạo ra âm thanh. Trong trường hợp chúng ta cần tập hợp tất cả các lớp con của lớp Động vật lại với nhau và thực hiện phương thức “tạo ra âm thanh”, chúng ta sử dụng khả năng đa hình. Nó sẽ trông như thế này:
List<Animal> animals = Arrays.asList(new Cat(), new Dog(), new Cat());
animals.forEach(animal -> animal.makeSound());
Vì vậy tính đa hình giúp ích cho chúng ta. Hơn nữa, điều này cũng áp dụng cho các phương pháp đa hình (quá tải). Thực hành sử dụng đa hình

Câu hỏi phỏng vấn - Cú pháp Java

5. Hàm tạo trong Java là gì?

Các đặc điểm sau đây là hợp lệ:
  1. Khi một đối tượng mới được tạo, chương trình sẽ sử dụng hàm tạo thích hợp để thực hiện việc đó.
  2. Một hàm tạo giống như một phương thức. Điểm đặc biệt của nó là không có phần tử trả về (kể cả void) và tên của nó trùng với tên của lớp.
  3. Nếu không có hàm tạo nào được viết rõ ràng thì một hàm tạo trống sẽ được tạo tự động.
  4. Hàm tạo có thể bị ghi đè.
  5. Nếu một hàm tạo có tham số đã được tạo, nhưng bạn cũng cần một hàm tạo không có tham số, thì bạn cần phải viết nó riêng vì nó không được tạo tự động.

6. Hai lớp nào không kế thừa từ Object?

Đừng để bị lừa bởi những lời khiêu khích, không có lớp nào như vậy: tất cả các lớp trực tiếp hoặc thông qua tổ tiên đều được kế thừa từ lớp Object!

7. Biến cục bộ là gì?

Một câu hỏi phổ biến khác trong cuộc phỏng vấn nhà phát triển Java. Biến cục bộ là biến được định nghĩa bên trong một phương thức và tồn tại cho đến thời điểm phương thức đó được thực thi. Khi quá trình thực thi kết thúc, biến cục bộ sẽ không còn tồn tại. Đây là chương trình sử dụng biến cục bộ helloMessage trong phương thức main():
public static void main(String[] args) {
   String helloMessage;
   helloMessage = "Hello, World!";
   System.out.println(helloMessage);
}

8. Biến instance là gì?

Biến thể hiện là một biến được định nghĩa bên trong một lớp và nó tồn tại cho đến thời điểm đối tượng tồn tại. Một ví dụ là lớp Bee, có hai biến honeyCapacity và maxNectarCapacity:
public class Bee {

   /**
    * Current nectar capacity
    */
   private double nectarCapacity;

   /**
    * Maximal nectar that can take bee.
    */
   private double maxNectarCapacity = 20.0;

  ...
}

9. Công cụ sửa đổi quyền truy cập là gì?

Công cụ sửa đổi quyền truy cập là một công cụ cho phép bạn tùy chỉnh quyền truy cập vào các lớp, phương thức và biến. Có các sửa đổi sau, được sắp xếp theo thứ tự tăng quyền truy cập:
  1. private- được sử dụng cho các phương thức, trường và hàm tạo. Cấp độ truy cập chỉ là lớp mà nó được khai báo.
  2. package-private(default)- có thể được sử dụng cho các lớp học. Chỉ truy cập trong một gói cụ thể trong đó lớp, phương thức, biến, hàm tạo được khai báo.
  3. protected— quyền truy cập tương tự như package-private+ đối với những lớp kế thừa từ một lớp có sửa đổi protected.
  4. public- cũng được sử dụng cho các lớp học. Toàn quyền truy cập trong toàn bộ ứng dụng.
  5. 50 câu hỏi và câu trả lời phỏng vấn cốt lõi về Java hàng đầu.  Phần 1 - 2

10. Phương thức ghi đè là gì?

Ghi đè phương thức xảy ra khi lớp con muốn thay đổi hành vi của lớp cha. Nếu bạn muốn những gì trong phương thức gốc được thực thi, bạn có thể sử dụng cấu trúc như super.methodName() ở con, nó sẽ thực hiện công việc của phương thức gốc và chỉ sau đó mới thêm logic. Yêu cầu cần đáp ứng:
  • chữ ký phương thức phải giống nhau;
  • giá trị trả về phải giống nhau.

11. Chữ ký phương thức là gì?

50 câu hỏi và câu trả lời phỏng vấn cốt lõi về Java hàng đầu.  Phần 1 - 3Chữ ký phương thức là một tập hợp tên của phương thức và các đối số mà phương thức đó chấp nhận. Chữ ký phương thức là mã định danh duy nhất cho một phương thức khi nạp chồng phương thức.

12. Nạp chồng phương thức là gì?

Nạp chồng phương thức là một thuộc tính đa hình trong đó bằng cách thay đổi chữ ký phương thức, bạn có thể tạo các phương thức khác nhau cho cùng một hành động:
  • cùng tên phương thức;
  • lập luận khác nhau;
  • có thể có một kiểu trả về khác.
Ví dụ: hàm tương tự add()ArrayListthể được nạp chồng như sau và sẽ thực hiện phép cộng theo một cách khác, tùy thuộc vào các đối số được đưa vào:
  • add(Object o)- chỉ cần thêm một đối tượng;
  • add(int index, Object o)— thêm một đối tượng vào một chỉ mục cụ thể;
  • add(Collection<Object> c)— thêm danh sách các đối tượng;
  • add(int index, Collection<Object> c)— thêm danh sách các đối tượng, bắt đầu từ một chỉ mục nhất định.

13. Giao diện là gì?

Đa kế thừa không được triển khai trong Java, vì vậy để khắc phục vấn đề này, các giao diện như chúng ta biết đã được thêm vào;) Trong một thời gian dài, các giao diện chỉ có các phương thức mà không triển khai chúng. Là một phần của câu trả lời này, chúng tôi sẽ nói về họ. Ví dụ:

public interface Animal {
   void makeSound();
   void eat();
   void sleep();
}
Một số sắc thái theo sau từ điều này:
  • tất cả các phương thức trong giao diện đều công khai và trừu tượng;
  • tất cả các biến đều là biến cuối cùng tĩnh công khai;
  • các lớp không kế thừa chúng (mở rộng) mà triển khai chúng (triển khai). Hơn nữa, bạn có thể triển khai bao nhiêu giao diện tùy thích.
  • các lớp triển khai giao diện phải cung cấp việc triển khai tất cả các phương thức mà giao diện đó có.
Như thế này:
public class Cat implements Animal {
   public void makeSound() {
       // method implementation
   }

   public void eat() {
       // implementation
   }

   public void sleep() {
       // implementation
   }
}

14. Phương thức mặc định trong Giao diện là gì?

Bây giờ hãy nói về các phương thức mặc định. Vì cái gì, vì ai? Những phương pháp này đã được thêm vào để biến mọi thứ thành “của cả bạn và của chúng tôi”. Tôi đang nói về cái gì vậy? Đúng vậy, một mặt, cần phải thêm chức năng mới: lambdas, Stream API, mặt khác, cần phải để lại thứ mà Java nổi tiếng - khả năng tương thích ngược. Để làm được điều này, cần phải đưa các giải pháp làm sẵn vào giao diện. Đây là cách các phương thức mặc định đến với chúng tôi. Nghĩa là, phương thức mặc định là một phương thức được triển khai trong giao diện có từ khóa default. Ví dụ: phương thức nổi tiếng stream()trong Collection. Hãy xem thử, giao diện này không đơn giản như bạn tưởng đâu ;). Hoặc cũng là một phương thức nổi tiếng không kém forEach()từ Iterable. Nó cũng không tồn tại cho đến khi các phương thức mặc định được thêm vào. Nhân tiện, bạn cũng có thể đọc về điều này trên JavaRush .

15. Làm thế nào để kế thừa hai phương thức mặc định giống hệt nhau?

Dựa trên câu trả lời trước về phương thức mặc định là gì, bạn có thể hỏi một câu hỏi khác. Nếu bạn có thể triển khai các phương thức trong giao diện thì về mặt lý thuyết bạn có thể triển khai hai giao diện với cùng một phương thức và thực hiện việc này như thế nào? Có hai giao diện khác nhau với cùng một phương thức:
interface A {
   default void foo() {
       System.out.println("Foo A");
   }
}

interface B {
   default void foo() {
       System.out.println("Foo B");
   }
}
Và có một lớp thực hiện hai giao diện này. Để tránh sự không chắc chắn và để biên dịch mã, chúng ta cần ghi đè phương thức foo()trong lớp Cvà chúng ta có thể chỉ cần gọi một phương thức foo()của bất kỳ giao diện nào trong đó - Ahoặc B. Nhưng làm thế nào để chọn một phương thức giao diện cụ thể Аhay В? Có một cấu trúc như thế này cho việc này A.super.foo():
public class C implements A, B {
   @Override
   public void foo() {
       A.super.foo();
   }
}
hoặc:
public class C implements A, B {
   @Override
   public void foo() {
       B.super.foo();
   }
}
Do đó, một phương thức foo()lớp Csẽ sử dụng phương thức mặc định foo()từ giao diện Ahoặc phương thức foo()từ giao diện B.

16. Các phương thức và lớp trừu tượng là gì?

Java có một từ dành riêng abstractđược sử dụng để biểu thị các lớp và phương thức trừu tượng. Đầu tiên, một số định nghĩa. Phương thức trừu tượng là phương thức được tạo mà không cần triển khai với từ khóa abstracttrong lớp trừu tượng. Tức là đây là một phương thức giống như trong giao diện, chỉ có thêm từ khóa, ví dụ:
public abstract void foo();
Lớp trừu tượng là lớp cũng có abstracttừ:
public abstract class A {

}
Một lớp trừu tượng có một số tính năng:
  • một đối tượng không thể được tạo ra trên cơ sở của nó;
  • nó có thể có các phương thức trừu tượng;
  • nó có thể không có phương pháp trừu tượng.
Các lớp trừu tượng là cần thiết để khái quát hóa một số loại trừu tượng (xin lỗi vì tautology), không tồn tại trong đời thực, nhưng nó chứa nhiều hành vi và trạng thái phổ biến (nghĩa là các phương thức và biến). Có quá nhiều ví dụ từ cuộc sống. Mọi thứ đều ở xung quanh chúng ta. Nó có thể là “động vật”, “ô tô”, “hình hình học”, v.v.

17. Sự khác biệt giữa Chuỗi, Trình tạo chuỗi và Bộ đệm chuỗi là gì?

Các giá trị Stringđược lưu trữ trong một nhóm chuỗi không đổi. Khi một hàng được tạo, nó sẽ xuất hiện trong nhóm này. Và sẽ không thể xóa nó được. Ví dụ:
String name = "book";
...biến sẽ tham chiếu đến nhóm chuỗi Nhóm chuỗi không đổi 50 câu hỏi và câu trả lời phỏng vấn cốt lõi về Java hàng đầu.  Phần 1 - 4 Nếu bạn đặt tên biến thành một giá trị khác, bạn sẽ nhận được những điều sau:
name = "pen";
Nhóm chuỗi không đổi 50 câu hỏi và câu trả lời phỏng vấn cốt lõi về Java hàng đầu.  Phần 1 - 5Vì vậy, hai giá trị này sẽ vẫn ở đó. Bộ đệm chuỗi:
  • các giá trị Stringđược lưu trữ trên ngăn xếp. Nếu giá trị được thay đổi thì giá trị mới sẽ được thay thế bằng giá trị cũ;
  • String Bufferđược đồng bộ hóa và do đó luồng an toàn;
  • Do sự an toàn của luồng, tốc độ hoạt động còn nhiều điều mong muốn.
Ví dụ:
StringBuffer name = "book";
50 câu hỏi và câu trả lời phỏng vấn cốt lõi về Java hàng đầu.  Phần 1 - 6Ngay khi giá trị của tên thay đổi, giá trị trên ngăn xếp sẽ thay đổi: 50 câu hỏi và câu trả lời phỏng vấn cốt lõi về Java hàng đầu.  Phần 1 - 7StringBuilder Chính xác giống như StringBuffer, chỉ có điều nó không an toàn cho luồng. Vì vậy, tốc độ của nó rõ ràng là cao hơn so với StringBuffer.

18. Sự khác biệt giữa lớp trừu tượng và giao diện là gì?

Lớp trừu tượng:
  • các lớp trừu tượng có một hàm tạo mặc định; nó được gọi mỗi khi một lớp con của lớp trừu tượng này được tạo ra;
  • chứa cả phương pháp trừu tượng và không trừu tượng. Nhìn chung, nó có thể không chứa các phương thức trừu tượng, nhưng vẫn là một lớp trừu tượng;
  • một lớp kế thừa từ một lớp trừu tượng chỉ được thực hiện các phương thức trừu tượng;
  • một lớp trừu tượng có thể chứa Biến thể hiện (xem câu hỏi số 5).
Giao diện:
  • không có hàm tạo và không thể khởi tạo;
  • chỉ nên thêm các phương thức trừu tượng (không tính các phương thức mặc định);
  • các lớp triển khai giao diện phải triển khai tất cả các phương thức (không tính các phương thức mặc định);
  • giao diện chỉ có thể chứa hằng số.

19. Tại sao việc truy cập một phần tử trong mảng lại mất O(1)?

Câu hỏi này theo đúng nghĩa đen là từ cuộc phỏng vấn trước. Sau này tôi mới biết, câu hỏi này được hỏi là để xem một người suy nghĩ như thế nào. Rõ ràng là kiến ​​thức này có rất ít ý nghĩa thực tế: chỉ cần biết sự thật này là đủ. Đầu tiên, chúng ta cần làm rõ rằng O(1) là ký hiệu cho độ phức tạp về thời gian của thuật toán khi hoạt động diễn ra trong thời gian không đổi. Đó là, chỉ định này là thực hiện nhanh nhất. Để trả lời câu hỏi này chúng ta cần hiểu rõ chúng ta biết gì về mảng? Để tạo một mảng int, chúng ta phải viết như sau:
int[] intArray = new int[100];
Một số kết luận có thể được rút ra từ bản ghi này:
  1. Khi tạo một mảng, kiểu của nó đã được biết, nếu biết kiểu thì sẽ biết rõ kích thước của mỗi ô trong mảng.
  2. Người ta biết kích thước của mảng sẽ là bao nhiêu.
Từ đó suy ra: để hiểu nên ghi vào ô nào, bạn chỉ cần tính toán vùng bộ nhớ nào sẽ ghi vào. Đối với một chiếc ô tô, điều đó không thể dễ dàng hơn. Máy có phần khởi đầu là bộ nhớ được phân bổ, một số phần tử và kích thước một ô. Từ đó, rõ ràng không gian ghi sẽ bằng vị trí bắt đầu của mảng + kích thước của ô nhân với kích thước của nó.

Làm cách nào để bạn có được quyền truy cập O(1) vào các đối tượng trong ArrayList?

Câu hỏi này nối tiếp câu hỏi trước. Đúng là khi chúng ta làm việc với một mảng và có các giá trị nguyên thủy ở đó, chúng ta biết trước kích thước của kiểu này khi nó được tạo. Nhưng điều gì sẽ xảy ra nếu có một sơ đồ giống như trong hình: Топ-50 Java Core вопросов и ответов на собеседовании. Часть 1 - 8và chúng tôi muốn tạo một bộ sưu tập với các phần tử thuộc loại A và thêm các cách triển khai khác nhau - B, C, D:
List<A> list = new ArrayList();
list.add(new B());
list.add(new C());
list.add(new D());
list.add(new B());
Trong tình huống này, làm thế nào bạn có thể hiểu kích thước của mỗi ô sẽ là bao nhiêu, vì mỗi đối tượng sẽ khác nhau và có thể có các trường bổ sung khác nhau (hoặc hoàn toàn khác nhau). Phải làm gì? Ở đây câu hỏi được đặt ra theo cách gây nhầm lẫn và nhầm lẫn. Chúng ta biết rằng trên thực tế, bộ sưu tập không lưu trữ các đối tượng mà chỉ liên kết đến các đối tượng này. Và tất cả các liên kết đều có cùng kích thước, và nó được biết đến. Vì vậy, việc đếm không gian ở đây hoạt động theo cách tương tự như câu hỏi trước.

21. Tự động đóng hộp và mở hộp

Bối cảnh lịch sử: autoboxing và autounboxing là một trong những cải tiến chính của JDK 5. Autoboxing là quá trình chuyển đổi tự động từ loại nguyên thủy sang lớp trình bao bọc thích hợp. Tự động mở hộp - thực hiện hoàn toàn ngược lại với tự động đóng hộp - chuyển đổi lớp bao bọc thành lớp nguyên thủy. Nhưng nếu có một giá trị bao bọc nullthì một ngoại lệ sẽ được đưa ra trong quá trình giải nén NullPointerException.

Phù hợp nguyên thủy - trình bao bọc

nguyên thủy Trình bao bọc lớp
boolean Boolean
int số nguyên
byte Byte
ký tự Tính cách
trôi nổi Trôi nổi
dài Dài
ngắn Ngắn
gấp đôi Gấp đôi

Tự động đóng gói xảy ra:

  • khi gán một tham chiếu nguyên thủy cho lớp trình bao bọc:

    TRƯỚC Java 5:

    //manual packaging or how it was BEFORE Java 5.
    public void boxingBeforeJava5() {
       Boolean booleanBox = new Boolean(true);
       Integer intBox = new Integer(3);
       // and so on to other types
    }
    
    после Java 5:
    //automatic packaging or how it became in Java 5.
    public void boxingJava5() {
       Boolean booleanBox = true;
       Integer intBox = 3;
       // and so on to other types
    }
  • khi truyền một đối số nguyên thủy làm đối số cho một phương thức mong đợi một trình bao bọc:

    public void exampleOfAutoboxing() {
       long age = 3;
       setAge(age);
    }
    
    public void setAge(Long age) {
       this.age = age;
    }

Tự động giải nén xảy ra:

  • khi chúng ta gán một biến nguyên thủy cho lớp trình bao bọc:

    //before Java 5:
    int intValue = new Integer(4).intValue();
    double doubleValue = new Double(2.3).doubleValue();
    char c = new Character((char) 3).charValue();
    boolean b = Boolean.TRUE.booleanValue();
    
    //and after JDK 5:
    int intValue = new Integer(4);
    double doubleValue = new Double(2.3);
    char c = new Character((char) 3);
    boolean b = Boolean.TRUE;
  • Trong trường hợp thực hiện các phép tính số học. Chúng chỉ áp dụng cho các kiểu nguyên thủy, để làm được điều này bạn cần phải giải nén về kiểu nguyên thủy.

    // Before Java 5
    Integer integerBox1 = new Integer(1);
    Integer integerBox2 = new Integer(2);
    
    // for comparison it was necessary to do this:
    integerBox1.intValue() > integerBox2.intValue()
    
    //в Java 5
    integerBox1 > integerBox2
  • khi được chuyển đến trình bao bọc trong một phương thức chấp nhận nguyên hàm tương ứng:

    public void exampleOfAutoboxing() {
       Long age = new Long(3);
       setAge(age);
    }
    
    public void setAge(long age) {
       this.age = age;
    }

22. Từ khóa cuối cùng là gì và sử dụng nó ở đâu?

Từ khóa finalcó thể được sử dụng cho các biến, phương thức và lớp.
  1. Biến cuối cùng không thể được gán lại cho đối tượng khác.
  2. lớp cuối cùng là vô trùng)) nó không thể có người thừa kế.
  3. phương thức cuối cùng không thể được ghi đè lên tổ tiên.
Chúng ta đã đề cập đến phần trên cùng, bây giờ hãy thảo luận chi tiết hơn.

biến cuối cùng

;Java cung cấp cho chúng ta hai cách để tạo một biến và gán một số giá trị cho nó:
  1. Bạn có thể khai báo một biến và khởi tạo nó sau.
  2. Bạn có thể khai báo một biến và gán nó ngay lập tức.
Ví dụ sử dụng biến cuối cùng cho những trường hợp này:
public class FinalExample {

   //final static variable, which is immediately initialized:
   final static String FINAL_EXAMPLE_NAME = "I'm likely final one";

   //final is a variable that is not initialized, but will only work if
   //initialize this in the constructor:
   final long creationTime;

   public FinalExample() {
       this.creationTime = System.currentTimeMillis();
   }

   public static void main(String[] args) {
       FinalExample finalExample = new FinalExample();
       System.out.println(finalExample.creationTime);

       // final field FinalExample.FINAL_EXAMPLE_NAME cannot be assigned
//    FinalExample.FINAL_EXAMPLE_NAME = "Not you're not!";

       // final field Config.creationTime cannot be assigned
//    finalExample.creationTime = 1L;
   }
}

Biến cuối cùng có thể được coi là một hằng số không?

Vì chúng ta không thể gán giá trị mới cho biến cuối cùng nên có vẻ như đây là các biến không đổi. Nhưng đây chỉ là cái nhìn đầu tiên. Nếu kiểu dữ liệu mà biến tham chiếu đến là immutable, thì có, đó là hằng số. Nhưng nếu kiểu dữ liệu mutablecó thể thay đổi, thì việc sử dụng các phương thức và biến sẽ có thể thay đổi giá trị của đối tượng mà finalbiến đó tham chiếu tới và trong trường hợp này nó không thể được gọi là hằng số. Vì vậy, ví dụ cho thấy rằng một số biến cuối cùng thực sự là hằng số, nhưng một số thì không và chúng có thể được thay đổi.
public class FinalExample {

   //immutable final variables:
   final static String FINAL_EXAMPLE_NAME = "I'm likely final one";
   final static Integer FINAL_EXAMPLE_COUNT  = 10;

   // mutable filter variables
   final List<String> addresses = new ArrayList();
   final StringBuilder finalStringBuilder = new StringBuilder("constant?");
}

Biến cuối cùng cục bộ

Khi finalmột biến được tạo bên trong một phương thức, nó được gọi là local finalbiến:
public class FinalExample {

   public static void main(String[] args) {
       // This is how you can
       final int minAgeForDriveCar = 18;

       // or you can do it this way, in the foreach loop:
       for (final String arg : args) {
           System.out.println(arg);
       }
   }

}
Chúng ta có thể sử dụng từ khóa finaltrong vòng lặp mở rộng forvì sau khi hoàn thành vòng lặp, formột biến mới sẽ được tạo mỗi lần. Nhưng không điều nào trong số này áp dụng cho vòng lặp for thông thường, vì vậy đoạn mã bên dưới sẽ gây ra lỗi thời gian biên dịch.
// final local changed j cannot be assigned
for (final int i = 0; i < args.length; i ++) {
   System.out.println(args[i]);
}

Lớp cuối cùng

Bạn không thể mở rộng một lớp được khai báo là final. Nói một cách đơn giản, không có lớp nào có thể kế thừa từ lớp này. Một ví dụ tuyệt vời finalvề một lớp trong JDK là String. Bước đầu tiên để tạo một lớp bất biến là đánh dấu nó là final, để nó không thể mở rộng:
public final class FinalExample {
}

// Compilation error here
class WantsToInheritFinalClass extends FinalExample {
}

Phương pháp cuối cùng

Khi một phương thức được đánh dấu là cuối cùng, nó được gọi là phương thức cuối cùng (hợp lý, phải không?). Phương thức Final không thể bị ghi đè trong lớp con. Nhân tiện, các phương thức trong lớp Object - wait() và notification() - là cuối cùng, vì vậy chúng ta không có cơ hội ghi đè chúng.
public class FinalExample {
   public final String generateAddress() {
       return "Some address";
   }
}

class ChildOfFinalExample extends FinalExample {

   // compile error here
   @Override
   public String generateAddress() {
       return "My OWN Address";
   }
}

Cách thức và vị trí sử dụng cuối cùng trong Java

  • sử dụng từ khóa cuối cùng để xác định một số hằng số cấp lớp;
  • tạo các biến cuối cùng cho các đối tượng khi bạn không muốn sửa đổi chúng. Ví dụ: các thuộc tính dành riêng cho đối tượng mà chúng ta có thể sử dụng cho mục đích ghi nhật ký;
  • nếu bạn không muốn lớp học được mở rộng, hãy đánh dấu nó là lớp học cuối cùng;
  • nếu bạn cần tạo một lớp < bất biến, bạn cần phải làm cho nó trở thành lớp cuối cùng;
  • nếu bạn muốn việc triển khai một phương thức không thay đổi ở các hậu duệ của nó, hãy chỉ định phương thức đó là final. Điều này rất quan trọng để đảm bảo rằng việc thực hiện không thay đổi.

23. Đột biến bất biến là gì?

Có thể thay đổi

Có thể thay đổi là các đối tượng có trạng thái và biến có thể thay đổi sau khi tạo. Ví dụ: các lớp như StringBuilder, StringBuffer. Ví dụ:
public class MutableExample {

   private String address;

   public MutableExample(String address) {
       this.address = address;
   }

   public String getAddress() {
       return address;
   }

   // this setter can change the name field
   public void setAddress(String address) {
       this.address = address;
   }

   public static void main(String[] args) {

       MutableExample obj = new MutableExample("first address");
       System.out.println(obj.getAddress());

       // update the name field, so this is a mutable object
       obj.setAddress("Updated address");
       System.out.println(obj.getAddress());
   }
}

bất biến

Bất biến là các đối tượng có trạng thái và biến không thể thay đổi sau khi đối tượng được tạo. Tại sao không phải là một khóa tuyệt vời cho HashMap, phải không?) Ví dụ: Chuỗi, Số nguyên, Double, v.v. Ví dụ:
// make this class final so no one can change it
public final class ImmutableExample {

   private String address;

   ImmutableExample (String address) {
       this.address = address;
   }

   public String getAddress() {
       return address;
   }

   //remove the setter

   public static void main(String[] args) {

       ImmutableExample obj = new ImmutableExample("old address");
       System.out.println(obj.getAddress());

       // Therefore, do not change this field in any way, so this is an immutable object
       // obj.setName("new address");
       // System.out.println(obj.getName());

   }
}

24. Làm thế nào để viết một lớp bất biến?

Sau khi bạn tìm ra đối tượng có thể thay đổi và không thể thay đổi là gì, câu hỏi tiếp theo là tự nhiên - viết nó như thế nào? Để viết một lớp bất biến, bạn cần làm theo các bước đơn giản:
  • làm cho lớp học kết thúc.
  • đặt tất cả các trường ở chế độ riêng tư và chỉ tạo getters cho chúng. Tất nhiên, người định cư là không cần thiết.
  • Đặt tất cả các trường có thể thay đổi ở dạng cuối cùng để giá trị chỉ có thể được đặt một lần.
  • khởi tạo tất cả các trường thông qua hàm tạo, thực hiện một bản sao sâu (nghĩa là sao chép chính đối tượng, các biến của nó, các biến của biến, v.v.)
  • sao chép các đối tượng biến có thể thay đổi trong getters để chỉ trả về bản sao của các giá trị chứ không phải tham chiếu đến các đối tượng thực tế.
Ví dụ:
/**
* An example of creating an immutable object.
*/
public final class FinalClassExample {

   private final int age;

   private final String name;

   private final HashMap<String, String> addresses;

   public int getAge() {
       return age;
   }


   public String getName() {
       return name;
   }

   /**
    * Clone the object before returning it.
    */
   public HashMap<String, String> getAddresses() {
       return (HashMap<String, String>) addresses.clone();
   }

   /**
    * In the constructor, deep copy the mutable objects.
    */
   public FinalClassExample(int age, String name, HashMap<String, String> addresses) {
       System.out.println("Performing a deep copy in the constructor");
       this.age = age;
       this.name = name;
       HashMap<String, String> temporaryMap = new HashMap<>();
       String key;
       Iterator<String> iterator = addresses.keySet().iterator();
       while (iterator.hasNext()) {
           key = iterator.next();
           temporaryMap.put(key, addresses.get(key));
       }
       this.addresses = temporaryMap;
   }
}
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION