什么是不可变类

1. 不可变类是指类的实例一经创建完成,这个实例的内容就不会改变。

2. Java中的String和八个基本类型的包装类(Integer, Short, Byte, Long, Double, Float,Boolean,Char)都是不可变类

3.不可变类 vs 不可变变量:

二者是不一样的。

不可变类是指类的实例内容不会改变,考虑如下代码:

1 String s = "ABC";2 s = "BCD"3 System.out.println("s:"+s);4 //output s:BCD

在line 2中我们对s变量进行了再次赋值,实际上是又创建了一个值为”BCD”的String 对象,并将s指向它。变化的是s指向的内存地址(或者简单的叫指针),值为”ABC” 与值为”BCD”的两个String 对象是没有变的

不可变变量是用Final关键字修饰的变量,考虑如下代码:

1 final String s = "ABC";2 s = "BCD" //此行报错,不能给final变量赋值3 System.out.println("s:"+s);

我们将s变量用final关键字修饰,这时在s被初始化之后,就无法在line 2 再次给它赋值了,也就是说我们没办法改变final变量指向的内存地址

如何实现一个不可变类

1. 所有的成员变量声明为private final,防止初始化后被修改

2. 类声明为final,禁止继承,其实是防止类中的方法被重写

3. 不为成员变量提供setter方法

4. 如果类中包含可变对象,比如一个成员变量是数组,或者其他可变类,那么要有如下操作:

1)在构造方法中,如果构造方法会传入可变对象,我们要使用这个对象的copy来初始化我们的成员变量,而不是直接使用传入的对象。因为传入的是指针,传入的对象在外面可能会被修改,如果直接引用的话会导致我们的成员变量也间接被修改。

2) 在返回这些可变对象的getter方法中,返回对象的copy,而不是直接返回该对象(或者叫该对象的引用/指针)

实例:

 1 package org.adeline.learning; 2  3 import java.util.Arrays; 4  5 public final class Immutable { 6     private final int vInt; 7     private final String vStr; 8     private final int[] vArr; 9 10     public Immutable(int vInt, String vStr, int[] vArr) {11         this.vInt = vInt;12         this.vStr = vStr;13         //this.vArr = vArr; //不正确14         this.vArr = vArr.clone();//使用传入数组的copy初始化15     }16 17     public int[] getVArr() {18         //return vArr; //不正确19         return vArr.clone(); //返回数组的copy20     }21 22     public static void main(String[] args) {23         int[] arr = new int[]{3,4};24         Immutable im = new Immutable(1,"2", arr);25         int[] arr1 = im.getVArr();26         Arrays.stream(arr1).forEach((e) -> {System.out.println(e);});27         arr[0] = 33;28         arr[1] = 44;29         Arrays.stream(arr1).forEach((e) -> {System.out.println(e);});30     }31 32 }

下面探讨一下为何类也需要声明为final. 考虑如下代码:

 1 public class ImmutableChild extends Immutable{ 2     private int[] vArr; 3     public ImmutableChild(int vInt, String vStr, int[] vArr) { 4         super(vInt, vStr, vArr); 5         this.vArr = vArr; 6     } 7     @Override 8     public int[] getVArr() { //父类中的方法被重写,返回的是子类中的vArr 9         return vArr;10     }11 12     public static void main(String[] args) {13         Immutable imNG = new ImmutableChild(1,"2", new int[]{3,4});14         imNG.getVArr()[0] = 33;15         imNG.getVArr()[1] = 44;16         Arrays.stream(imNG.getVArr()).forEach(e -> {System.out.println(e);});//output 33,4417     }18 }

我们把上面Immutable 类的final 声明去掉,ImmutableChild继承了Immutable类,重写了getVArr方法,返回自己的成员变量数组vArr,而这个子类里面的vArr是可变的,在main方法里面初始化时我们给其赋值{3,4},可以看到后面我们改成了{33,44}.

在使用中,任何一个接受Immutable实例的地方都可以接受其子类ImmutableChild实例,并将它作为一个不可变的实例来操作,而实际上它是可变的,这样就有可能出错。

所以把不可变类声明为final是为了防止恶意继承,或者继承中考虑不周密导致的问题。

不可变类的优点与用途

1. 线程安全,省去了加锁的过程,因为对象内容不可变就不用担心线程同时对对象做修改

2. 拷贝效率高。当类不可变时, 拷贝其对象只需拷贝指针即可,而不用拷贝对象本身,不用担心会被修改

3. 可以作为HashMap的key,类不可变保证了Hashcode的稳定性。

当然,也要注意不可变类在使用过程中可能出现的内存浪费问题,比如大家都知道的最好不要用许多”+”连接String