非可变类,这个名词可能并不是所有人都知道。所谓非可变类,就是具有如下特征的类:每个实例中包含的所有信息都必须在该实例被创建的时候就提供出来;并且在此对象的整个生命周期内固定不变。
Java平台中其实有很多非可变类,比如原语类型的包装类(Integer、Long、Short等)、String、BigDecimal、BigInteger等。
使用非可变类的理由是:比起可变类,它们更加易于设计、实现和使用,更不容易出错,更加安全。
为了使一个类成为非可变类,要遵循以下五个规则:
1)不要提供任何会修改对象的方法。
2)保证没有可被子类改写的方法。(一般的做法是使本类成为final的)
3)使所有的域都是final的。(最好连类都是final的等同效果(final或者所有构造函数私有化),避免使用者派生出不合规范的子类)
4)使所有的域都是private的。
5)保证对于任何可变组件的互斥访问。
解释一下第5条,首先如果在你的类中有可变对象的域,则必须确保使用者无法获得这个对象的引用。具体说就是不能提供公有的getter方法,否则,如:
public class MyImmutable {
private final java.lang.Date birthday;
public Date getBirthday() {
return this.birthday;
}
}
//那么使用者可以这样来更改birthday的内容:(应为java.lang.Date是可变类,这一点是个遗憾,后面会提到)
MyImmutable mi = new MyImmutable(...);
mi.getBirthday().setYear(2000);
// 这就破坏了mi的非可变性。
再有就是在构造函数、访问方法和readObject方法(见【第56条】)中使用保护性拷贝技术。(见【第24条】)
如果类似上面的getter方法是必要的,那么要这样写:
public Date getBirthday() {
return new Date(this.birthday); // 用birthday做实参构造一个新的、和birthday同内容而不同地址的新实例,并返回
}
在非可变类的任何一个方法中,切忌不能改变内部信息。在来看看【第8条】中提及到的复数Complex的例子,以复数的加法为例,可千万不能再这样写了:
public void add(Complex c) {
this.re += c.re;
this.im += c.im;
}
对于非可变类,add方法必须要返回一个新的实例,而不再是在原来的基础上修改:
public Complex add(Complex c) {
return new Complex(this.re + c.re, this.im + c.im);
}
非可变类的好处除了简单(要有5大条条框框,真的简单吗?)以外,还有就是它是线程安全的,他们不要求同步(当然了,因为“不可能改变”吗),因此可以被自由地共享。
另外,还有一个好处就是可以节省系统内存开销。举个例子,String是非可变类,我们先假设它是可变类。如果将数据库中储存的全中国所有人的姓名都已String的形式一一保存,那么将要在内存中开销掉13亿个String的实例。幸好String是非可变的,所以内存开销可能仅有13亿的十分之一。因为中国人有大量的重名,那些重名的仅仅会被实例化一次。你可以做这样一个试验:
String s1 = "abc";
String s2 = "xyz";
String s3 = "abc";
通过Eclipse的调试工具,你可以看到,s1和s3的地址引用是相同的,也就是说系统并没有为s1和s3各分配一块空间,而是使用的同一个实例化的空间。
如果把这个例子中的“姓名”换成“生日”,并用java.lang.Date型来保存,那么不幸的事情发生了:假设现在在世的所有中国人中最大年龄为100岁,且近100年中,每天都会有目前健在的人出生,那么所有中国人不重复的生日可能性就是365 * 100 + 25(闰年) = 36525。然而,事实上你却会看到内存中真的出现了13亿个实例化的Data对象,而不是36525个。这就是因为前面提到的,java.lang.Date并不是非可变类。这是一个历史遗留下来的遗憾——在java系统设计之初,那时候的牛人们也还没有意识到非可变类的好处和必要性,所以把Date型设计成可变类了。后来,即使悔悟了,但是由于要保持向前兼容性而不得不继续这样。从一点也能看出上一条(【第11条】)的重要性。(当然Date设计出来就是让别人用的)
最后说一说非可变类的缺点,可能只有一条——对于每一个不同的值都要求一个单独的对象。创建这样的类可能会代价很高,特别是对于大型对象的情况。综合这一点和前面的优点,我总结一下,就是在那些容易出现重复值,而且并经常会改变值的应用中,适合使用非可变类。
如果一个类的实例在被创建后,还要频繁的改变其值,就不合适用非可变类,例如:
public String toString(){
String strValue = "";
strValue += this.f1.toString();
strValue += this.f2.toString();
strValue += this.f3.toString();
strValue += this.f4.toString();
strValue += this.f5.toString();
strValue += this.f6.toString();
strValue += this.f7.toString();
return strValue;
}
不停地改变String对象的值,其实是在不停地创建新的实例,并由JVM(Java虚拟机)去回收旧的实例,这样的性能很差。
toString方法应该这样写:
public String toString(){
StringBuffer sb = new StringBuffer(1000); // 简单的例子,就不考虑缓冲区溢出的问题了
sb.append(this.f1.toString());
sb.append(this.f2.toString());
sb.append(this.f3.toString());
sb.append(this.f4.toString());
sb.append(this.f5.toString());
sb.append(this.f6.toString());
sb.append(this.f7.toString());
return sb.toString();
}
StringBuffer是可变类,append方法就是直接改变它的值,而不必创建新的实例。
还有一句总结的话,就是“所有的值类都应该考虑使用非可变类”。但也不是绝对的,JavaBean应该也广义地算是值类。可是我们通常用一个JavaBean来保存一个客户订单,然而订单是经常要被修改的(如不停地在改变状态),显然使用非可变类每次改变都重新创建一个新的实例是不明智的(这就属于大型对象)。
同样,还有一句总结性的话,比上一句更“恐怖”:“除非有很好的理由要让一个类成为可变类,否则就应该是非可变的” 。而其,“即便一个类不能被做成非可变的,你仍然应该尽可能地限制它的可变性”。
【Effective Java 学习笔记】系列连载专题请见:
http://tonylian.iteye.com/categories/64208
分享到:
相关推荐
第三方支持库,由源代码作者按照静态编译技术文档(参见sdk\static_docs)完成自身改造并提供静态库后,可支持静态编译。外部OCX组件和COM组件,不支持静态编译。 此次重大版本升级不影响以前的源代码(.e)和模块...
文字式采用流行的div+css开发设计,界面新颖美观,采用文字式导航条更有利于搜索引擎抓取页面信息,同时程序还支持原有的图片式菜单效果,后台可轻松切换使用,以上2类导航条菜单均支持模板切换时导航条自动变换颜色...
十三、支持商品排序浏览,可以按价格高低、浏览量、添加时间进行排序显示。 十四、新增商品对比功能!可任意选择4款商品横向排开,一次性对比,更直观! 十五、购物车同比推荐功能,商城帮助中心栏目无限量...
十三、支持商品排序浏览,可以按价格高低、浏览量、添加时间进行排序显示。 十四、新增商品对比功能!可任意选择4款商品横向排开,一次性对比,更直观! 十五、购物车同比推荐功能,商城帮助中心栏目无限量...
文字式采用流行的div+css开发设计,界面新颖美观,采用文字式导航条更有利于搜索引擎抓取页面信息,同时程序还支持原有的图片式菜单效果,后台可轻松切换使用,以上2类导航条菜单均支持模板切换时导航条自动变换颜色...
第十三章 事件和索引指示器 .148 13.1 事 件 .148 13.2 索引指示器 .151 13.3 小 结 .154 第十四章 继 承 .155 14.1 C#的继承机制.155 <<page 3>> page begin==================== 14.2 多 态 性 ....
十三、现已整合财付通支付接口 财付通是腾讯推出的一款功能强大的在线支付工具,应用户的强烈要求,现已整合。程序目前拥有7种在线支付接口,后台自由切换使用,极其方便! 十四、订单自动通知功能! 有新订单...
第13章 极端的意见有害无益 236 13.1 API必须是漂亮的 237 13.2 API必须是正确的 237 13.3 API应该尽量简单 240 13.4 API必须是高性能的 242 13.5 API必须绝对兼容 242 13.6 API必须是对称的 ...
第一套 选择题 1.计算机感染病毒后会产生各种现象,以下不属于病毒现象的是__D__。 A、文件占用的空间变大 B、发生异常蜂鸣声 C、屏幕显示异常图形 D、机内的电扇不转 2. Windows98支持下面___C__网络协议。 A、...
第13章 显式锁227 13.1 Lock与 ReentrantLock227 13.1.1 轮询锁与定时锁228 13.1.2 可中断的锁获取操作230 13.1.3 非块结构的加锁231 13.2 性能考虑因素231 13.3 公平性232 13.4 在synchronized和...
间接依赖仅有commons-lang, slf4j等7个通用库,作为一个ORM框架,对第三方依赖极小。简单直接的API 框架的API设计直接面向数据库操作,不绕弯子,开发者只需要数据库基本知识,不必学习大量新的操作概念即可使用API...
第13章 反射与调试 clock命令 info命令 跨平台支持 跟踪变量的值 交互式命令历史记录 调试 scriptics的tclpro 其他工具 性能调校 第14章 名字空间 使用名字空间 名字空间变量 命令查找 嵌套名字空间...
内容简介 以用户为中心的时代,应用的界面外观正在变得越来越重要。然而,很多程序员都缺乏美术功底,要开发出...第13章 通过Ext Framework合理地应用EXT 附录A EXT常见问题 附录B EXT对AIR的支持 附录C EXT的版本变迁
内容简介 以用户为中心的时代,应用的界面外观正在变得越来越重要。然而,很多程序员都缺乏美术功底,要开发出...第13章 通过Ext Framework合理地应用EXT 附录A EXT常见问题 附录B EXT对AIR的支持 附录C EXT的版本变迁
支持数据库压缩功能,这是非常实用的功能,如网站商品过多,数据库会变得庞大且有冗余数据,使用压缩功能可使数据库大大减小,同时也会提高页面打开速度。支持查看当前数据库大小、下载数据库备份的功能。支持删除...
AbsolutePosition 不支持 不支持 可读写 可读写 ActiveConnection 可读写 可读写 可读写 可读写 BOF 只读 只读 只读 只读 Bookmark 不支持 不支持 可读写 可读写 CacheSize 可读写 可读写 可读写 可读写 ...
4.2.2 可变长子网掩码(VLSM) 37 4.3 无类域前路由(CIDR) 38 4.3.1 无类地址 38 4.3.2 强化路由汇聚 39 4.3.3 超网化 39 4.3.4 CIDR怎样工作 39 4.3.5 公共地址空间 40 4.3.6 RFC 1597和1918 40 4.4 小结 40 第5章 ...
8.3 可变性113 8.4 集合家族115 8.4.1 NSArray115 8.4.2 可变数组118 8.4.3 枚举“王国”119 8.4.4 快速枚举120 8.4.5 NSDictionary120 8.4.6 使用,但不要扩展122 8.5 各种数值122 8.5.1 NSNumber122 8.5.2 NSValue...