Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 287|回复: 0

[Java基础知识]关于不可变类和可变类

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-10-2 09:28:09 | 显示全部楼层 |阅读模式
    所谓不可变类,是指当创建了这个类的实例后,就不允许修改它的属性值。在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修饰的属性所属的类是不可变类,就无须提供保护性拷贝,因为该属性所引用的实例的值永远不会被改变。这进一步体现了不可变类的优点。
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-2-26 22:09 , Processed in 0.618251 second(s), 36 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表