CHÀO! Bạn đã quen thuộc với từ "sửa đổi". Ở mức tối thiểu, bạn đã gặp phải các công cụ sửa đổi quyền truy cập (công khai, riêng tư) và công cụ sửa đổi tĩnh. Hôm nay chúng ta sẽ thảo luận về một công cụ sửa đổi đặc biệt có tên là final . Bạn có thể nói phần sửa đổi cuối cùng là "chất kết dính" trong chương trình của chúng tôi, nơi cần có các hành vi liên tục, rõ ràng, không thay đổi. Có ba vị trí trong chương trình của bạn mà bạn có thể sử dụng nó: lớp, phương thức và biến. Các giá trị cố định trong Java: cuối cùng, hằng số và không thay đổi - 2 Hãy lướt qua chúng theo thứ tự. Nếu công cụ sửa đổi cuối cùng được sử dụng trong khai báo lớp, điều đó có nghĩa là lớp không thể được kế thừa. Trong các bài học trước, chúng ta đã sử dụng một ví dụ kế thừa đơn giản: chúng ta có một Animallớp cha và hai lớp con: CatDog

public class Animal {
}



public class Cat extends Animal {
   // Fields and methods of the Cat class
}


public class Dog extends Animal {

   // Fields and methods of the Dog class
}
Tuy nhiên, nếu chúng ta sử dụng công cụ sửa đổi cuối cùng trên Animallớp, lớp CatDogkhông thể kế thừa nó.

public final class Animal {

}

public class Cat extends Animal {

   // Error! Cannot inherit from final Animal
}
Trình biên dịch ngay lập tức phát sinh lỗi. Trong Java, nhiều lớp cuối cùng đã được triển khai. Trong số những người bạn sử dụng thường xuyên, Stringlà nổi tiếng nhất. Hơn nữa, nếu một lớp được khai báo là final thì tất cả các phương thức của lớp đó cũng trở thành final . Điều đó nghĩa là gì? Nếu một phương thức được khai báo bằng công cụ sửa đổi cuối cùng , bạn không thể ghi đè phương thức đó. Ví dụ, ở đây chúng ta có một Animallớp khai báo một speak()phương thức. Nhưng, chó và mèo chắc chắn "nói" theo những cách khác nhau. Vì vậy, chúng ta sẽ khai báo các phương thức speak() trong cả hai lớp CatDognhưng chúng ta sẽ triển khai chúng theo cách khác.

public class Animal {
  
   public void speak() {
       System.out.println("Hello!");
   }
}

public class Cat extends Animal {

   @Override
   public void speak() {
       System.out.println("Meow!");
   }
}

public class Dog extends Animal {

   @Override
   public void speak() {
       System.out.println("Woof!");
   }
}
Chúng tôi đã tạo CatDogcác lớp ghi đè lên phương thức được khai báo trong lớp cha. Bây giờ, một con vật sẽ nói khác đi, tùy thuộc vào loại đối tượng của nó:

public class Main {

   public static void main(String[] args) {

       Cat cat = new Cat();
       Dog dog = new Dog();
      
       cat.speak();
       dog.speak();
   }
}
Đầu ra: Meo meo! Gâu! Tuy nhiên, nếu chúng ta khai báo phương thức Animalcủa lớp speak()là cuối cùng, thì chúng ta không thể ghi đè lên nó trong các lớp khác:

public class Animal {

   public final void speak() {
       System.out.println("Hello!");
   }
}


public class Cat extends Animal {

   @Override
   public void speak() {// Error! A final method can't be overridden!
       System.out.println("Meow!");
   }
}
Và các đối tượng của chúng ta sẽ buộc phải sử dụng speak()phương thức như được định nghĩa trong lớp cha:

public static void main(String[] args) {

   Cat cat = new Cat();
   Dog dog = new Dog();

   cat.speak();
   dog.speak();
}
Đầu ra: Xin chào! Xin chào! Bây giờ, liên quan đến các biến cuối cùng . Chúng còn được gọi là hằng số . Đầu tiên (và quan trọng nhất), không thể thay đổi giá trị ban đầu được gán cho một giá trị không đổi. Nó được chỉ định một lần và mãi mãi.

public class Main {
  
   private static final int CONSTANT_EXAMPLE = 333;

   public static void main(String[] args) {

       CONSTANT_EXAMPLE = 999;// Error! You can't assign a new value to a final variable!
   }
}
Một hằng số không cần phải được khởi tạo ngay lập tức. Điều đó có thể được thực hiện sau này. Tuy nhiên, giá trị ban đầu được gán cho nó sẽ giữ nguyên mãi mãi.

