关于不可变类和可变类
所谓不可变类,是指当创建了这个类的实例后,就不允许修改它的属性值。在JDK的基本类库中,所有基本类型的包装类,如Integer和Long类,都是不可变类,java.lang.String也是不可变类。以下代码创建了一个String对象和Integer对象,它们的值分别为“Hello”和 10,在程序代码中无法再改变这两个对象的值,因为Integer和String类没有提供修改其属性值的接口。
String s=new String("Hello");
Integer i=new Integer(10);
用户在创建自己的不可变类时,可以考虑采用以下设计模式:
1..把属性定义为private final类型。
2.不对外公开用于修改属性的setXXX()方法。
public Name(String firstname, String lastname) {
3.只对外公开用于读取属性的getXXX()方法。
4.在构造方法中初始化所有属性。
5.覆盖Object类的equals()和hashCode()方法。在equals()方法中根据对象的属性值来比较两个对象是否相等,并且保证用 equals()方法判断为相等的两个对象的hashCode()方法的返回值也相等,这可以保证这些对象能正确地放到HashMap或HashSet集合中。
如果需要的话,提供实例缓存和静态工厂方法,允许用户根据特定参数获得与之匹配的实例。
例程11-9的Name类就是不可变类,它仅仅提供了读取sex和description属性的getXXX()方法,但没有提供修改这些属性的setXXX()方法。
例程11-9 Name.java
public class Name {
private final String firstname;
private final String lastname;
this.firstname = firstname;
this.lastname = lastname;
}
public String getFirstname(){
return firstname;
}
public String getLastname(){
return lastname;
}
public boolean equals(Object o){
if (this == o) return true;
if (!(o instanceof Name)) return false;
final Name name = (Name) o;
if(!firstname.equals(name.firstname)) return false;
if(!lastname.equals(name.lastname)) return false;
return true;
}
public int hashCode(){
int result;
result= (firstname==null?0:firstname.hashCode());
result = 29 * result + (lastname==null?0:lastname.hashCode());
return result;
}
public String toString(){
return lastname+" "+firstname;
}
}
假定Person类的name属性定义为Name类型:
public class Person{
private Name name ;
private Gender gender;
…
}
以下代码创建了两个Person对象,他们的姓名都是“王小红”,一个是女性,另一个是男性。在最后一行代码中,把第一个Person对象的姓名改为“王小虹”。
Name name=new Name("小红","王");
Person person1=new Person(name,Gender.FEMALE);
Person person2=new Person(name,Gender.MALE);
name=new Name("小虹","王");
person1.setName(name); //修改名字
与不可变类对应的是可变类,可变类的实例属性是允许修改的。如果把以上例程11-9的Name类的firstname属性和lastname属性的 final修饰符去除,并且增加相应的public类型的setFirstname()和setLastname()方法,Name类就变成了可变类。以下程序代码本来的意图也是创建两个Person对象,他们的姓名都是“王小红”,接着把第一个Person对象的姓名改为“王小虹”:
//假定以下Name类是可变类
Name name=new Name("小红","王");
Person person1=new Person(name,Gender.FEMALE);
Person person2=new Person(name,Gender.MALE);
name.setFirstname(" 小虹"); //试图修改第一个Person对象的名字
以上最后一行代码存在错误,因为它会把两个Person对象的姓名都改为“王小虹”。由此可见,使用可变类更容易使程序代码出错。因为随意改变一个可变类对象的状态,有可能会导致与之关联的其他对象的状态被错误地改变。
不可变类的实例在实例的整个生命周期中永远保持初始化的状态,它没有任何状态变化,简化了与其他对象之间的关系。不可变类具有以下优点:
l 不可变类能使程序更加安全,不容易出错。
l 不可变类是线程安全的,当多个线程访问不可变类的同一个实例时,无须进行线程的同步。
由此可见,应该优先考虑把类设计为不可变类,假使必须使用可变类,也应该把可变类尽可能多的属性设计为不可变的,即用final修饰符来修饰,并且不对外公开用于改变这些属性的方法。
在创建不可变类时,假如它的属性的类型是可变类型,在必要的情况下,必须提供保护性拷贝,否则,这个不可变类实例的属性仍然有可能被错误地修改。这条建议同样适用于可变类中用final修饰的属性。
例如例程11-10的Schedule类包含学校的开学时间和放假时间信息,它是不可变类,它的两个属性start和end都是final类型,表示不允许被改变,但是这两个属性都是Date类型,而Date类是可变类。
例程11-10 Schedule.java
import java.util.Date;
public final class Schedule{
private final Date start; //开学时间,不允许被改变
private final Date end; //放假时间,不允许被改变
public Schedule(Date start,Date end){
//不允许放假日期在开学日期的前面
if(start.compareTo(end)>0)
throw new IllegalArgumentException(start +" after " +end);
this.start=start;
this.end=end;
}
public Date getStart(){return start;}
public Date getEnd(){return end;}
}
尽管以上Schedule类的start和end属性是final类型的,但由于它们引用Date对象,在程序中可以修改所引用Date对象的属性。以下程序代码创建了一个Schedule对象,接下来把开学时间和放假时间都改为当前系统时间。
Calendar c= Calendar.getInstance();
c.set(2006,9,1);
Date start=c.getTime();
c.set(2007,1,25);
Date end=c.getTime();
Schedule s=new Schedule(start,end);
end.setTime(System.currentTimeMillis()); //修改放假时间
start=s.getStart();
start.setTime(System.currentTimeMillis()); //修改开学时间
为了保证Schedule对象的start属性和end属性值不会被修改,必须为这两个属性使用保护性拷贝,参见例程11-11。
例程11-11 采用了保护性拷贝的Schedule.java
import java.util.Date;
public final class Schedule {
private final Date start;
private final Date end;
public Schedule(Date start,Date end){
//不允许放假日期在开学日期的前面
if(start.compareTo(end)>0)throw new IllegalArgumentException(start +" after " +end);
this.start=new Date(start.getTime()); // 采用保护性拷贝
this.end=new Date(end.getTime()); // 采用保护性拷贝
}
public Date getStart(){return (Date)start.clone() ;} // 采用保护性拷贝
public Date getEnd(){return (Date)end.clone() ;} // 采用保护性拷贝
}
通过采用保护性拷贝,其他程序无法获得与Schedule对象关联的两个Date对象的引用,因此也就无法修改这两个Date对象的属性值。
Tips
如果Schedule类中被final修饰的属性所属的类是不可变类,就无须提供保护性拷贝,因为该属性所引用的实例的值永远不会被改变。这进一步体现了不可变类的优点。
分享到:
相关推荐
57 023模式的不可反驳和可反驳.mp4
来看个不可变类示例:ThreeStooges类是一个不可变类, 它的状态在创建后不能再修改,所有域都是final类型,并且它被正确创建(创建期间没有发生this引用的逸出)。该类有三个方法:isStooge、getStoogeNames,都是...
本文将深入探讨网络游戏如何利用可变长度封包(Variable Length Packet,VLP)和可变长度上行时隙(Variable Length Upstream Time Slot,VLUS)在单点对多点的无源光网络架构下实现高效的数据传输。 首先,无源光...
在Java编程中,不可变对象是指一旦创建后其状态不能被修改的对象。这种对象模式在多线程环境和频繁对象创建的场景下,提供...在设计代码时,合理使用不可变对象可以提高代码的可读性和可维护性,减少潜在的错误和问题。
在 Java 中实现不可变类是提高程序安全性和可维护性的重要手段,尤其是在函数式编程中。不可变类的核心特性是其对象一旦创建,其状态就不能再发生改变。这是因为不可变对象具有以下优点: 1. **线程安全**:由于...
同时,状态变量通常是系统内部的不可直接测量的变量,需要通过输入和输出的信息来推断。因此,可控性和可观测性成为现代控制理论中的关键研究点。 可控性是指系统是否能通过输入信号在有限时间内将任意初始状态驱动...
如果电桥平衡,电容C2的电压不受输入电压u(t)的影响,表明状态变量x2(t)不可控,系统不完全可控。相反,如果电桥不平衡,两个电容的电压都可以通过输入电压控制,那么系统是可控的。 又如并联双水槽系统,当考虑...
然而,仅仅设置列宽并不能实现拖动列宽的功能。为了达到这个效果,我们需要引入一个额外的插件,比如 "bootstrap-table-colresize"。首先,下载或通过 CDN 引入该插件的 JavaScript 和 CSS 文件,例如: ```html ...
前言 nodejs中大量的api与流有关,曾经...后两种其实是对可读和可写流的应用。所以我想先聊聊可读流和可写流。 可读流 可读流有两种模式,并随时可以转换,我们可以通过监听可读流的事件来操作它。 两种模式(引用自n
标题中的“基于中心差分有限离散化和 Newton Raphson 算法求解 NACA 翼型二维不可压缩和可压缩流动附matlab代码”表明了本项目涉及的主要内容是数值方法在流体力学中的应用,具体是用 MATLAB 编程实现对 NACA 翼型的...
总之,不可变类在Java中被广泛使用,它们提供了安全性、效率和可预测性,是软件设计中的重要原则。String、Boolean和LocalDate等类的不可变性体现了这一原则,使得在处理这些类的对象时更加简单和可靠。
不可区分度和可区分度是指信息系统中对象之间的不可区分关系和可区分关系。不可区分度是指对象之间的不可区分关系,而可区分度是指对象之间的可区分关系。两者之间存在着紧密的关系。 知识点3:知识约简算法的设计 ...
不可变的 Python的不可变映射类型。 基础数据结构是在Clojure,Scala,Haskell和其他功能语言中使用的哈希数组映射树(HAMT)。 CPython 3.7中的contextvars模块中使用了此实现(有关更多详细信息,请参见和 )。 ...
由于用户私钥的不可追踪性,当签名者的私钥被泄露时,很难找到泄露者。 为了克服这个问题,研究者们提出了可追溯的属性基签名方案,使得在某些条件下,签名者的行为可以被追溯。一个可追溯的ABS方案通过特定的机制...
可修改性则强调在不影响其他功能的前提下,对软件进行修改的难易程度。可测试性确保软件模块化和易于验证,降低调试成本。可移植性则是指软件在不同硬件或软件平台上重用的容易程度。 根据GB/T 14394标准,软件可靠...
本文主要研究了具有状态相关噪声的离散随机Markov跳跃系统的可检测性和可观测性问题。在现代控制理论中,可检测性和可观测性是两个基本概念,它们在系统分析和设计中扮演着重要的角色。例如,这两个概念在进行线性二...
首先,章节中提到了**不可变对象与不可变类**的概念。不可变对象是指一旦创建后,其内容无法更改的对象,对应的类即为不可变类。例如,如果删除`Circle`类中的`set`方法,使得半径不能被修改,那么该类就变成了不可...
利用卡诺定理证明了温度绝对零的不可及性。 因此,这种不可实现性不同于热力学第三定律的普朗克-费-米陈述,即熵在T = 0时消失。 结果表明,等温压缩率KT通常大于绝热压缩率Ks,并且在低温极限时差KT-Ks消失。