一、Java中的不可变类

不可变类(Immutable Objects):当类的实例一经创建,其内容便不可改变,即无法修改其成员变量。

可变类(Mutable Objects):类的实例创建后,可以修改其内容。

Java 中八个基本类型的包装类和 String 类都属于不可变类,而其他的大多数类都属于可变类。

二、与引用不可变的区别

需要特别注意的是,不可变类的不可变是指该类的实例不可变而非指向该实例的引用的不可变。

String s = "abc";
System.out.println("s:" + s); // 输出s:abc
s = "xyz";
System.out.println("s:" + s); // 输出s:xyz

以上代码显示,不可变类 String 貌似是可以改变值的,但实际上并不是。变量 s 只是一个指向 String 类的实例的引用,存储的是实例对象在内存中的地址。代码中第三行的 “改变” 实际上是新实例化了一个 String 对象,并将 s 的指向修改到新对象上,而原来的对象在内存中并未发生变化,只是少了一个指向它的引用,并且在未来被垃圾回收前它都将保持不变。

public class Immutable {

    public static void main(String[] args) {
String str = new String("abc");
String str2 = str;
System.out.println(str == str2); // true
str2 = "cba";
System.out.println(str == str2); // false System.out.println(str == row(str)); // true
System.out.println(str == other(str)); // false
} static private String row(String s){
return s;
}
static private String other(String s){
s="xyz"; //此处形参 s 指向了新的String对象,引用的地址发生变化
return s;
}
}

如此我们看到,对于不可变类的对象,都是通过新创建一个对象并将引用指向新对象来实现变化的。

通常,使用关键字 final 修饰的字段初始化后是不可变的,而这种不可变就是指引用的不可变。具体就是该引用所指对象的内存地址是不可变的,但并非该对象不可变。如果该对象也不可变,那么该对象就是不可变类的一个实例。

public class Immutable {

    public static void main(String[] args) {
Immutable immutable = new Immutable();
final Inner inner = immutable.new Inner();
inner.value = 123; // 实例可变
// 下面语句编译错误,inner 是final的,无法让它指向新的对象(改变指向地址)
// inner = it.new Inner();
Inner inner2 = inner; // 复制了一份引用,inner和inner2指向同一个对象
System.out.println(inner); // 将调用 toString 方法输出对象内存地址
System.out.println(inner2); // inner和inner2具有相同的地址
System.out.println(inner.value); // 输出 123
System.out.println(inner2.value); // 输出123
inner2.value = 321;
System.out.println(inner); // 输出321
} class Inner{
private int value;
}
}

三、不可变类是如何实现的

immutable对象的状态在创建之后就不能发生改变,任何对它的改变都应该产生一个新的对象。

因此,一个不可变类的定义应当具备以下特征:

  1. 所有成员都是 private final 的
  2. 不提供对成员的改变方法,例如:setXXXX
  3. 确保所有的方法不会被重载。手段有两种:使用final Class(强不可变类),或者将所有类方法加上final(弱不可变类)。
  4. 如果某一个类成员不是基本类型(primitive type)或不可变类,必须通过在成员初始化(in)或者getter方法(out)时通过深度拷贝(即复制一个该类的新实例而非引用)方法,来确保类的不可变。
  5. 如果有必要,重写hashCode和equals方法,同时应保证两个用equals方法判断为相等的对象,其hashCode也应相等。

下面是一个示例:

public final class ImmutableDemo {
private final int[] myArray;
public ImmutableDemo(int[] array) {
// this.myArray = array; // 错误!
this.myArray = array.clone(); // 正确
}
public int[] get(){
return myArray.clone();
}
}

上例中错误的方法不能保证不可变性,myArray 和形参 array 指向同一块内存地址,用户可以在 ImmutableDemo 实例之外通过修改 array 对象的值来改变实例内部 myArray 的值。正确的做法是通过深拷贝将 array 的值传递给 myArray 。同样, getter 方法中不能直接返回对象本身,而应该是克隆对象并返回对象的拷贝,这种做法避免了对象外泄,防止通过 getter 获得内部可变成员对象后对成员变量直接操作,导致成员变量发生改变。

对于不可变类,String 是一个典型例子,看看它的源码也有助于我们设计不可变类。

四、不可变类的优点

不可变类有两个主要有点,效率和安全。

  • 效率

    当一个对象是不可变的,那么需要拷贝这个对象的内容时,就不用复制它的本身而只是复制它的地址,复制地址(通常一个指针的大小)只需要很小的内存空间,具有非常高的效率。同时,对于引用该对象的其他变量也不会造成影响。

    此外,不变性保证了hashCode 的唯一性,因此可以放心地进行缓存而不必每次重新计算新的哈希码。而哈希码被频繁地使用, 比如在hashMap 等容器中。将hashCode 缓存可以提高以不变类实例为key的容器的性能。

  • 线程安全

    在多线程情况下,一个可变对象的值很可能被其他进程改变,这样会造成不可预期的结果,而使用不可变对象就可以避免这种情况同时省去了同步加锁等过程,因此不可变类是线程安全的。

