不可变类

Posted wuxun1997

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了不可变类相关的知识,希望对你有一定的参考价值。

  顾名思义,一个类实例化一个对象后,对象的属性无法被改变,可称之为不可变类。如JDK中的八大包装类、String类等。不可变类各有用处,如包装类用于对基本类型的装箱操作,把基本类型化身为对象使用。而String类作为我们最常用的类之一,通过字符串常量池大大提升了性能。不可变类因为是不可变的,所以天然具有线程安全性。那么如何定义一个类为不可变类呢?

  首先,关于类的成员变量(也就是属性),如果它们都不是不可变类,那么它们应该是私有的、final的。通过私有的封装来让属性外部无法修改,而final的作用是让属性初始化后就不能再改变;

  其次,关于类的方法,我们不能提供任何修改属性是方法,比如常见的setXXX方法就不能再出现了。如果类成员变量本身不是不可变的,那么需要注意两点:1、在初始化(比如构造器里)给该成员变量赋值时,应该取的是外部对象引用的克隆;2、如果不可变类提供给外部用于获取该成员变量的方法时,应该使用该成员变量的克隆,而非直接返回成员变量本身。这两点的目的都是避免外部通过对象引用修改不可变类的内部成员变量。详见下面例子:

import java.util.Date;

public class Immutable 
    private String name;
    private final Date date;

    public Immutable(String name, Date date) 
        this.name = name;
        this.date = date;
    

    public String getName() 
        return name;
    

    public Date getDate() 
        return date;
    

    @Override
    public String toString() 
        return "Immutable" +
                "name=‘" + name + ‘\‘‘ +
                ", date=" + date +
                ‘‘;
    

    public static void main(String[] args) 
        String name = "wlf";
        Date today = new Date();

        Immutable t = new Immutable(name, today);
        System.out.println(t.toString());

        // 构造器初始化方法漏洞
        name = "wms";
        today.setTime(today.getTime() + 100000);
        System.out.println("Change: " + t.toString());

        // get方法漏洞
        name = t.name;
        today = t.getDate();
        name = "hello";
        today.setTime(today.getTime() + 100000);
        System.out.println("Change: " + t.toString());
    

  运行结果:

Immutablename=‘wlf‘, date=Mon Jun 03 22:55:10 CST 2019
Change: Immutablename=‘wlf‘, date=Mon Jun 03 22:56:50 CST 2019
Change: Immutablename=‘wlf‘, date=Mon Jun 03 22:58:30 CST 2019

  我们看到String是不可变类,所以我们很放心,而Date并非不可变类,所以它变了。我们通过克隆来让对象的引用不被外部操纵:

import java.util.Date;

public class Immutable 
    private String name;
    private final Date date;

    public Immutable(String name, Date date) 
        this.name = name;
        this.date = (Date) date.clone();
    

    public String getName() 
        return name;
    

    public Date getDate() 
        return (Date) date.clone();
    

    @Override
    public String toString() 
        return "Immutable" +
                "name=‘" + name + ‘\‘‘ +
                ", date=" + date +
                ‘‘;
    

    public static void main(String[] args) 
        String name = "wlf";
        Date today = new Date();

        Immutable t = new Immutable(name, today);
        System.out.println(t.toString());

        // 构造器初始化方法漏洞
        name = "wms";
        today.setTime(today.getTime() + 100000);
        System.out.println("Change: " + t.toString());

        // get方法漏洞
        name = t.name;
        today = t.getDate();
        name = "hello";
        today.setTime(today.getTime() + 100000);
        System.out.println("Change: " + t.toString());
    

  运行结果:

Immutablename=‘wlf‘, date=Mon Jun 03 22:57:16 CST 2019
Change: Immutablename=‘wlf‘, date=Mon Jun 03 22:57:16 CST 2019
Change: Immutablename=‘wlf‘, date=Mon Jun 03 22:57:16 CST 2019

  最后,关于类本身,为了防止子类复写父类的方法从而破坏不可变性,我们把类定义为final的。

以上是关于不可变类的主要内容,如果未能解决你的问题,请参考以下文章

实战并发编程 - 03基于不可变模式解决并发问题_1

不可变对象

可变类还是不可变类?

为啥片段类应该是公开的?

Java 不可变类

java中一个类是不可变类的条件是啥?求详细解答。