Java中如何创建不可变(immutable)类

什么是不可变类

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关键字修饰的变量,考虑如下代码:

1final 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,而不是直接返回该对象(或者叫该对象的引用/指针)

实例:

package org.adeline.learning;

import java.util.Arrays;

public final class Immutable {

private final int vInt;

private final String vStr;

private final int[] vArr;

public Immutable(int vInt, String vStr, int[] vArr) {

this.vInt = vInt;

this.vStr = vStr;

//this.vArr = vArr; //不正确

this.vArr = vArr.clone();//使用传入数组的copy初始化

}

public int[] getVArr() {

//return vArr; //不正确

return vArr.clone(); //返回数组的copy

}

public static void main(String[] args) {

int[] arr = new int[]{3,4};

Immutable im = new Immutable(1,"2", arr);

int[] arr1 = im.getVArr();

Arrays.stream(arr1).forEach((e) -> {System.out.println(e);});

arr[0] = 33;

arr[1] = 44;

Arrays.stream(arr1).forEach((e) -> {System.out.println(e);});

}

}

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

public class ImmutableChild extends Immutable{

private int[] vArr;

public ImmutableChild(int vInt, String vStr, int[] vArr) {

super(vInt, vStr, vArr);

this.vArr = vArr;

}

@Override

public int[] getVArr() { //父类中的方法被重写,返回的是子类中的vArr

return vArr;

}

public static void main(String[] args) {

Immutable imNG = new ImmutableChild(1,"2", new int[]{3,4});

imNG.getVArr()[0] = 33;

imNG.getVArr()[1] = 44;

Arrays.stream(imNG.getVArr()).forEach(e -> {System.out.println(e);});//output 33,44

}

}

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

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

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

不可变类的优点与用途

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

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值