Java 的不可变类 (IMMUTABLE CLASS) 和 可变类 (MUTABLE CLASS)

一、简单定义
不可变对象(Immutable Objects)即对象一旦被创建,它的状态(对象的数据,也即对象属性值)就不能改变,反之即为可变对象(Mutable Objects)。
当满足以下条件时,对象才是不可变的:
1. 对象创建以后其状态就不能修改。
2. 对象的所有域都是final类型。
3. 对象是正确创建的(在对象的创建期间,this引用没有逸出)。
不可变对象的类即为不可变类(Immutable Class)。Java平台类库中包含许多不可变类,如String、基本类型的包装类、BigInteger和BigDecimal等。
对于String和StringBuilder,String是immutable的,每次对String对象的修改都将产生一个新的String对象,而原来的对象保持不变。而StringBuilder是mutable,因为每次对于它的对象的修改都作用于该对象本身,并没有产生新的对象。

二、优缺点
不可变对象有很多优点:
1. 构造、测试和使用都很简单。
2. 线程安全且没有同步问题,不需要担心数据会被其它线程修改。因为在多线程同时进行的情况下,一个可变对象的值很可能被其他进程改变,这样会造成不可预期的结果,而使用不可变对象就可以避免这种情况。
3. 当用作类的属性时不需要保护性拷贝。
4. 可以很好的用作Map键值和Set元素。
不可变对象最大的缺点就是创建对象的开销,因为每一步操作都会产生一个新的对象。

三、编写不可变类

可以遵照以下几点来编写一个不可变类:
1. 类应该定义成final,避免被继承。将类声明为final (强不可变类),或者将所有类方法加上final(弱不可变类)。或者使用静态工厂并声明构造器为private。
2. 声明属性为 private 和 final 。
3. 不要提供任何可以修改对象状态的方法:不仅仅是set方法,还有任何其它可以改变状态的方法。
4. 如果类有任何可变对象属性,那么当它们在类和类的调用者间传递的时候必须被保护性拷贝。如果某一个类成员不是原始变量(primitive)或者不可变类,必须通过在成员初始化(in)或者get方法(out)时通过深度clone方法,来确保类的不可变。
注意:get 方法不要把类里的成员变量让外部客户端引用,当需要访问成员变量时,返回成员变量的copy。构造函数不要引用外部可变对象,如果需要引用可以在外部改变值的变量,应该在构造函数里进行defensive copy。

import java.util.Date;

/**
 * Planet是一个不可变类,因为当它构造完成之后没有办法改变它的状态
 */

final class Planet {
    /**
     * 声明为final的基本类型数据总是不可变的
     */
    private final double fMass;

    /**
     * 不可变的对象属性 (String对象不可变)
     */
    private final String fName;

    /**
     * 可变的对象属性. 在这种情况下, 这个可变属性只能被这个类改变。
     * (在其它情况下, 允许在原生类外部改变一个属性是很有意义的;
     * 这种情况就是当属性作为其它地方创建的一个对象引用)
     */
    private final Date fDateOfDiscovery;

    public Planet(double aMass, String aName, Date aDateOfDiscovery) {
        fMass = aMass;
        fName = aName;
        //创建aDateOfDiscovery的一个私有拷贝,而不是外部变量的引用。
        //这是保持fDateOfDiscovery属性为private的唯一方式, 并且保护这个
        //类不受调用者对于原始aDateOfDiscovery对象所做任何改变的影响
        fDateOfDiscovery = new Date(aDateOfDiscovery.getTime());
    }

    /**
     * 返回一个基本类型值.
     * <p>
     * 调用者可以随意改变返回值,但是不会影响类内部。
     */
    public double getMass() {
        return fMass;
    }

    /**
     * 返回一个不可变对象
     * <p>
     * 调用者得到内部属性的一个直接引用. 由于String是不可变的所以没什么影响
     */
    public String getName() {
        return fName;
    }

    // /**
//      * 返回一个可变对象 - 不是一个好的方式.
//      *
//      * 调用者得到内部属性的一个直接引用. 这通常很危险,因为Date对象既可以
//      * 被这个类改变也可以被它的调用者改变. 即, 类不再对fDate拥有绝对的控制。
//      */
//    public Date getDateOfDiscovery() {
//        return fDateOfDiscovery;
//    }