public static void main(String[] args) {

   final int CONSTANT_EXAMPLE;

   CONSTANT_EXAMPLE = 999;// This is allowed
}
Thứ hai, lưu ý tên biến của chúng tôi. Java có một quy ước đặt tên khác cho các hằng số. Nó không phải là ký hiệu camelCase thông thường . Nếu nó là một biến thông thường, chúng ta sẽ gọi nó là hằngVí dụ. Tuy nhiên, tên của các hằng số được viết hoa toàn bộ, với dấu gạch dưới giữa các từ (nếu có nhiều hơn một từ), ví dụ: "CONSTANT_EXAMPLE". Tại sao chúng ta cần hằng số? Chúng rất hữu ích nếu, ví dụ, có một giá trị cố định mà bạn thường xuyên sử dụng trong một chương trình. Ví dụ: bạn đã quyết định làm nên lịch sử và tự mình viết trò chơi "The Witcher 4". Trò chơi rõ ràng sẽ thường xuyên sử dụng tên của nhân vật chính: "Geralt of Rivia". Chuỗi này (và tên của các anh hùng khác) tốt nhất nên được khai báo là một hằng số: giá trị của nó sẽ được lưu trữ ở một nơi và bạn chắc chắn sẽ không mắc lỗi đánh máy khi nhập nó hàng triệu lần.

public class TheWitcher4 {

   private static final String GERALT_NAME = "Geralt of Rivia";
   private static final String YENNEFER_NAME = "Yennefer of Wengerberg";
   private static final String TRISS_NAME = "Triss Merigold";

   public static void main(String[] args) {

       System.out.println("The Witcher 4");
       System.out.println("It's already the fourth Witcher game, but " + GERALT_NAME + " still can't decide who" +
               " he likes more: " + YENNEFER_NAME + " or " + TRISS_NAME);

       System.out.println("But, if you've never played The Witcher before, we'll start from the beginning.");
       System.out.println("The protagonist's name is " + GERALT_NAME);
       System.out.println(GERALT_NAME + " is a witcher, a monster hunter");
   }
}
Đầu ra: The Witcher 4 Đã là trò chơi Witcher thứ tư, nhưng Geralt of Rivia vẫn chưa thể quyết định xem anh ấy thích ai hơn: Yennefer of Wengerberg hay Triss Merigold Nhưng, nếu bạn chưa từng chơi The Witcher trước đây, chúng ta sẽ bắt đầu từ bắt đầu. Tên nhân vật chính là Geralt of Rivia Geralt of Rivia là một phù thủy, một thợ săn quái vật Chúng tôi đã tuyên bố tên của các anh hùng là hằng số. Bây giờ chúng tôi chắc chắn sẽ không mắc lỗi đánh máy và không cần phải viết tay mỗi lần. Một điểm cộng nữa: nếu chúng ta cần thay đổi giá trị của biến trong toàn bộ chương trình, bạn có thể thực hiện việc đó ở một nơi, thay vì sửa đổi thủ công trên toàn bộ cơ sở mã. :)

loại bất biến

Khi bạn đã làm việc với Java, có lẽ bạn đã quen với ý tưởng rằng các lập trình viên có quyền kiểm soát gần như hoàn toàn đối với trạng thái của tất cả các đối tượng. Nếu bạn muốn tạo một Catđối tượng, bạn có thể. Nếu bạn muốn đổi tên nó, bạn có thể. Nếu bạn muốn thay đổi tuổi của nó hoặc cái gì khác, bạn có thể. Nhưng Java có một số kiểu dữ liệu có thuộc tính đặc biệt. Chúng là bất biến . Nếu một lớp là bất biến, thì trạng thái của các đối tượng của nó không thể thay đổi được. Muốn một số ví dụ? Nó có thể làm bạn ngạc nhiên, nhưng lớp bất biến nổi tiếng nhất là String! Vì vậy, chúng tôi thực sự không thể thay đổi giá trị của Chuỗi? Vâng, hãy thử nó:

public static void main(String[] args) {

   String str1 = "I love Java";

   String str2 = str1;// Both reference variables point to the same string.
   System.out.println(str2);

   str1 = "I love Python";// but changing str1 has no impact on str2
   System.out.println(str2);// str2 continues to point to the "I love Java" string, but str1 now points to a different object
}
Đầu ra: Tôi yêu Java Tôi yêu Java Sau khi chúng ta viết

str1 = "I love Python";
đối "I love Java"tượng chuỗi không thay đổi hoặc đi đâu cả. Nó vẫn vui vẻ tồn tại và có cùng một văn bản như trước đây. Mật mã

str1 = "I love Python";
chỉ cần tạo một đối tượng khác, mà str1 bây giờ trỏ đến. Tuy nhiên, dường như chúng ta không thể có bất kỳ ảnh hưởng nào đối với đối tượng chuỗi "Tôi yêu Java". Được rồi, hãy thử một cái gì đó khác! Lớp Stringcó đầy đủ các phương thức và một số trong số chúng dường như thay đổi trạng thái của đối tượng! Ví dụ, có một replace()phương pháp. Hãy thay đổi từ "Java" thành "Python" trong chuỗi của chúng ta!

