如何写一个不可变类?

不可变的对象指的是一旦创建之后,它的状态就不能改变。String类就是个不可变类,它的对象一旦创建之后,值就不能被改变了。

阅读更多: 为什么String类是不可变的

不可变对象对于缓存是非常好的选择,因为你不需要担心它的值会被更改。不可变类的另外一个好处是它自身是线程安全的,你不需要考虑多线程环境下的线程安全问题。

阅读更多: Java线程教程以及Java多线程面试问题

下面是创建不可变类的方法,我也给出了代码,加深理解。

要创建不可变类,要实现下面几个步骤:

  1. 将类声明为final,所以它不能被继承
  2. 将所有的成员声明为私有的,这样就不允许直接访问这些成员
  3. 对变量不要提供setter方法
  4. 将所有可变的成员声明为final,这样只能对它们赋值一次
  5. 通过构造器初始化所有成员,进行深拷贝(deep copy)
  6. 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝

为了理解第5和第6条,我将使用FinalClassExample来阐明。

FinalClassExample.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package com.journaldev.java;
 
import java.util.HashMap;
import java.util.Iterator;
 
public final class FinalClassExample {
 
     private final int id;
 
     private final String name;
 
     private final HashMap testMap;
 
     public int getId() {
         return id;
     }
 
     public String getName() {
         return name;
     }
 
     /**
      * 可变对象的访问方法
      */
     public HashMap getTestMap() {
         //return testMap;
         return (HashMap) testMap.clone();
     }
 
     /**
      * 实现深拷贝(deep copy)的构造器
      * @param i
      * @param n
      * @param hm
      */
 
     public FinalClassExample( int i, String n, HashMap hm){
         System.out.println( "Performing Deep Copy for Object initialization" );
         this .id=i;
         this .name=n;
         HashMap tempMap= new HashMap();
         String key;
         Iterator it = hm.keySet().iterator();
         while (it.hasNext()){
             key=it.next();
             tempMap.put(key, hm.get(key));
         }
         this .testMap=tempMap;
     }
 
     /**
      * 实现浅拷贝(shallow copy)的构造器
      * @param i
      * @param n
      * @param hm
      */
     /**
     public FinalClassExample(int i, String n, HashMap hm){
         System.out.println("Performing Shallow Copy for Object initialization");
         this.id=i;
         this.name=n;
         this.testMap=hm;
     }
     */
 
     /**
      * 测试浅拷贝的结果
      * 为了创建不可变类,要使用深拷贝
      * @param args
      */
     public static void main(String[] args) {
         HashMap h1 = new HashMap();
         h1.put( "1" , "first" );
         h1.put( "2" , "second" );
 
         String s = "original" ;
 
         int i= 10 ;
 
         FinalClassExample ce = new FinalClassExample(i,s,h1);
 
         //Lets see whether its copy by field or reference
         System.out.println(s==ce.getName());
         System.out.println(h1 == ce.getTestMap());
         //print the ce values
         System.out.println( "ce id:" +ce.getId());
         System.out.println( "ce name:" +ce.getName());
         System.out.println( "ce testMap:" +ce.getTestMap());
         //change the local variable values
         i= 20 ;
         s= "modified" ;
         h1.put( "3" , "third" );
         //print the values again
         System.out.println( "ce id after local variable change:" +ce.getId());
         System.out.println( "ce name after local variable change:" +ce.getName());
         System.out.println( "ce testMap after local variable change:" +ce.getTestMap());
 
         HashMap hmTest = ce.getTestMap();
         hmTest.put( "4" , "new" );
 
         System.out.println( "ce testMap after changing variable from accessor methods:" +ce.getTestMap());
 
     }
 
}

输出:

1
2
3
4
5
6
7
8
9
10
Performing Deep Copy for Object initialization
true
false
ce id:10
ce name:original
ce testMap:{2=second, 1=first}
ce id after local variable change:10
ce name after local variable change:original
ce testMap after local variable change:{2=second, 1=first}
ce testMap after changing variable from accessor methods:{2=second, 1=first}