当然,不可变类也有缺点:不可变类的每一次“改变”都会产生新的对象,因此在使用中不可避免的会产生很多垃圾。

更多关于深度拷贝的知识,参阅这篇:java对象深复制、浅复制(深拷贝、浅拷贝)的理解

Java中的不可变类理解的更多相关文章

  1. Java中的不可变类

    概念:不可变类的意思是创建该类的实例后,该实例的属性是不可改变的.java中的8个包装类和String类都是不可变类.所以不可变类并不是指该类是被final修饰的,而是指该类的属性是被final修饰的 ...

  2. 《Java中的不可变类》

    //不可变类举例: /* 下面程序试图定义一个不可变类Person类,但=因为Person类包含一个引用类型的成员变量, 且这个引用类是可变类,所以导致Person类也变成了可变类. */ class ...

  3. 关于java中的不可变类(转)

    如何在Java中写出Immutable的类? 要写出这样的类,需要遵循以下几个原则: 1)immutable对象的状态在创建之后就不能发生改变,任何对它的改变都应该产生一个新的对象. 2)Immuta ...

  4. 深入理解Java中的不可变对象

    深入理解Java中的不可变对象 不可变对象想必大部分朋友都不陌生,大家在平时写代码的过程中100%会使用到不可变对象,比如最常见的String对象.包装器对象等,那么到底为何Java语言要这么设计,真 ...

  5. 为什么 String 在 Java 中是不可变的?

    我最喜欢的 Java 面试问题,很棘手,但同时也非常有用.一些面试者也常问这个问题,为什么 String 在 Java 中是 final 的.字符串在 Java 中是不可变的,因为 String 对象 ...

  6. java中容器的学习与理解

    以前一直对于java中容器的概念不理解,虽然学习过,但始终没有认真理解过,这几天老师提出了这样一个问题,你怎么理解java中的容器.瞬间就蒙了.于是各种搜资料学习了一下,下面是我学习后整理出来的的一些 ...

  7. 为什么 String 在 Java 中是不可变的(终极答案)

    为什么 String 在 Java 中是不可变的(终极答案) 我们可以从2个角度去看待这个问题: 1.为什么要设计成不可变2.如何保证不可变? 1.为什么设计不可变? 1.String对象缓存在Str ...

  8. JAVA中封装JSONUtils工具类及使用

    在JAVA中用json-lib-2.3-jdk15.jar包中提供了JSONObject和JSONArray基类,用于JSON的序列化和反序列化的操作.但是我们更习惯将其进一步封装,达到更好的重用. ...

  9. Java中直接输出一个类的对象

    例如 package com.atguigu.java.fanshe; public class Person { String name; private int age; public Strin ...

随机推荐

  1. 一: WCF的服务端与客户端在通信时有三种模式:请求响应模式、数据报模式和双工通讯模式。

    说一下基本知识,  1.如果想要将当前接口作为wcf服务器,则一定要加上[ServiceContract] 契约 2.要想将方法作为wcf服务方法发布给外部调用,则一定要加上    [Operatio ...

  2. C# Upload

    using System; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Web; ...

  3. JavaScript基础笔记(十四)最佳实践

    最佳实践 一)松散耦合 1.解耦HTML/JavaScript: 1)避免html种使用js 2)避免js种创建html 2.解耦CSS/JS 操作类 3.解耦应用逻辑和事件处理 以下是要牢记的应用和 ...

  4. [CC-MCO16306]Fluffy and Alternating Subsequence

    [CC-MCO16306]Fluffy and Alternating Subsequence 题目大意: 给定一个\(1\sim n(n\le3\times10^5)\)的排列\(a\). 对于一个 ...

  5. Android 中的设计模式

    1.单例模式 ContentProvider是单例模式,多个ContentResolver操作的都是同一个ContentProvider.

  6. 【最大公约数&链表】权值 @upcexam5921

    时间限制: 1 Sec 内存限制: 512 MB 题目描述 给定一个长为n的正整数序列Ai.对于它的任意一个连续的子序列{Al, Al+1, …, Ar},定义其权值W (l, r)为其长度与序列中所 ...

  7. JAVA自学笔记19

    JAVA自学笔记19 1.集合总结 Collection(单列集合) List(有序可重复) ArrayList:底层数据结构是数组 ,查询快,增删慢.线程不安全,效率高 Vector:底层数据结构是 ...

  8. openstack 之~keystone基础

    第一:keystone是什么? keystone是 OpenStack Identity Service 的项目名称,是一个负责身份管理验证.服务规则管理和服务令牌功能.它实现了openstack的i ...

  9. linux下的EDA——VCS使用

    原帖地址:https://blog.csdn.net/moon9999/article/details/75283926 在Linux下对verilogHDL进行功能仿真时非常必要的,下面提供两种常见 ...

  10. Spring Boot中使用Swagger2自动构建API文档

    由于Spring Boot能够快速开发.便捷部署等特性,相信有很大一部分Spring Boot的用户会用来构建RESTful API.而我们构建RESTful API的目的通常都是由于多终端的原因,这 ...