Làm thế nào người ta có thể tạo ra một lớp Java là bất biến, nhu cầu của tính bất biến là gì và có lợi ích gì khi sử dụng điều này không?
@Immutable
chú thích này từ jcabi-khía cạnh
Làm thế nào người ta có thể tạo ra một lớp Java là bất biến, nhu cầu của tính bất biến là gì và có lợi ích gì khi sử dụng điều này không?
@Immutable
chú thích này từ jcabi-khía cạnh
Câu trả lời:
Đối tượng bất biến là gì?
Đối tượng bất biến là đối tượng sẽ không thay đổi trạng thái sau khi nó được khởi tạo.
Làm thế nào để làm cho một đối tượng bất biến?
Nói chung, một đối tượng bất biến có thể được thực hiện bằng cách xác định một lớp mà không có bất kỳ thành viên nào của nó được hiển thị và không có bất kỳ bộ thiết lập nào.
Lớp sau sẽ tạo một đối tượng không thay đổi:
class ImmutableInt {
private final int value;
public ImmutableInt(int i) {
value = i;
}
public int getValue() {
return value;
}
}
Như có thể thấy trong ví dụ trên, giá trị của ImmutableInt
chỉ có thể được thiết lập khi đối tượng được khởi tạo, và bằng cách chỉ có getter ( getValue
), trạng thái của đối tượng không thể thay đổi sau khi khởi tạo.
Tuy nhiên, cần phải lưu ý rằng tất cả các đối tượng được tham chiếu bởi đối tượng cũng phải là bất biến, hoặc có thể thay đổi trạng thái của đối tượng.
Ví dụ: cho phép một tham chiếu đến một mảng hoặc ArrayList
lấy thông qua một getter sẽ cho phép trạng thái bên trong thay đổi bằng cách thay đổi mảng hoặc tập hợp:
class NotQuiteImmutableList<T> {
private final List<T> list;
public NotQuiteImmutableList(List<T> list) {
// creates a new ArrayList and keeps a reference to it.
this.list = new ArrayList(list);
}
public List<T> getList() {
return list;
}
}
Vấn đề với đoạn mã trên là ArrayList
có thể lấy được getList
và bị thao túng, dẫn đến trạng thái của bản thân đối tượng bị thay đổi, do đó, không phải là bất biến.
// notQuiteImmutableList contains "a", "b", "c"
List<String> notQuiteImmutableList= new NotQuiteImmutableList(Arrays.asList("a", "b", "c"));
// now the list contains "a", "b", "c", "d" -- this list is mutable.
notQuiteImmutableList.getList().add("d");
Một cách để giải quyết vấn đề này là trả về bản sao của mảng hoặc tập hợp khi được gọi từ getter:
public List<T> getList() {
// return a copy of the list so the internal state cannot be altered
return new ArrayList(list);
}
Ưu điểm của tính bất biến là gì?
Lợi thế của tính bất biến đi kèm với tính đồng thời. Rất khó để duy trì tính đúng đắn trong các đối tượng có thể thay đổi, vì nhiều luồng có thể đang cố gắng thay đổi trạng thái của cùng một đối tượng, dẫn đến một số luồng nhìn thấy trạng thái khác nhau của cùng một đối tượng, tùy thuộc vào thời gian đọc và ghi đối tượng đã nói vật.
Bằng cách có một đối tượng không thay đổi, người ta có thể đảm bảo rằng tất cả các luồng đang nhìn vào đối tượng sẽ thấy cùng một trạng thái, vì trạng thái của một đối tượng bất biến sẽ không thay đổi.
return Collections.unmodifiableList(list);
trả lại chế độ xem chỉ đọc trên danh sách.
final
. Nếu không, nó có thể được mở rộng bằng phương thức setter (hoặc các loại phương thức thay đổi khác và trường có thể thay đổi).
final
nếu lớp chỉ có private
các trường vì chúng không thể được truy cập từ các lớp con.
Ngoài các câu trả lời đã được đưa ra, tôi khuyên bạn nên đọc về tính bất biến trong Java hiệu quả, Phiên bản thứ 2, vì có một số chi tiết rất dễ bỏ sót (ví dụ: các bản sao phòng thủ). Thêm vào đó, phiên bản Java thứ hai hiệu quả. là phải đọc cho mọi nhà phát triển Java.
Bạn tạo một lớp không thay đổi như thế này:
public final class Immutable
{
private final String name;
public Immutable(String name)
{
this.name = name;
}
public String getName() { return this.name; }
// No setter;
}
Sau đây là các yêu cầu để làm cho một lớp Java trở nên bất biến:
final
(Vì vậy, không thể tạo các lớp con)final
(Để chúng ta không thể thay đổi giá trị của nó sau khi tạo đối tượng)Các lớp bất biến rất hữu ích vì
- Chúng an toàn theo chuỗi.
- Họ cũng thể hiện điều gì đó sâu sắc về thiết kế của bạn: “Không thể thay đổi điều này.”, Khi nó được áp dụng, đó chính xác là thứ bạn cần.
Tính bất biến có thể đạt được chủ yếu bằng hai cách:
final
thuộc tính phiên bản để tránh gán lạiƯu điểm của tính bất biến là các giả định mà bạn có thể thực hiện trên đối tượng này:
Các lớp bất biến không thể gán lại giá trị sau khi nó được khởi tạo. Hàm khởi tạo gán giá trị cho các biến riêng của nó. Cho đến khi đối tượng trở thành null, các giá trị không thể thay đổi do không có sẵn các phương thức setter.
là bất biến nên đáp ứng sau,
/**
* Strong immutability - by making class final
*/
public final class TestImmutablity {
// make the variables private
private String Name;
//assign value when the object created
public TestImmutablity(String name) {
this.Name = name;
}
//provide getters to access values
public String getName() {
return this.Name;
}
}
Ưu điểm: Đối tượng bất biến chứa các giá trị khởi tạo của nó cho đến khi nó chết.
Lớp bất biến là những lớp có đối tượng không thể thay đổi sau khi tạo.
Các lớp bất biến rất hữu ích cho
Thí dụ
Lớp chuỗi
Ví dụ về mã
public final class Student {
private final String name;
private final String rollNumber;
public Student(String name, String rollNumber) {
this.name = name;
this.rollNumber = rollNumber;
}
public String getName() {
return this.name;
}
public String getRollNumber() {
return this.rollNumber;
}
}
Làm thế nào người ta có thể làm cho một lớp Java trở nên bất biến?
Từ JDK 14+ có JEP 359 , chúng ta có thể sử dụng " records
". Đây là cách đơn giản nhất và hối hả để tạo lớp Immutable.
Một lớp bản ghi là một sóng mang trong suốt, không thay đổi nông đối với một tập hợp các trường cố định được gọi là bản ghi components
cung cấp state
mô tả cho bản ghi. Mỗi thứ component
làm phát sinh một final
trường chứa giá trị đã cung cấp và một accessor
phương thức để truy xuất giá trị. Tên trường và tên trình truy cập khớp với tên của thành phần.
Hãy xem xét ví dụ về việc tạo một hình chữ nhật bất biến
record Rectangle(double length, double width) {}
Không cần khai báo bất kỳ phương thức khởi tạo nào, không cần triển khai các phương thức equals & hashCode. Chỉ cần bất kỳ Hồ sơ nào cần có tên và mô tả trạng thái.
var rectangle = new Rectangle(7.1, 8.9);
System.out.print(rectangle.length()); // prints 7.1
Nếu bạn muốn xác thực giá trị trong quá trình tạo đối tượng, chúng ta phải khai báo hàm tạo một cách rõ ràng.
public Rectangle {
if (length <= 0.0) {
throw new IllegalArgumentException();
}
}
Phần thân của bản ghi có thể khai báo các phương thức tĩnh, trường tĩnh, bộ khởi tạo tĩnh, hàm tạo, phương thức thể hiện và các kiểu lồng nhau.
Phương pháp phiên bản
record Rectangle(double length, double width) {
public double area() {
return this.length * this.width;
}
}
trường tĩnh, phương thức
Vì trạng thái phải là một phần của các thành phần, chúng tôi không thể thêm trường cá thể vào bản ghi. Tuy nhiên, chúng ta có thể thêm các trường và phương thức tĩnh:
record Rectangle(double length, double width) {
static double aStaticField;
static void aStaticMethod() {
System.out.println("Hello Static");
}
}
nhu cầu của tính bất biến là gì và có lợi thế nào khi sử dụng điều này?
Các câu trả lời đã đăng trước đây đủ tốt để biện minh cho sự cần thiết của tính bất biến và đó là ưu
Một cách khác để tạo đối tượng không thay đổi là sử dụng thư viện Immutables.org :
Giả sử rằng các phụ thuộc bắt buộc đã được thêm vào, hãy tạo một lớp trừu tượng với các phương thức truy cập trừu tượng. Bạn có thể làm tương tự bằng cách chú thích bằng giao diện hoặc thậm chí chú thích (@interface):
package info.sample;
import java.util.List;
import java.util.Set;
import org.immutables.value.Value;
@Value.Immutable
public abstract class FoobarValue {
public abstract int foo();
public abstract String bar();
public abstract List<Integer> buz();
public abstract Set<Long> crux();
}
Bây giờ có thể tạo và sau đó sử dụng triển khai không thay đổi đã tạo:
package info.sample;
import java.util.List;
public class FoobarValueMain {
public static void main(String... args) {
FoobarValue value = ImmutableFoobarValue.builder()
.foo(2)
.bar("Bar")
.addBuz(1, 3, 4)
.build(); // FoobarValue{foo=2, bar=Bar, buz=[1, 3, 4], crux={}}
int foo = value.foo(); // 2
List<Integer> buz = value.buz(); // ImmutableList.of(1, 3, 4)
}
}
Một lớp bất biến chỉ đơn giản là một lớp mà các thể hiện của nó không thể sửa đổi được.
Tất cả thông tin chứa trong mỗi trường hợp được cố định trong suốt thời gian tồn tại của đối tượng, vì vậy không bao giờ có thể quan sát thấy thay đổi.
Các lớp bất biến dễ thiết kế, triển khai và sử dụng hơn các lớp có thể thay đổi.
Để làm cho một lớp trở nên bất biến, hãy làm theo năm quy tắc sau:
Không cung cấp các phương thức sửa đổi trạng thái của đối tượng
Đảm bảo rằng lớp học không thể được mở rộng.
Làm cho tất cả các trường cuối cùng.
Đặt tất cả các trường ở chế độ riêng tư.
Đảm bảo quyền truy cập độc quyền vào bất kỳ thành phần có thể thay đổi nào.
Các đối tượng bất biến vốn dĩ an toàn theo luồng; chúng không yêu cầu đồng bộ hóa.
Các đối tượng bất biến có thể được chia sẻ tự do.
Các đối tượng bất biến tạo nên các khối xây dựng tuyệt vời cho các đối tượng khác
@Jack, Việc có các trường và bộ thiết lập cuối cùng trong lớp sẽ không làm cho lớp trở nên bất biến. từ khóa cuối cùng chỉ đảm bảo rằng một biến không bao giờ được gán lại. Bạn cần trả về bản sao sâu của tất cả các trường trong phương thức getter. Điều này sẽ đảm bảo rằng, sau khi lấy một đối tượng từ phương thức getter, trạng thái bên trong của đối tượng không bị xáo trộn.
Là một người nói tiếng Anh không phải là bản ngữ, tôi không thích cách hiểu thông thường về "lớp không thay đổi" là "các đối tượng lớp được xây dựng là không thay đổi"; đúng hơn, bản thân tôi nghiêng về giải thích rằng "đối tượng lớp tự nó là bất biến".
Điều đó nói rằng, "lớp bất biến" là một loại đối tượng bất biến. Sự khác biệt là khi trả lời lợi ích là gì. Theo hiểu biết / diễn giải của tôi, lớp không thay đổi ngăn các đối tượng của nó khỏi các sửa đổi hành vi thời gian chạy.
Hầu hết các câu trả lời ở đây là tốt, và một số đã đề cập đến các quy tắc nhưng tôi cảm thấy rất tốt khi đặt ra các từ tại sao và khi nào chúng ta cần tuân theo các quy tắc này. Vì vậy, tôi đưa ra lời giải thích bên dưới
Và tất nhiên nếu chúng ta cố gắng sử dụng Bộ cài đặt cho các biến cuối cùng đó, trình biên dịch sẽ ném ra lỗi.
public class ImmutableClassExplored {
public final int a;
public final int b;
/* OR
Generally we declare all properties as private, but declaring them as public
will not cause any issues in our scenario if we make them final
public final int a = 109;
public final int b = 189;
*/
ImmutableClassExplored(){
this. a = 111;
this.b = 222;
}
ImmutableClassExplored(int a, int b){
this.a = a;
this.b= b;
}
}
Chúng ta có cần khai báo lớp là 'cuối cùng' không?
1. Chỉ có các thành viên nguyên thủy: Chúng ta không gặp vấn đề Nếu lớp chỉ có các thành viên nguyên thủy, thì chúng ta không cần khai báo lớp là cuối cùng.
2. Có đối tượng là biến thành viên: Nếu chúng ta có đối tượng là biến thành viên thì chúng ta phải làm cho các thành viên của các đối tượng đó cũng là cuối cùng. Có nghĩa là chúng ta cần phải đi sâu xuống dưới cái cây và làm cho tất cả các đối tượng / nguyên thủy là cuối cùng mà có thể không phải lúc nào cũng có thể thực hiện được. Vì vậy, giải pháp là tạo lớp cuối cùng để ngăn chặn sự kế thừa. Vì vậy, không có câu hỏi về các phương thức getter ghi đè lớp con.
Chú thích @Value của Lombok có thể được sử dụng để tạo các lớp bất biến. Nó đơn giản như mã bên dưới.
@Value
public class LombokImmutable {
int id;
String name;
}
Theo tài liệu trên trang của Lombok:
@Value là biến thể bất biến của @Data; tất cả các trường được đặt ở chế độ riêng tư và cuối cùng theo mặc định và bộ định tuyến không được tạo. Bản thân lớp cũng được đặt cuối cùng theo mặc định, bởi vì tính bất biến không phải là thứ có thể bị buộc vào một lớp con. Giống như @Data, các phương thức toString (), equals () và hashCode () hữu ích cũng được tạo, mỗi trường nhận được một phương thức getter và một hàm tạo bao hàm mọi đối số (ngoại trừ các trường cuối cùng được khởi tạo trong khai báo trường). .
Một ví dụ hoạt động đầy đủ có thể được tìm thấy ở đây.
record
có thể được sử dụng?