public static void main(String[] args) {

   String str1 = "I love Java";

   String str2 = str1;// Both reference variables point to the same string.
   System.out.println(str2);

   str1.replace("Java", "Python");// We try to change the state of str1 by swapping the word "Java" with "Python"
   System.out.println(str2);
}
Đầu ra: Tôi yêu Java Tôi yêu Java Nó không hoạt động trở lại! Có lẽ phương pháp thay thế không hoạt động? Hãy thử một cái gì đó khác. Ví dụ, substring(). Nó trả về một chuỗi con dựa trên các chỉ số ký tự được truyền dưới dạng đối số. Hãy cắt bỏ 10 ký tự đầu tiên của chuỗi:

public static void main(String[] args) {

   String str1 = "I love Java";

   String str2 = str1;// Both reference variables point to the same string.
   System.out.println(str2);

   str1.substring(10);// Truncate the original String 
   System.out.println(str2);
}
Đầu ra: Tôi yêu Java Tôi yêu Java Giá trị cố định trong Java: cuối cùng, hằng số và bất biến - 3 Không có gì thay đổi. Và nó không nên có. Như chúng tôi đã nói trước đó, Chuỗi là bất biến. Vì vậy, những gì với tất cả các phương pháp trong Stringlớp? Rốt cuộc, họ có thể cắt bớt chuỗi, thay đổi ký tự, v.v. Điểm quan trọng là gì nếu không có gì xảy ra? Họ thực sự có thể làm những điều này! Tuy nhiên, mỗi lần chúng trả về một Chuỗi mới. Thật vô nghĩa khi viết

str1.replace("Java", "Python");
bởi vì bạn không thể thay đổi đối tượng ban đầu. Nhưng, nếu bạn ghi kết quả của phương thức vào một biến tham chiếu mới, bạn sẽ thấy ngay sự khác biệt!

public static void main(String[] args) {

   String str1 = "I love Java";

   String str2 = str1;// Both reference variables point to the same string.
   System.out.println(str2);

   String str1AfterReplacement =  str1.replace("Java", "Python");
   System.out.println(str2);

   System.out.println(str1AfterReplacement);
}
Tất cả Stringcác phương pháp làm việc theo cách này. Không có gì có thể được thực hiện cho "I love Java"các đối tượng. Bạn chỉ có thể tạo một đối tượng mới và viết: "<đối tượng mới> = kết quả của việc thao tác với "I love Java" object ". Còn những kiểu nào khác là bất biến? Một số mà bạn chắc chắn cần phải nhớ ngay lập tức là tất cả các lớp bao bọc cho các kiểu nguyên thủy. Integer, Byte, Character, Short, , , : tất cả các lớp này đều tạo đối tượng (chúng ta sẽ nói về chúng trong các bài học sắp tới). Điều Booleannày bao gồm các lớp được sử dụng để tạo số lượng lớn, chẳng hạn như và . Gần đây chúng ta đã đề cập đến các ngoại lệ và đề cập đến dấu vết ngăn xếp . Vâng , đoán xem, java.lang.StackTraceElementLongDoubleFloatimmutableBigIntegerBigDecimalcác đối tượng cũng là bất biến. Điều này có ý nghĩa: nếu ai đó có thể thay đổi dữ liệu của ngăn xếp của chúng tôi, điều đó sẽ khiến toàn bộ sự việc trở nên vô nghĩa. Hãy tưởng tượng ai đó đi qua dấu vết ngăn xếp và thay đổi OutOfMemoryError thành FileNotFoundException . Và sau đó bạn sử dụng ngăn xếp đó để tìm nguyên nhân gây ra lỗi. Nhưng chương trình thậm chí không sử dụng tệp. :) Vì vậy, họ đã làm cho những đối tượng này trở nên bất biến, đề phòng. Được rồi, vì vậy nó ít nhiều có ý nghĩa đối với StackTraceElement . Nhưng, tại sao mọi người lại cần biến Chuỗi thành bất biến? Tại sao thay đổi giá trị của họ là một vấn đề? Nó thậm chí có thể sẽ thuận tiện hơn. :/ Cái này có một vài nguyên nhân. Đầu tiên, nó tiết kiệm bộ nhớ. Các chuỗi bất biến có thể được đặt trong nhóm chuỗi, cho phép sử dụng lại chuỗi thay vì tạo chuỗi mới. Thứ hai, để bảo mật. Ví dụ: tên người dùng và mật khẩu là Chuỗi trong hầu hết mọi chương trình. Việc có thể thay đổi chúng có thể dẫn đến các vấn đề về ủy quyền. Có những lý do khác, nhưng nghiên cứu về Java của chúng ta vẫn chưa đề cập đến chúng, vì vậy chúng ta sẽ quay lại với chúng sau.