java不可变类

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

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%.暂时就写到这了.等以后悟性高了再回头看吧.

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航