    /**
     * 返回一个可变对象 - 好的方式.
     * <p>
     * 返回属性的一个保护性拷贝.调用者可以任意改变返回的Date对象,但是不会
     * 影响类的内部.为什么? 因为它们没有fDate的一个引用. 更准确的说, 它们
     * 使用的是和fDate有着相同数据的另一个Date的拷贝对象
     */
    public Date getDateOfDiscovery() {
        return new Date(fDateOfDiscovery.getTime());
    }
}

public class ImmutableClass {
    /**
     * 测试方法
     *
     * @param args
     */
    public static void main(String[] args) {
        Planet planet = new Planet(1.0D, "earth", new Date());
        Date date = planet.getDateOfDiscovery();
        date.setTime(888_888_888L);
        System.out.println("the value of fDateOfDiscovery of internal class : " + planet.getDateOfDiscovery().getTime());
        System.out.println("the value of date after change its value : " + date.getTime());
    }
}

运行结果如下:

the value of fDateOfDiscovery of internal class : 1453794489406
the value of date after change its value : 888888888

由此可见Planet类的属性fDateOfDiscovery在对象构造完成之后就没有再改变。

四、使用场景
不可变类最适合表示抽象数据类型(如数字、枚举类型或颜色)的值。
另一个适合用不可变类实现的好示例就是 事件 。事件的生命期较短,而且常常会在创建它们的线程之外的线程中消耗,所以使它们成为不可变的是利大于弊。同样地,在 通信系统的 组件间 进行 消息传递,将消息对象设计成不可变的是明智的。
但有的时候String的immutable特性也会引起安全问题,这就是密码应该存放在字符数组中而不是String中的原因!
1. 由于String在Java中是不可变的,如果你将密码以明文的形式保存成字符串,那么它将一直留在内存中,直到垃圾收集器把它清除。而由于字符串被放在字符串缓冲池中以方便重复使用,所以它就可能在内存中被保留很长时间,而这将导致安全隐患,因为任何能够访问内存(memory dump内存转储)的人都能清晰的看到文本中的密码,这也是为什么你应该总是使用加密的形式而不是明文来保存密码。由于字符串是不可变的,所以没有任何方式可以修改字符串的值,因为每次修改都将产生新的字符串,然而如果你使用char[]来保存密码,你仍然可以将其中所有的元素都设置为空或者零。所以将密码保存到字符数组中很明显的降低了密码被窃取的风险。
2. Java本身也推荐使用JPasswordField组件的getPassword()方法,该方法将返回一个字符数组,而放弃了原来的getText()方法,这个方法把密码以明文的形式返回而可能会引起安全问题。
这就是为什么使用字符数组存储密码比字符串更好的原因。只使用字符数组也是不够的,为了更安全你需要将数组内容进行转化。建议使用哈希的或者是加密过的密码而不是明文,然后一旦完成验证,就将它从内存中清除掉。

欢迎关注微信公众号:shoshana

原文地址:https://www.cnblogs.com/shoshana-kong/p/10822759.html

时间: 2024-10-10 15:49:15

Java 的不可变类 (IMMUTABLE CLASS) 和 可变类 (MUTABLE CLASS)的相关文章

JAVA不可变类(immutable)机制与String的不可变性

不可变类:是指这个类实例一旦创建,就不能不该其成员变量的值 优点: 1.线程安全 对象的值无法改变,降低并发错误的可能性 2.效率高 当一个对象需要复制时,就只需要复制对象地址,不用复制本生 不变性,保证了hashcode的唯一性,每次缓存时不必重新计算hashcode,所以常用string作为key 3.便于测试 而且如果程序里的变量都是immutable 的话 side effect就比较小 程序只要写好测一遍基本没有什么bug 缺点: 每一次改变都需要产生新的对象,容易产生很多垃圾 设计方

【Java基础】JAVA不可变类(immutable)机制与String的不可变性

