标签:
0.final修饰的类就是不可变类吗?
答:不是.final修饰的类叫不可继承类.两者并无关系.也就是说不可变类的类名可以用final修饰也可以不用.
1.不可变类的特点是什么?即什么是不可变类?
答:特点是一旦创建了类的实例,实例内容(状态)不可被修改.典型类就是java中的String.
2.既然String是不可变类,为何可以对它重新赋值而改变了其内容?
答:String确实被设计成了不可变类,但重新赋值并不是对String a对象的改变;可以看到第7句本来想将BOOK改为小写,但最后未成功.java中的引用类型都是引用变量指向对象这种结构来管理的.重新赋值只是将对象的引用重新改变了位置(由上面的hashCode可知).
3.String的new和" "和+三种用法的区别;
答:new会在堆中开辟内容," "会缓存在字符串常量池. +会隐式调用toString方法. 拼接相同字符串并不会重新开辟内存
只要记得比较字符串用equals就行了.但是深入理解这个问题将对我们优化程序大有裨益.注意String重写了equals方法,该equals比较对象内容相同就返回true.
== 更严格些,它要求对象引用不变.
右上图可知(1) " "的内容相同指同一个对象,这是由于java的String pool的缓存作用.jvm会将" "内的内容作为常量存在pool内,除非显示new一个,否则找pool中有没有,有就指向,如上例的 a == b;没有就在pool里再创建一个.
(2)new出来的String,就算内容相同也是不同的对象. 由c == d可知.
(3)new出来的对象和" "就算内容相同,也不是同一个对象.//很明显这句话可能有误区,""的内容有的书上称为常量池,常量池是编译时就确定了的,常量池里的内容不光是字符串类型,
也就是 说 常量池里并未保存的是对象而是常量. 本质上对象和常量在内存中都是一样的,
(4)由a == e , c== e可知只要new了就不同了.字符串拼接也是先从pool中找.
4.final修饰基本类型和引用类型的区别?
答:final修饰基本类型变量不可改变;修饰引用类型,由于其只是保存了一个引用,final只能保证这个引用的地址不会改变,即一直引用同一个对象,对象内容改变与否无关,
注意final修饰引用类型变量和不变类的区别.
一个是引用不变,一个是引用的对象不变.
5.那么如何实现不可变类?
答:参考String.
使用private final 修饰该类的field//注意该field如果是基本类型还好,如果是引用类型要保证其不可变,否则将可能导致创建不变类失败.
//filed要为基本类型或不可变类的引用类型
提供带参构造器,用于根据传入的参数来初始化该field
仅仅为该类提供getter方法,不提供setter方法
如有必要重写hashCode和equals方法
该程序来自<<疯狂java>>
class Name { private String firstName; private String lastName; public Name() {} public Name(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getFirstName() { return this.firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getLastName() { return this.lastName; } } public class Person { private final Name name; public Person(Name name) { this.name = name;//(1) this.name = new Name(name.getFirstName(), name.getLastName()); } public Name getName() { return name; //(2) return new Name(name.getFirstName(), name.getLastName());
} public static void main(String[] args) { Name n = new Name("wukong", "sun"); Person p = new Person(n); System.out.println(p.getName().getFirstName());//wukong n.setFirstName("bajie"); System.out.println(p.getName().getFirstName()); //bajie } }
可以看到Person类的filed是引用类型的,且在Name类里可以改变其状态.所以Person类并未形成一个不可变类.
书上提供了一种方法将Person类变为不可变类
即把(1)(2)这两句都用匿名对象来处理.
原版本经过Name类来改变Person类里的field,然后从Personl类的构造器返回为改变后的对象. 因此该filed指向的对象容易受到Name类的影响而改变
而改为匿名对象后,从Person类的构造器返回就是临时创建的Name对象,无论你是改变firstName还是lastName,他都只是改变了n指向的firstName.
读者可以在n.setFirstName前一行和后一行分别加上
System.out.println(n.getFirstName());
会发现前后是不一样的.
这就说明n指向的Name对象确实被改变了.
而p的name没改变.
注意:n的filed是两个String类型的;p的filed是一个Name类型的.//类也是一种对象
下面定义一个不可变类Address----来自疯狂java
/** private final 基本数据类型 或 不可变类的引用类型 提供带参构造器,用于根据传入的参数来初始化该field 仅仅为该类提供getter方法,不提供setter方法 如有必要重写hashCode和equals方法 */ public class Address { private final String detail; private final String postCode; public Address() { //如果类编写了有参构造器通常建议提供一个无参构造器 this.detail = ""; this.postCode = ""; } public Address(String detail, String postCode) { this.detail = detail; this.postCode = postCode; } public String getPostCode() { return this.postCode; } public String getDetail() { return this.detail; } public boolean equals(Object obj) { if(this == obj) { return true; } if (obj != null && obj.getClass() == Address.class) { Address ad = (Address)obj; if (this.getDetail().equals(ad.getDetail()) && this.getPostCode().equals(ad.getPostCode())) { return true; } } return false; } public int hashCode() { return detail.hashCode() + postCode.hashCode() * 31; } }
由于没提供方法setter方法,所以无法修改Address类的field;
那么难道只要不提供setter方法就可以构成不可变类吗?
不,private final可以保证不可变类创建的实例的field不被改变不被外界访问到.
6.不可变类的实例状态不会变化,这样的实例可以安全地被其他与之关联的对象共享,还可以安全地被多个线程共享,为了节约内存空间,应该尽可能重用不可变类的实例,避免重复创建具有相同属性值的实例.那么该如何实现缓存技术呢?缓存技术有很多种,下面用数组作为缓存池,来实现一个缓存实例的不可变类.
/** * 此程序用数组模拟缓存池 * CachePerson类能控制生成的CachePerson对象个数,只能通过valueof()获取该实例 * 如果某个对象只使用一次,那么没必要使用缓存; * 如果某个对象频繁使用,缓存就很必要. * java.lang.Integer采用了相同的策略,如果采用new对象,则每次都是全新的;如果采用valueof则缓存 */ class CachePerson { private static int MAX_SIZE = 10; //使用数组缓存 private static CachePerson[] cache = new CachePerson[MAX_SIZE]; //记录缓存实例在缓存中的位置:cache[pos-1]是新缓存的实例 private static int pos = 0; private final String name; //private构造器,外类无法构造对象 private CachePerson(String name) { this.name = name; } public String getName() { return name; } //一旦封装了构造器,一定有public方法来创建对象 //static是必要的,因创建对象只能是类方法 public static CachePerson 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 CachePerson(name); pos = 1; } else { //新创建的实例缓存起来,pos+1 cache[pos++] = new CachePerson(name); } return cache[pos - 1]; } /*此处equals方法很常用最好记下来:判断两个对象是否相等 == 对于基本数据类型只要值相等就行;对于引用变量,只有指向同一个对象时才true; == 不可比较没有继承关系的对象 默认的String类的equals方法只要对象的字符序列相等即认为相等; String类的equals方法是重写了Object类的equals方法,该方法与==区别不大; 一般情况下,我们要求像String类那样只要值相同则认为其相同 @see java.lang.String#964 */ public boolean equals(Object obj) { //判断两个对象是否是同一个对象 if (this == obj) { return true; } //当obj不为null且是CachePerson的实例时 if (obj != null && obj.getClass() == CachePerson.class) { CachePerson ci = (CachePerson)obj; return name.equals(ci.getName()); } return false; } public int hashCode() { return name.hashCode(); } } public class CachePersonTest { public static void main(String[] args) { CachePerson c1 = CachePerson.valueof("hello"); CachePerson c2 = CachePerson.valueof("hello"); //output:true System.out.println(c1 == c2); }
题外话:说实话,关于java中对于String,不可变类,只能懂60%.暂时就写到这了.等以后悟性高了再回头看吧.
标签:
原文地址:http://www.cnblogs.com/xzqhextt/p/4914798.html