Làm thế nào để tạo các đối tượng bất biến trong Java?
Những đối tượng nào nên được gọi là bất biến?
Nếu tôi có lớp với tất cả các thành viên tĩnh thì nó có phải là bất biến không?
Làm thế nào để tạo các đối tượng bất biến trong Java?
Những đối tượng nào nên được gọi là bất biến?
Nếu tôi có lớp với tất cả các thành viên tĩnh thì nó có phải là bất biến không?
Câu trả lời:
Dưới đây là các yêu cầu khó của một đối tượng bất biến.
final
nhưng đối tượng vẫn có thể thay đổi được. Tức là private final Date imStillMutable
). Bạn nên thực hiện defensive copies
trong những trường hợp này.Lý do đằng sau việc tạo ra lớp học final
là rất tinh vi và thường bị bỏ qua. Nếu những người không phải là cuối cùng của nó có thể tự do mở rộng lớp, ghi đè public
hoặc protected
hành vi của bạn, thêm các thuộc tính có thể thay đổi, sau đó cung cấp lớp con của họ như một lớp thay thế. Bằng cách khai báo lớp, final
bạn có thể đảm bảo điều này sẽ không xảy ra.
Để xem vấn đề đang diễn ra, hãy xem xét ví dụ dưới đây:
public class MyApp{
/**
* @param args
*/
public static void main(String[] args){
System.out.println("Hello World!");
OhNoMutable mutable = new OhNoMutable(1, 2);
ImSoImmutable immutable = mutable;
/*
* Ahhhh Prints out 3 just like I always wanted
* and I can rely on this super immutable class
* never changing. So its thread safe and perfect
*/
System.out.println(immutable.add());
/* Some sneak programmer changes a mutable field on the subclass */
mutable.field3=4;
/*
* Ahhh let me just print my immutable
* reference again because I can trust it
* so much.
*
*/
System.out.println(immutable.add());
/* Why is this buggy piece of crap printing 7 and not 3
It couldn't have changed its IMMUTABLE!!!!
*/
}
}
/* This class adheres to all the principles of
* good immutable classes. All the members are private final
* the add() method doesn't modify any state. This class is
* just a thing of beauty. Its only missing one thing
* I didn't declare the class final. Let the chaos ensue
*/
public class ImSoImmutable{
private final int field1;
private final int field2;
public ImSoImmutable(int field1, int field2){
this.field1 = field1;
this.field2 = field2;
}
public int add(){
return field1+field2;
}
}
/*
This class is the problem. The problem is the
overridden method add(). Because it uses a mutable
member it means that I can't guarantee that all instances
of ImSoImmutable are actually immutable.
*/
public class OhNoMutable extends ImSoImmutable{
public int field3 = 0;
public OhNoMutable(int field1, int field2){
super(field1, field2);
}
public int add(){
return super.add()+field3;
}
}
Trong thực tế, rất thường gặp vấn đề trên trong môi trường Dependency Injection. Bạn không khởi tạo mọi thứ một cách rõ ràng và tham chiếu siêu lớp mà bạn được cung cấp thực sự có thể là một lớp con.
Điều khó khăn là để đảm bảo chắc chắn về tính bất biến, bạn phải đánh dấu lớp là final
. Điều này được đề cập sâu trong Java hiệu quả của Joshua Bloch và được tham chiếu rõ ràng trong đặc tả cho mô hình bộ nhớ Java .
Chỉ cần không thêm các phương thức đột biến công khai (setter) vào lớp.
private
hoặc lớp phải là final
. Chỉ để tránh thừa kế. Bởi vì kế thừa vi phạm tính đóng gói.
Các lớp không phải là bất biến, các đối tượng là.
Bất biến có nghĩa là: trạng thái hiển thị công khai của tôi không thể thay đổi sau khi khởi tạo.
Các trường không nhất thiết phải được khai báo cuối cùng, mặc dù nó có thể giúp ích rất nhiều để đảm bảo an toàn cho luồng
Nếu lớp của bạn chỉ có các thành viên tĩnh, thì các đối tượng của lớp này là bất biến, vì bạn không thể thay đổi trạng thái của đối tượng đó (có thể bạn cũng không thể tạo nó :))
Để làm cho một lớp trở nên bất biến trong Java, bạn có thể lưu ý những điểm sau:
1. Không cung cấp các phương thức setter để sửa đổi giá trị của bất kỳ biến cá thể nào của lớp.
2. Khai báo lớp là 'cuối cùng' . Điều này sẽ ngăn không cho bất kỳ lớp nào khác mở rộng nó và do đó không ghi đè bất kỳ phương thức nào từ nó mà có thể sửa đổi các giá trị biến cá thể.
3. Khai báo các biến instance là private và final .
4. Bạn cũng có thể khai báo hàm tạo của lớp là private và thêm một phương thức factory để tạo một thể hiện của lớp khi được yêu cầu.
Những điểm này sẽ giúp !!
private
?
Từ trang oracle , cách tạo các đối tượng bất biến trong Java.
- Không cung cấp các phương thức "setter" - các phương thức sửa đổi các trường hoặc đối tượng được các trường tham chiếu.
- Đặt tất cả các trường cuối cùng và riêng tư.
- Không cho phép các lớp con ghi đè các phương thức. Cách đơn giản nhất để làm điều này là khai báo lớp là cuối cùng. Một cách tiếp cận phức tạp hơn là đặt phương thức khởi tạo là riêng tư và tạo các thể hiện trong các phương thức của nhà máy.
- Nếu các trường cá thể bao gồm các tham chiếu đến các đối tượng có thể thay đổi, không cho phép các đối tượng đó được thay đổi:
I. Không cung cấp các phương thức sửa đổi các đối tượng có thể thay đổi.
II. Không chia sẻ các tham chiếu đến các đối tượng có thể thay đổi. Không bao giờ lưu trữ các tham chiếu đến các đối tượng bên ngoài, có thể thay đổi được chuyển cho hàm tạo; nếu cần, hãy tạo các bản sao và lưu trữ các tham chiếu đến các bản sao. Tương tự, hãy tạo bản sao của các đối tượng có thể thay đổi bên trong của bạn khi cần thiết để tránh trả lại bản gốc trong các phương thức của bạn.
Một đối tượng không thay đổi là một đối tượng sẽ không thay đổi trạng thái bên trong của nó sau khi tạo. Chúng rất hữu ích trong các ứng dụng đa luồng vì chúng có thể được chia sẻ giữa các luồng mà không cần đồng bộ hóa.
1. Không thêm bất kỳ phương thức setter nào
Nếu bạn đang xây dựng một đối tượng bất biến thì trạng thái bên trong của nó sẽ không bao giờ thay đổi. Nhiệm vụ của phương thức setter là thay đổi giá trị bên trong của một trường, vì vậy bạn không thể thêm nó.
2. Khai báo tất cả các trường cuối cùng và riêng tư
Trường riêng tư không hiển thị từ bên ngoài lớp nên không thể áp dụng các thay đổi thủ công cho trường đó.
Khai báo một trường cuối cùng sẽ đảm bảo rằng nếu nó tham chiếu đến một giá trị nguyên thủy thì giá trị sẽ không bao giờ thay đổi nếu nó tham chiếu đến một đối tượng thì không thể thay đổi tham chiếu. Điều này không đủ để đảm bảo rằng một đối tượng chỉ có các trường cuối cùng riêng tư là không thể thay đổi.
3. Nếu một trường là một đối tượng có thể thay đổi, hãy tạo các bản sao phòng thủ của nó cho các phương thức getter
Chúng ta đã thấy trước đây rằng việc xác định trường cuối cùng và riêng tư là không đủ vì có thể thay đổi trạng thái bên trong của nó. Để giải quyết vấn đề này, chúng ta cần tạo một bản sao phòng thủ của trường đó và trả lại trường đó mỗi khi nó được yêu cầu.
4. Nếu một đối tượng có thể thay đổi được chuyển cho hàm tạo phải được gán cho một trường, hãy tạo một bản sao bảo vệ của nó
Vấn đề tương tự cũng xảy ra nếu bạn giữ một tham chiếu được truyền cho hàm tạo vì bạn có thể thay đổi nó. Vì vậy, việc giữ một tham chiếu đến một đối tượng được truyền cho hàm tạo có thể tạo ra các đối tượng có thể thay đổi. Để giải quyết vấn đề này, cần tạo một bản sao bảo vệ của tham số nếu chúng là các đối tượng có thể thay đổi.
Lưu ý rằng nếu một trường là một tham chiếu đến một đối tượng không thể thay đổi thì không cần thiết phải tạo các bản sao bảo vệ của nó trong hàm tạo và trong các phương thức getter, nó đủ để xác định trường là cuối cùng và riêng tư.
5. Không cho phép các lớp con ghi đè các phương thức
Nếu một lớp con ghi đè một phương thức, nó có thể trả về giá trị ban đầu của một trường có thể thay đổi thay vì một bản sao bảo vệ của nó.
Nếu bạn tuân theo những quy tắc đơn giản đó, bạn có thể thoải mái chia sẻ các đối tượng bất biến của mình giữa các luồng vì chúng là luồng an toàn!
Không có đúng hay sai, nó chỉ phụ thuộc vào những gì bạn thích. Nó chỉ phụ thuộc vào sở thích của bạn và những gì bạn muốn đạt được (và có thể dễ dàng sử dụng cả hai cách tiếp cận mà không làm xa lánh những người hâm mộ khó tính của bên này hay bên khác là một chén thánh mà một số ngôn ngữ đang tìm kiếm).
Trước hết, bạn biết lý do tại sao bạn cần tạo đối tượng không thay đổi, và ưu điểm của đối tượng không thay đổi là gì.
Ưu điểm của đối tượng Immutable
Đồng thời và đa luồng Nó tự động An toàn theo luồng nên vấn đề đồng bộ hóa .... vv
Không cần sao chép hàm tạo Không cần thực hiện sao chép. Không thể ghi đè lớp Đặt trường làm trường riêng tư và cuối cùng Buộc người gọi xây dựng một đối tượng hoàn toàn trong một bước duy nhất, thay vì sử dụng một phương thức khởi tạo không đối số
Các đối tượng bất biến chỉ đơn giản là các đối tượng có trạng thái có nghĩa là dữ liệu của đối tượng không thể thay đổi sau khi đối tượng không thay đổi được xây dựng.
xin vui lòng xem mã dưới đây.
public final class ImmutableReminder{
private final Date remindingDate;
public ImmutableReminder (Date remindingDate) {
if(remindingDate.getTime() < System.currentTimeMillis()){
throw new IllegalArgumentException("Can not set reminder" +
" for past time: " + remindingDate);
}
this.remindingDate = new Date(remindingDate.getTime());
}
public Date getRemindingDate() {
return (Date) remindingDate.clone();
}
}
Giảm thiểu khả năng thay đổi
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 có trong mỗi cá thể được cung cấp khi nó được tạo và được cố định trong suốt thời gian tồn tại của đối tượng.
Các lớp bất biến của JDK: Chuỗi, các lớp nguyên thủy đóng hộp (các lớp trình bao bọc), BigInteger và BigDecimal, v.v.
Làm thế nào để biến một lớp trở thành bất biến?
Tạo bản sao phòng thủ. Đả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.
public List getList () {return Collections.unmodifiableList (danh sách); <=== bản sao phòng thủ của trường có thể thay đổi trước khi trả lại trường đó cho người gọi}
Nếu lớp của bạn có bất kỳ trường nào tham chiếu đến các đối tượng có thể thay đổi, hãy đảm bảo rằng các máy khách của lớp không thể lấy tham chiếu đến các đối tượng này. Không bao giờ khởi tạo một trường như vậy thành tham chiếu đối tượng do máy khách cung cấp hoặc trả về tham chiếu đối tượng từ trình truy cập.
import java.util.Date;
public final class ImmutableClass {
public ImmutableClass(int id, String name, Date doj) {
this.id = id;
this.name = name;
this.doj = doj;
}
private final int id;
private final String name;
private final Date doj;
public int getId() {
return id;
}
public String getName() {
return name;
}
/**
* Date class is mutable so we need a little care here.
* We should not return the reference of original instance variable.
* Instead a new Date object, with content copied to it, should be returned.
* */
public Date getDoj() {
return new Date(doj.getTime()); // For mutable fields
}
}
import java.util.Date;
public class TestImmutable {
public static void main(String[] args) {
String name = "raj";
int id = 1;
Date doj = new Date();
ImmutableClass class1 = new ImmutableClass(id, name, doj);
ImmutableClass class2 = new ImmutableClass(id, name, doj);
// every time will get a new reference for same object. Modification in reference will not affect the immutability because it is temporary reference.
Date date = class1.getDoj();
date.setTime(date.getTime()+122435);
System.out.println(class1.getDoj()==class2.getDoj());
}
}
Để biết thêm thông tin, hãy xem blog của tôi:
http://javaexplorer03.blogspot.in/2015/07/minimize-mutability.html
một đối tượng được gọi là bất biến nếu trạng thái của nó không thể thay đổi sau khi được tạo. Một trong những cách đơn giản nhất để tạo lớp bất biến trong Java là đặt tất cả các trường của nó là cuối cùng. Nếu bạn cần viết lớp không thay đổi bao gồm các lớp có thể thay đổi như "java.util.Date". Để duy trì tính bất biến trong những trường hợp như vậy, họ nên trả lại bản sao của đối tượng gốc,
Đối tượng bất biến là những đối tượng có trạng thái không thể thay đổi khi chúng được tạo ra, ví dụ lớp String là một lớp bất biến. Không thể sửa đổi các đối tượng bất biến vì vậy chúng cũng an toàn cho luồng khi thực thi đồng thời.
Tính năng của các lớp bất biến:
Các phím để viết lớp bất biến:
Một số bước sau đây phải được xem xét, khi bạn muốn bất kỳ lớp nào là một lớp bất biến.
Hãy xem qua những gì chúng tôi đã nhập ở trên:
//ImmutableClass
package younus.attari;
public final class ImmutableExample {
private final String name;
private final String address;
public ImmutableExample(String name,String address){
this.name=name;
this.address=address;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
}
//MainClass from where an ImmutableClass will be called
package younus.attari;
public class MainClass {
public static void main(String[] args) {
ImmutableExample example=new ImmutableExample("Muhammed", "Hyderabad");
System.out.println(example.getName());
}
}
Thêm giao cho câu trả lời cung cấp bởi @ nsfyn55, các khía cạnh sau cũng cần phải được xem xét tính bất biến đối tượng, đó là các số nguyên tố quan trọng
Hãy xem xét các lớp sau:
public final class ImmutableClass {
private final MutableClass mc;
public ImmutableClass(MutableClass mc) {
this.mc = mc;
}
public MutableClass getMutClass() {
return this.mc;
}
}
public class MutableClass {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
public class MutabilityCheck {
public static void main(String[] args) {
MutableClass mc = new MutableClass();
mc.setName("Foo");
ImmutableClass iMC = new ImmutableClass(mc);
System.out.println(iMC.getMutClass().getName());
mc.setName("Bar");
System.out.println(iMC.getMutClass().getName());
}
}
Sau đây sẽ là đầu ra từ MutabilityCheck:
Foo
Bar
Điều quan trọng cần lưu ý là,
Xây dựng các đối tượng có thể thay đổi trên một đối tượng không thể thay đổi (thông qua hàm tạo), bằng cách 'sao chép' hoặc 'nhân bản' vào các biến thể hiện của bất biến được mô tả bằng các thay đổi sau:
public final class ImmutableClass {
private final MutableClass mc;
public ImmutableClass(MutableClass mc) {
this.mc = new MutableClass(mc);
}
public MutableClass getMutClass() {
return this.mc;
}
}
public class MutableClass {
private String name;
public MutableClass() {
}
//copy constructor
public MutableClass(MutableClass mc) {
this.name = mc.getName();
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
vẫn không đảm bảo tính bất biến hoàn toàn vì điều sau vẫn còn hiệu lực từ Kiểm tra tính ổn định của lớp:
iMC.getMutClass().setName("Blaa");
Tuy nhiên, chạy MutabilityCheck với các thay đổi được thực hiện trong 1. sẽ dẫn đến kết quả là:
Foo
Foo
Để đạt được tính bất biến hoàn toàn trên một đối tượng, tất cả các đối tượng phụ thuộc của nó cũng phải là 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 và hối hả để tạo lớp Immutable.
Lớp bản ghi là một sóng mang trong suốt, không thay đổi nông cho 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ỳ hàm tạo nào, không cần triển khai equals
& hashCode
phương thức. 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");
}
}