非可变类,这个名词可能并不是所有人都知道。所谓非可变类,就是具有如下特征的类:每个实例中包含的所有信息都必须在该实例被创建的时候就提供出来;并且在此对象的整个生命周期内固定不变。
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
分享到:
相关推荐
第十三条:各企事业单位运行维护队伍参照总部信息系统年度运行维护计划,编制本单位信息系统年度运行维护计划,各单位负责落实各自运行维护的费用。 第四章 日常运行维护 第十四条:信息系统运行维护工作坚持“变...
ISO26262分为多个部分,其中第8部分专门关注支持过程,即那些虽非直接涉及产品开发,但对确保整个开发流程的有效性和安全性至关重要的过程。 #### 支持过程的重要性 支持过程对于维持整个开发过程的质量、效率以及...
首先,滚动条作为网页界面不可或缺的一部分,它的美观性和交互性直接影响到用户对网站的第一印象。传统的浏览器默认滚动条往往简洁但缺乏个性化,而jQuery滚动条插件则提供了丰富的定制选项,允许开发者根据品牌风格...
第三方支持库,由源代码作者按照静态编译技术文档(参见sdk\static_docs)完成自身改造并提供静态库后,可支持静态编译。外部OCX组件和COM组件,不支持静态编译。 此次重大版本升级不影响以前的源代码(.e)和模块...
【标题】:“第十三届Scratch编程比赛试卷” 【描述】:这份文档包含了第十三届云飞杯SCRATCH作品比赛的详细试题,旨在考核参赛者在Scratch编程中的基本技能和创新能力。 【标签】:“互联网” 【内容】:这次...
在21世纪的高级营销主管培训中,第十四章的主题聚焦于设计和管理服务。这一章节探讨了服务的本质,服务的分类,以及如何通过差异化和质量提升来优化服务。服务的定义强调其不可分离性、易消失性、无形性和可变性这四...
- 第三个子网支持18台主机; - 第四个子网支持50台主机。 ##### 2. 计算所需的主机位数 - 第一个子网:\( 2^3 \geq 4 + 2 \)(主机位数为3) - 第二个子网:\( 2^3 \geq 4 + 2 \)(主机位数为3) - 第三个子网:\( ...
《刑法》第二百五十三条之一作为对侵犯公民个人信息行为的法律规定,在大数据的背景下如何界定入罪的边界,是法律实践和理论研究中面临的一个重要问题。 个人医疗信息属于公民个人信息的一部分,具有极高的敏感性和...
#### 法则十三:高效的数据存储与检索 随着数据量的增长,如何有效地存储和检索信息变得越来越重要。这要求开发者选择合适的数据库解决方案,并优化查询效率。例如,使用NoSQL数据库来处理非结构化数据,或者采用...
此外,它还能与第三方软件配合,导入导出标准图像文件序列,增强了系统的兼容性和灵活性。 编辑功能是X-EDIT的核心,支持不同码率的DV格式素材混编,提供视音频素材与字幕素材的混编,独特的四点编辑操作,以及故事...
本章“c#开发经验技巧宝典--第13章”将深入探讨如何利用C#语言来处理和操作图形图像。以下是一些关键知识点的详细说明: 1. **GDI+(Graphics Device Interface Plus)**:这是.NET Framework提供的一组API,用于...
4. **分类**:实时操作系统根据实时性的强弱可分为强实时、实时、弱实时和非实时四类。此外,还可以按照功能和应用环境进行分类,如嵌入式实时操作系统,广泛应用于移动计算和网络化设备。 5. **基本特点**: - **...
文字式采用流行的div+css开发设计,界面新颖美观,采用文字式导航条更有利于搜索引擎抓取页面信息,同时程序还支持原有的图片式菜单效果,后台可轻松切换使用,以上2类导航条菜单均支持模板切换时导航条自动变换颜色...
十三、支持商品排序浏览,可以按价格高低、浏览量、添加时间进行排序显示。 十四、新增商品对比功能!可任意选择4款商品横向排开,一次性对比,更直观! 十五、购物车同比推荐功能,商城帮助中心栏目无限量...
十三、支持商品排序浏览,可以按价格高低、浏览量、添加时间进行排序显示。 十四、新增商品对比功能!可任意选择4款商品横向排开,一次性对比,更直观! 十五、购物车同比推荐功能,商城帮助中心栏目无限量...
该技术的核心在于利用DNA双链互补配对的原理,即当带有荧光标记的DNA探针与固相表面的DNA片段发生碱基配对时,会产生可检测的荧光信号。 DNA芯片分为两种主要类型:原位合成芯片和DNA微集阵列。原位合成芯片通过显...
在第十三章中,我们将深入探讨信息化动态管理的关键方面,包括但不限于以下几个核心知识点: 1. **信息化战略规划**:信息化动态管理首先要明确信息化的目标和策略,这涉及到对业务需求的理解、技术趋势的把握以及...
【多云环境下的智能运维】是当前IT领域的重要议题,特别是在第十三届中国IDC产业年度大典中。随着数字化转型的加速,云技术和人工智能(AI)的融合正在深刻改变着应用的开发、部署和运维模式。 传统的应用依赖于固定...
第十条:版本控制和代码审查 使用版本控制系统(如Git)管理代码,进行代码审查,可以发现潜在问题,提高团队协作效率,确保代码质量。 以上十条戒律旨在帮助Java开发者养成良好的编程习惯,提高代码质量和团队合作...
文字式采用流行的div+css开发设计,界面新颖美观,采用文字式导航条更有利于搜索引擎抓取页面信息,同时程序还支持原有的图片式菜单效果,后台可轻松切换使用,以上2类导航条菜单均支持模板切换时导航条自动变换颜色...