一.可变数据类型和不可变数据类型
1.首先看一下二者的概念:
不可变数据类型: 当该数据类型的对应变量的值发生了改变,那么它对应的内存地址也会发生改变,对于这种数据类型,就称为不可变数据类型。
可变数据类型 :当该数据类型的对应变量的值发生了改变,那么它对应的内存地址不发生改变,对于这种数据类型,就称为可变数据类型,当可变数据类型改变时它实际上是更改了内存中的内容。
2.在一般情况下,java中基本数据类型(包括int,char,long等等)以及String类型均为不可变类型,而BigInteger,BigDecimal,StringBuilder,List,Set,Map这些为可变类型。
可以举个简单的例子来看一下两种数据类型的区别:
String s = " Hello ";
s += " World ";
执行第一条语句后,s只指向hello所在的内存地址。
而执行完第二条语句后,内存地址发生变化,内容也跟着变。而原地址的内容不变,且无法被访问到。
StringBuilder sb=new StringBuilder(“a”);
sb.append(“b”);
而对于上面两条语句,首先会建立一个内存地址,存储字符a;在append操作后,只会将内容变为ab,内存地址不变。
3.两种类型变量各自的优点
(1)可变数据类型:
由于对不可变数据类型进行修改会产生大量的临时拷贝,很占用空间,相比之下可变数据类型可以将拷贝最少化从而提高效率。
一个程序较多的的使用可变数据类型,会获得更好的性能。
这种类型也适合于在多个模块之间共享数据。
(2)不可变数据类型:
不可变类型更安全,用户或其他人访问时,对类中的元素不会进行修改,从而保证程序的正确性,在项目中可以更好地保存。
存在对一个对象的多次引用时,采用不可变类型是明智的,否则对该对象修改时,同时会改变其他几个类的引用,会出现大错。
3.对于这两种类型,我们要根据实际情况选择合适的类型折中处理,不能一概而论。
4.对不可变数据类型的引用,可以通过在声明变量时用final进行修饰,此时变量不可改变其指向的地址。如果修饰的变量是可变数据类型的,其里面的值是可以改变的;若是不可变数据类型,则定义后我们只能读取内容,无法改变内容,否则编译器报错。
5.对于List,Map,Set三种类型,可以通过封装函数让里面的数据只能看,不能被修改,否则报错。利用以下三个方法可对三类分别进行封装
Collections.unmodifiableList() Collections.unmodifiableSet()
Collections.unmodifiableMap()
二.可变类和不可变类
1.同样来看一下二者的概念:
不可变类:是指这个类的实例一旦创建完成后,就不能改变其成员变量值。如JDK内部自带的很多不可变类:Interger、Long和String等。
可变类:相对于不可变类,可变类创建实例后可以改变其成员变量值,开发中创建的大部分类都属于可变类。
2.可变类和不可变类各自的优点:
(1)可变类:在进行参数的传递和修改时会大大地节省空间,而且性能较高。
(2)不可变类:
不可变对象是线程安全的,在线程之间可以相互共享,因为对象的值无法改变,不需要利用特殊机制来保证同步问题。同样可以降低并发错误的可能性,因为不需要用一些锁机制等保证内存一致性问题,这也减少了同步开销。
易于构造、使用和测试,减少了程序出错的概率。
不可变对象可以被重复使用,可以将它们缓存起来重复使用,也可以使用静态工厂方法来提供类似于valueOf()这样的方法,它可以从缓存中返回一个已经存在的Immutable对象,而不是重新创建一个。
3.对于不可变类有一些它自身的特点(设计时遵循的准则),大概有如下几点:
(1)所有成员变量必须是private,且最好同时用final修饰,保证其安全性。
(2)不能提供能够修改原有对象状态的方法
最常见的是不提供setter方法,如果提供修改方法,需要新创建一个对象,并在新创建的对象上进行修改
(3)通过构造器初始化所有成员变量,引用类型的成员变量必须进行深拷贝(deep copy)。为了方便理解,我举一个例子:
如果构造器传入的对象直接赋值给成员变量,还是可以通过对传入对象的修改进而导致改变内部变量的值。
public final class ImmutableDemo
{
private final int[] myArray;
public ImmutableDemo(int[] array)
{
this.myArray = array; // wrong
}
}
这种方式不能保证不可变性,myArray和array指向同一块内存地址,用户可以在ImmutableDemo之外通过修改array对象的值来改变myArray内部的值。
为了保证内部的值不被修改,可以采用深度copy来创建一个新内存保存传入的值。正确做法:
public final class MyImmutableDemo
{
private final int[] myArray;
public MyImmutableDemo(int[] array)
{
this.myArray = array.clone();
}
}
(4)在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝,且不能对外泄露this引用以及成员变量的引用。
(5)类添加final修饰符,保证类不被继承。
如果类可以被继承会破坏类的不可变性机制,只要继承类覆盖父类的方法并且继承类可以改变成员变量值,那么一旦子类以父类的形式出现时,不能保证当前类是否可变。