现在我们注释掉深拷贝的构造器,取消对浅拷贝构造器的注释。也对getTestMap()方法中的返回语句取消注释,返回实际的对象引用。然后再一次执行代码。

1
2
3
4
5
6
7
8
9
10
Performing Shallow Copy for Object initialization
true
true
ce id:10
ce name:original
ce testMap:{2=second, 1=first}
ce id after local variable change:10
ce name after local variable change:original
ce testMap after local variable change:{3=third, 2=second, 1=first}
ce testMap after changing variable from accessor methods:{3=third, 2=second, 1=first, 4=new}

从输出可以看出,HashMap的值被更改了,因为构造器实现的是浅拷贝,而且在getter方法中返回的是原本对象的引用。

如果我漏掉了某些细节,请指出。

更多阅读: 如果不可变类有许多成员,而其中一些是可选的,那么我们可以使用建造者模式(builder pattern)来创建不可变类

 

当构造器中的参数很多时,并且参数的顺序会给人造成困扰的时候,那么使用建造者模式来创建不可变类就是非常好的方法了。

使用建造者模式来创建不可变类

下面是使用建造者模式来创建不可变类的例子:

ImmutableClass.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.journaldev.design.builder;
 
import java.util.HashMap;
 
public class ImmutableClass {
 
     //required fields
     private int id;
     private String name;
 
     //optional fields
     private HashMap properties;
     private String company;
 
     public int getId() {
         return id;
     }
 
     public String getName() {
         return name;
     }
 
     public HashMap getProperties() {
         //return cloned object to avoid changing it by the client application
         return (HashMap) properties.clone();
     }
 
     public String getCompany() {
         return company;
     }
 
     private ImmutableClass(ImmutableClassBuilder builder) {
         this .id = builder.id;
         this .name = builder.name;
         this .properties = builder.properties;
         this .company = builder.company;
     }
 
         //Builder class
     public static class ImmutableClassBuilder{
         //required fields
         private int id;
         private String name;
 
         //optional fields
         private HashMap properties;
         private String company;
 
         public ImmutableClassBuilder( int i, String nm){
             this .id=i;
             this .name=nm;
         }
 
         public ImmutableClassBuilder setProperties(HashMap hm){
             this .properties = (HashMap) hm.clone();
             return this ;
         }
 
         public ImmutableClassBuilder setCompany(String comp){
             this .company = comp;
             return this ;
         }
 
         public ImmutableClass build(){
             return new ImmutableClass( this );
         }
     }
}

下面的测试代码为我们测试到底创建的对象是不是不可变的。

ImmutableBuilderTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.journaldev.design.test;
 
import java.util.HashMap;
 
import com.journaldev.design.builder.ImmutableClass;
 
public class ImmutableBuilderTest {
 
     public static void main(String[] args) {
 
         // Getting immutable class only with required parameters
         ImmutableClass immutableClass = new ImmutableClass.ImmutableClassBuilder( 1 , "Pankaj" ).build();
 
         HashMap hm = new HashMap();
         hm.put( "Name" , "Pankaj" );
         hm.put( "City" , "San Jose" );
         // Getting immutable class with optional parameters
         ImmutableClass immutableClass1 = new ImmutableClass.ImmutableClassBuilder( 1 , "Pankaj" )
                                                      .setCompany( "Apple" ).setProperties(hm).build();
 
         //Testing immutability
         HashMap hm1 = immutableClass1.getProperties();
 
         //lets modify the Object passed as argument or get from the Object
         hm1.put( "test" , "test" );
 
         //check that immutable class properties are not changed
         System.out.println(immutableClass1.getProperties());
     }
 
}

重要的知识点

  • 不可变类只有getter方法
  • 不可变类只有一个私有的构造器,以Builder对象作为参数来创建不可变对象
  • 如果不可变类的成员变量是可变的(譬如HashMap),我们需要使用深拷贝(deep copy)或者克隆来防止成员变量被更改
  • 当可选的成员变量很多的时候,使用建造者模式创建不可变类是不错的方法
原文链接: Journaldev 翻译: ImportNew.com - 唐小娟
译文链接: http://www.importnew.com/7860.html
[ 转载请保留原文出处、译者和译文链接。]


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值