概念:不可变类的意思是创建该类的实例后,该实例的属性是不可改变的。java中的8个包装类和String类都是不可变类。所以不可变类并不是指该类是被final修饰的,而是指该类的属性是被final修饰的。
自定义不可变类遵守如下原则:
1、使用private和final修饰符来修饰该类的属性。
2、提供带参数的构造器,用于根据传入的参数来初始化属性。
3、仅为该类属性提供getter方法,不要提供setter方法。
4、如果有必要,重写hashCode和equals方法,同时应保证两个用equals方法判断为相等的对象,其hashCode也应相等。
构造一个不可变类非常容易,下面举一个简单例子:
package com.home; public class Address { private final String detail; public Address() { this.detail = ""; } public Address(String detail) { this.detail = detail; } public String getDetail() { return detail; } @Override public int hashCode() { return detail.hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof Address) { Address address = (Address) obj; if (this.getDetail().equals(address.getDetail())) { return true; } } return false; } }
但是值得注意的是,该类的属性虽然是被final修饰的,但若属性是非String的其他引用类型的话,那么虽然该属性的内容(所指对象的地址)不会改变,但其指向的对象却有可能会改变,这样的类当然并不能成为不可变类。比如下面的Person类中有一个Name类型的属性:
package com.home; public class Person { private final Name name; public Person(Name name) { super(); this.name = name; } public Name getName() { return name; } public static void main(String[] args) { Name n = new Name("三", "张"); Person p = new Person(n); System.out.println(p.getName().getFirstName()); // 改变Person对象Name属性的firstName属性值 n.setFirstName("无忌"); System.out.println(p.getName().getFirstName()); } }
Name:
package com.home; public class Name { private String firstName; private String lastName; public Name() { super(); } public Name(String firstName, String lastName) { super(); this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } }
运行上面程序可以看到,Person对象的Name属性的firstName属性已经被改变,这就违背了不可变类设计的初衷。我们可以采取如下办法来解决,修改Person类如下:
package com.home; public class Person { private final Name name; public Person(Name name) { super(); // 设置name属性为临时创建的Name对象,该对象的firstName属性和lastName属性 // 与传入的name对象的firstName属性和lastName属性相同 this.name = new Name(name.getFirstName(), name.getLastName()); } public Name getName() { // 返回一个匿名对象,该对象的firstName属性和lastName属性 // 与该对象里的name属性的firstName属性和lastName属性相同 return new Name(name.getFirstName(), name.getLastName()); } public static void main(String[] args) { Name n = new Name("三", "张"); Person p = new Person(n); System.out.println(p.getName().getFirstName()); // 改变Person对象Name属性的firstName属性值 n.setFirstName("无忌"); System.out.println(p.getName().getFirstName()); } }
再次运行程序,发现Person对象的Name属性的firstName属性没有改变了。
另外,由于不可变类的实例的状态不可改变,所以可以很方便地被多个对象所共享,那么如果程序要经常使用相同的不可变类实例,为了减少系统开销,一般要考虑使用缓存机制。下面使用数组作为缓存池来构建一个可以缓存实例的不可变类:
package com.home; public class CacheImmutale { private final String name; private static CacheImmutale[] cache = new CacheImmutale[10]; private static int pos = 0; public CacheImmutale(String name) { super(); this.name = name; } public String getName() { return name; } public static CacheImmutale valueOf(String name) { // 遍历已缓存的对象 for (int i = 0; i < pos; i++) { // 如果已有相同实例,直接返回该缓存的实例 if (cache[i] != null && cache[i].getName().equals(name)) { return cache[i]; } } // 如果缓冲池已满 if (pos == 10) { // 把缓存的第一个对象覆盖 cache[0] = new CacheImmutale(name); pos = 1; return cache[0]; } else { // 把新创建的对象缓存起来,pos加1 cache[pos++] = new CacheImmutale(name); return cache[pos - 1]; } } @Override public int hashCode() { return name.hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof CacheImmutale) { CacheImmutale ci = (CacheImmutale) obj; if (name.equals(ci.getName())) { return true; } } return false; } public static void main(String[] args) { CacheImmutale c1 = CacheImmutale.valueOf("hello"); CacheImmutale c2 = CacheImmutale.valueOf("hello"); System.out.println(c1 == c2);// 输出结果为true } }
对于缓存的使用,应根据系统需求而定,简单的说,如果某个对象使用的次数不多,重复使用的概率不大,就没必要使用缓存,毕竟缓存的对象也会占用系统内存。如果某个对象需要频换地重复使用,这时就应该使用缓存了。