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>>
className {privateString firstName;privateString lastName;publicName() {}publicName(String firstName, String lastName) {this.firstName =firstName;this.lastName =lastName;
}publicvoidsetFirstName(String firstName) {this.firstName =firstName;
}publicString getFirstName() {returnthis.firstName;
}publicvoidsetLastName(String lastName) {this.lastName =lastName;
}publicString getLastName() {returnthis.lastName;
}
}publicclassPerson {privatefinalName name;publicPerson(Name name) {this.name =name;//(1) this.name = new Name(name.getFirstName(), name.getLastName());
}publicName getName() {returnname; //(2) return new Name(name.getFirstName(), name.getLastName());
}publicstaticvoidmain(String[] args) {
Name n=newName("wukong", "sun");
Person p=newPerson(n);
System.out.println(p.getName().getFirstName());//wukongn.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方法*/publicclassAddress {privatefinalString detail;privatefinalString postCode;publicAddress() {//如果类编写了有参构造器通常建议提供一个无参构造器this.detail = "";this.postCode = "";
}publicAddress(String detail, String postCode) {this.detail =detail;this.postCode =postCode;
}publicString getPostCode() {returnthis.postCode;
}publicString getDetail() {returnthis.detail;
}publicbooleanequals(Object obj) {if(this==obj) {returntrue;
}if(obj !=null&& obj.getClass() == Address.class) {
Address ad=(Address)obj;if(this.getDetail().equals(ad.getDetail())&&this.getPostCode().equals(ad.getPostCode())) {returntrue;
}
}returnfalse;
}publicinthashCode() {returndetail.hashCode() + postCode.hashCode() * 31;
}
}
由于没提供方法setter方法,所以无法修改Address类的field;
那么难道只要不提供setter方法就可以构成不可变类吗?
不,private final可以保证不可变类创建的实例的field不被改变不被外界访问到.
6.不可变类的实例状态不会变化,这样的实例可以安全地被其他与之关联的对象共享,还可以安全地被多个线程共享,为了节约内存空间,应该尽可能重用不可变类的实例,避免重复创建具有相同属性值的实例.那么该如何实现缓存技术呢?缓存技术有很多种,下面用数组作为缓存池,来实现一个缓存实例的不可变类.
/*** 此程序用数组模拟缓存池
* CachePerson类能控制生成的CachePerson对象个数,只能通过valueof()获取该实例
* 如果某个对象只使用一次,那么没必要使用缓存;
* 如果某个对象频繁使用,缓存就很必要.
* java.lang.Integer采用了相同的策略,如果采用new对象,则每次都是全新的;如果采用valueof则缓存*/classCachePerson {privatestaticintMAX_SIZE = 10;//使用数组缓存privatestaticCachePerson[] cache =newCachePerson[MAX_SIZE];//记录缓存实例在缓存中的位置:cache[pos-1]是新缓存的实例privatestaticintpos = 0;privatefinalString name;//private构造器,外类无法构造对象privateCachePerson(String name) {this.name =name;
}publicString getName() {returnname;
}//一旦封装了构造器,一定有public方法来创建对象//static是必要的,因创建对象只能是类方法publicstaticCachePerson valueof(String name) {//遍历已缓存的实例for(inti = 0;i < MAX_SIZE; i++) {
如有相同实例则直接返回改实例if(cache[i] !=null&&cache[i].getName().equals(name)) {returncache[i];
}
}//缓存已满if(pos ==MAX_SIZE) {//把缓存的第一个实例覆盖,即把刚生成的对象放在缓存池的开始位置cache[0] =newCachePerson(name);
pos= 1;
}else{//新创建的实例缓存起来,pos+1cache[pos++] =newCachePerson(name);
}returncache[pos - 1];
}/*此处equals方法很常用最好记下来:判断两个对象是否相等
== 对于基本数据类型只要值相等就行;对于引用变量,只有指向同一个对象时才true;
== 不可比较没有继承关系的对象
默认的String类的equals方法只要对象的字符序列相等即认为相等;
String类的equals方法是重写了Object类的equals方法,该方法与==区别不大;
一般情况下,我们要求像String类那样只要值相同则认为其相同 @see java.lang.String#964*/publicbooleanequals(Object obj) {//判断两个对象是否是同一个对象if(this==obj) {returntrue;
}//当obj不为null且是CachePerson的实例时if(obj !=null&& obj.getClass() == CachePerson.class) {
CachePerson ci=(CachePerson)obj;returnname.equals(ci.getName());
}returnfalse;
}publicinthashCode() {returnname.hashCode();
}
}publicclassCachePersonTest {publicstaticvoidmain(String[] args) {
CachePerson c1= CachePerson.valueof("hello");
CachePerson c2= CachePerson.valueof("hello");//output:trueSystem.out.println(c1 ==c2);
}
题外话:说实话,关于java中对于String,不可变类,只能懂60%.暂时就写到这了.等以后悟性高了再回头看吧.