不可变类
不可变类的意思是创建该类实例后,该实例的
实例变量是不可改变的
。- Java中的
8个包装类
和java.lang.String类
都是不可变类,当创建它们的实例后,其实例变量
不可改变。
- Java中的
如果需要创建自定义的不可变类,遵循以下规则:
使用
private
和final
修饰符来修饰该类的成员变量
提供
带参数构造器
,用于根据传入参数来初始化类中的成员变量
仅为该类的成员变量
提供getter方法
,不要
为该类的成员变量提供setter方法,因为普通方法无法修改final修饰的成员变量如果有必要,重写hashCode()和equals()。
equals()方法根据关键成员变量来作为两个对象是否相等的标准
- 应保证
两个用equals()方法判断为相等的对象的hashCode()也相等
例如String类对象里的
字符序列作为相等的标准
,其hashCode()
方法也是根据字符序列
计算得到的。String类中的
equals()
方法源码public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
String类中的hashCode()方法的源码:
public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
可变类
的含义是该类的实例变量
是可变的
。大部分创建的类都是可变类,特别是JavaBean,因为总是为其实例变量提供了setter和getter方法。与可变类相比,
不可变类的实例在整个生命周期中永远处于初始化状态
,它的实例变量不可改变。因此对不可变类的实例的控制将更加简单。如果需要设计一个不可变类,尤其要注意其
引用类型的成员变量
,如果引用类型的成员变量的类是可变的
,就必须采取必要的措施来保护该成员变量所引用的对象不会被修改
,这样才能创建真正的不可变类。
缓存不可变类
不可变类的实例状态不可改变,可以很
方便的被多个对象共享
。如果程序经常使用相同的不可变实例,就应该考虑缓存
这种不可变类的实例
。毕竟重复创建相同的对象没有意义,而且会加大系统开销
。用
数组创建缓存池
,用于缓存实例:class CacheImmutale { private static int MAX_SIZE = 10; // 使用数组来缓存已有的实例 private static CacheImmutale[] cache = new CacheImmutale[MAX_SIZE]; // 记录缓存实例在缓存中的位置,cache[pos-1]是最新缓存的实例 private static int pos = 0; private final String name; private CacheImmutale(String name) { this.name = name; } public String getName() { return name; } public static CacheImmutale valueOf(String name) { // 遍历已缓存的对象, for (int i = 0 ; i < MAX_SIZE; i++) { // 如果已有相同实例,直接返回该缓存的实例 if (cache[i] != null && cache[i].getName().equals(name)) { return cache[i]; } } // 如果缓存池已满 if (pos == MAX_SIZE) { //先进先出 // 把缓存的第一个对象覆盖,即把刚刚生成的对象放在缓存池的最开始位置。 cache[0] = new CacheImmutale(name); // 把pos设为1 pos = 1; } else { // 把新创建的对象缓存起来,pos加1 cache[pos++] = new CacheImmutale(name); } return cache[pos - 1]; } public boolean equals(Object obj) { if(this == obj) { return true; } if (obj != null && obj.getClass() == CacheImmutale.class) { CacheImmutale ci = (CacheImmutale)obj; return name.equals(ci.getName()); } return false; } public int hashCode() { return name.hashCode(); } } public class CacheImmutaleTest { public static void main(String[] args) { CacheImmutale c1 = CacheImmutale.valueOf("hello"); CacheImmutale c2 = CacheImmutale.valueOf("hello"); // 下面代码将输出true System.out.println(c1 == c2); } }
是否需要隐藏缓存池类的构造器完全取决于系统需求。盲目乱用缓存也可能导致系统性能下降,缓存的对象会占用系统内存,如果某个对象只使用一次,重复使用的概率不大,缓存该实例就弊大于利;反之,如果某个对象需要频繁地重复使用,缓存该实例就利大于弊。
Java中
Integer
类就采取了上述CacheImmutale类相同的处理策略,如果采用new构造器
来创建Integetr对象,则每次返回全新的Integer对象
;如果采用valueOf()方法来创建Integer对象
,则会缓存该方法创建的对象
。由于new构造器方式创建Integer对象不会启用缓存,因此性能较差,所以
Java9
中已经将该构造器标记为过时,全面采用valueOf()方法创建。public class IntegerCacheTest { public static void main(String[] args) { // 生成新的Integer对象 Integer in1 = new Integer(6); // 生成新的Integer对象,并缓存该对象 Integer in2 = Integer.valueOf(6); // 直接从缓存中取出Ineger对象 Integer in3 = Integer.valueOf(6); System.out.println(in1 == in2); // 输出false System.out.println(in2 == in3); // 输出true // 由于Integer只缓存-128~127之间的值, // 因此200对应的Integer对象没有被缓存。 Integer in4 = Integer.valueOf(200); Integer in5 = Integer.valueOf(200); System.out.println(in4 == in5); //输出false } }
- 由于Integer只缓存-128~127之间的Integer对象,因此两次通过
Integer.valueOf(200)
方法生成的Integer对象不是同一个。
- 由于Integer只缓存-128~127之间的Integer对象,因此两次通过