一.不可变类简介 不可变类:所谓的不可变类是指这个类的实例一旦创建完成后,就不能改变其成员变量值.如JDK内部自带的很多不可变类:Interger.Long和String等. 可变类:相对于不可变类,可变类创建实例后可以改变其成员变量值,开发中创建的大部分类都属于可变类. 下面的理解可能会易懂一些: {概念:不可变类的意思是创建该类的实例后,该实例的属性是不可改变的.Java中的8个包装类和String类都是不可变类.所以不可变类并不是指该类是被final修饰的,而是指该类的属性是被final修

从字节码和JVM的角度解析Java核心类String的不可变特性

1. 前言 最近看到几个有趣的关于Java核心类String的问题. String类是如何实现其不可变的特性的,设计成不可变的好处在哪里. 为什么不推荐使用+号的方式去形成新的字符串,推荐使用StringBuilder或者StringBuffer呢. 翻阅了网上的一些博客和stackoverflow,结合自己的理解做一个汇总. 2. String类是如何实现不可变的 String类的一大特点,就是使用Final类修饰符. A class can be declared final if its

Java设计模式之immutable(不可变)模式

immutable简介 不可变对象永远不会发生改变,其字段的值只在构造函数运行时设置一次,其后就不会再改变.例如JDK中常见的两种基本数据类型String和Integer,它们都是不可变对象.为了理解immutable与mutable的区别,可以看看下面的一段代码: package date0804.demo2; import java.awt.Point; public class ImmutableString { public static void main(String[] args)

Java面向对象进阶篇(包装类,不可变类)

一. Java 8的包装类 Java中的8种基本数据类型不支持面向对象的变成机制,也不具备对象的特性:没有成员变量,方法可以调用.为此,Java为这8 种基本数据类型分别提供了对应的 包装类(Byte,Short,Integer,Long,Double,Float,Charater,Boolean). 从jdk 1.5开始,Java提供了自动装箱和自动拆箱的功能.自动装箱就是可以把一个基本类型变量赋给对应的包装类变量.自动拆箱与之相反. 包装类提供了基本类型变量和字符串之间的转换的方法.有两种方

Java基础知识回顾-22(静态导入,可变参数,Collections集合工具类,集合嵌套)

1.在导包的时候可以导入其静态部分,这样在类中使用其时,可以直接以其名使用 例如:Map.Entry的访问,在类文件头部导入import java.util.Map.Entry后简化后为Entry. 2.可以用"修饰符 返回值类型 方法名(参数类型... 形参名){  }"的方式来同时传入不确定个数,相同参数类型的参数.比如当求不确定个数的int类型的数据的和时,可以使用这种方式,避免写多个方法,如果参数类型不改变,则该方法不可重载,因为可变参数不确定参数个数吗.若要增加不可变参数,需

【Java 进阶篇】【第一课】String类

引用 String类包含在java.lang包中.这个包会在Java启动的时候自动import,所以可以当做一个内置类(built-in class).我们不需要显式的使用import引入String类. 创建 String类是唯一一个不需要new关键字来创建对象的类.使用的时候需要注意 String s = "Hello World!"; System.out.println(s); 操作 可以用+实现字符串的连接(concatenate),比如: "abc" +

JAVA 中为什么String 是immutable的

本文翻译自:http://www.programcreek.com/2013/04/why-string-is-immutable-in-java/ 这是一个很老但很流行的问题,这里有几个原因String在java中被设计成immutable的.对内存.同步.数据结构等有好的理解,能更好的回答这个问题.下面我将简单的介绍这些原因: 1, String Pool的需要. String pool(String intern pool) 是一个方法区里的特殊的存储区域.当创建一个String, 如果它

java 面向对象编程 --第十二章 JDK常用类

1.  系统类 java.lang包   System类 sys.out;sys.exit;sys.gc; sys.currentTimeMillis();----得到从1970-01-01到当前时间的毫秒数,long型 sys.getProperties();&sys.getProperty();user.dir确定&得到当前工程的工作目录,String型 2.  日期类 java.util包     Date类 @Deprecated——> 方法前加上该注解,表示该方法已过时,有