Java中如何创建不可变(immutable)类
什么是不可变类
1. 不可变类是指类的实例一经创建完成,这个实例的内容就不会改变。
2. Java中的String和八个基本类型的包装类(Integer, Short, Byte, Long, Double, Float,Boolean,Char)都是不可变类
3.不可变类 vs 不可变变量:
二者是不一样的。
不可变类是指类的实例内容不会改变,考虑如下代码:
1 String s = "ABC";
2 s = "BCD"
3 System.out.println("s:"+s);
4 //output s:BCD
在line 2中我们对s变量进行了再次赋值,实际上是又创建了一个值为"BCD"的String 对象,并将s指向它。变化的是s指向的内存地址(或者简单的叫指针),值为"ABC" 与值为"BCD"的两个String 对象是没有变的。
不可变变量是用Final关键字修饰的变量,考虑如下代码:
1 final String s = "ABC";
2 s = "BCD" //此行报错,不能给final变量赋值
3 System.out.println("s:"+s);
我们将s变量用final关键字修饰,这时在s被初始化之后,就无法在line 2 再次给它赋值了,也就是说我们没办法改变final变量指向的内存地址。
如何实现一个不可变类
1. 所有的成员变量声明为private final,防止初始化后被修改
2. 类声明为final,禁止继承,其实是防止类中的方法被重写
3. 不为成员变量提供setter方法
4. 如果类中包含可变对象,比如一个成员变量是数组,或者其他可变类,那么要有如下操作:
1)在构造方法中,如果构造方法会传入可变对象,我们要使用这个对象的copy来初始化我们的成员变量,而不是直接使用传入的对象。因为传入的是指针,传入的对象在外面可能会被修改,如果直接引用的话会导致我们的成员变量也间接被修改。
2) 在返回这些可变对象的getter方法中,返回对象的copy,而不是直接返回该对象(或者叫该对象的引用/指针)
实例:
1 package org.adeline.learning;
2
3 import java.util.Arrays;
4
5 public final class Immutable {
6 private final int vInt;
7 private final String vStr;
8 private final int[] vArr;
9
10 public Immutable(int vInt, String vStr, int[] vArr) {
11 this.vInt = vInt;
12 this.vStr = vStr;
13 //this.vArr = vArr; //不正确
14 this.vArr = vArr.clone();//使用传入数组的copy初始化
15 }
16
17 public int[] getVArr() {
18 //return vArr; //不正确
19 return vArr.clone(); //返回数组的copy
20 }
21
22 public static void main(String[] args) {
23 int[] arr = new int[]{3,4};
24 Immutable im = new Immutable(1,"2", arr);
25 int[] arr1 = im.getVArr();
26 Arrays.stream(arr1).forEach((e) -> {System.out.println(e);});
27 arr[0] = 33;
28 arr[1] = 44;
29 Arrays.stream(arr1).forEach((e) -> {System.out.println(e);});
30 }
31
32 }
下面探讨一下为何类也需要声明为final. 考虑如下代码:
1 public class ImmutableChild extends Immutable{
2 private int[] vArr;
3 public ImmutableChild(int vInt, String vStr, int[] vArr) {
4 super(vInt, vStr, vArr);
5 this.vArr = vArr;
6 }
7 @Override
8 public int[] getVArr() { //父类中的方法被重写,返回的是子类中的vArr
9 return vArr;
10 }
11
12 public static void main(String[] args) {
13 Immutable imNG = new ImmutableChild(1,"2", new int[]{3,4});
14 imNG.getVArr()[0] = 33;
15 imNG.getVArr()[1] = 44;
16 Arrays.stream(imNG.getVArr()).forEach(e -> {System.out.println(e);});//output 33,44
17 }
18 }
我们把上面Immutable 类的final 声明去掉,ImmutableChild继承了Immutable类,重写了getVArr方法,返回自己的成员变量数组vArr,而这个子类里面的vArr是可变的,在main方法里面初始化时我们给其赋值{3,4},可以看到后面我们改成了{33,44}.
在使用中,任何一个接受Immutable实例的地方都可以接受其子类ImmutableChild实例,并将它作为一个不可变的实例来操作,而实际上它是可变的,这样就有可能出错。
所以把不可变类声明为final是为了防止恶意继承,或者继承中考虑不周密导致的问题。
不可变类的优点与用途
1. 线程安全,省去了加锁的过程,因为对象内容不可变就不用担心线程同时对对象做修改
2. 拷贝效率高。当类不可变时, 拷贝其对象只需拷贝指针即可,而不用拷贝对象本身,不用担心会被修改
3. 可以作为HashMap的key,类不可变保证了Hashcode的稳定性。
当然,也要注意不可变类在使用过程中可能出现的内存浪费问题,比如大家都知道的最好不要用许多"+"连接String
Java中如何创建不可变(immutable)类的更多相关文章
- Java中如何创建进程(转)
在Java中,可以通过两种方式来创建进程,总共涉及到5个主要的类. 第一种方式是通过Runtime.exec()方法来创建一个进程,第二种方法是通过ProcessBuilder的start方法来创建进 ...
- Java中如何创建线程
Java中如何创建线程 两种方式:1)继承Thread类:2)实现Runnable接口. 1.继承Thread类 继承Thread类,重写run方法,在run方法中定义需要执行的任务. class M ...
- java中如何创建带路径的文件
请教各位大侠了,java中如何创建带路径的文件,说明下 这个路径不存在 ------回答--------- ------其他回答(2分)--------- Java code File f = new ...
- java 中操作字符串都有哪些类?(未完成)它们之间有什么区别?(未完成)
java 中操作字符串都有哪些类?(未完成)它们之间有什么区别?(未完成)
- Java 中能创建 volatile 数组吗?
能,Java 中可以创建 volatile 类型数组,不过只是一个指向数组的引用,而不 是整个数组.我的意思是,如果改变引用指向的数组,将会受到 volatile 的保护, 但是如果多个线程同时改变数 ...
- 什么是不可变对象(immutable object)?Java 中怎么 创建一个不可变对象?
不可变对象指对象一旦被创建,状态就不能再改变.任何修改都会创建一个新的对象,如 String.Integer 及其它包装类. 详情参见答案,一步一步指导你在 Java中创建一个不可变的类.
- java中只能有一个实例的类的创建
Java中,如果我们创建一个类,想让这个类只有一个对象,那么我们可以 1:把该类的构造方法设计为private 2:在该类中定义一个static方法,在该方法中创建对象 package test; / ...
- 为什么Java中字符串是不可变的
前言 在Java中,字符串是一个不可变的类,一个不可变的类指的是它的实例对象不能被修改,所有关于这个对象的信息在这个对象被创建时已初始化且不能被改变. 不可变类有很多优势,这篇文章总结了字符串类之所以 ...
- 全面解释java中StringBuilder、StringBuffer、String类之间的关系
StringBuilder.StringBuffer.String类之间的关系 java中String.StringBuffer.StringBuilder是编程中经常使用的字符串类,在上一篇博文中我 ...
随机推荐
- sql-关键词的大小写与注释
是否区分大小写 和 注释 大小写 oracle 自带的sqlplus: mysql 客户端 : Navicat: 注释 oracle 自带的sqlplus: mysql 客户端 : 小节 oracle ...
- Java 图片生成PDF
public static void main(String[] args) { String imageFolderPath = "E:\\Tencet\\图片\\test\\" ...
- 如何将 DevSecOps 引入企业?
前 言 现如今,大部分企业已经在内部实现了 DevOps 实践.DevOps 为团队提供了交付可靠软件和快速更新的方法论.这种方法让团队更专注于质量而不是将时间浪费在运维上.然而,结果是,安全实践往往 ...
- Tapdata 等40余家行业知名企业,应邀参与共建 NextArch Foundation
日前,Linux 基金会执行董事 Jim Zemlin 于 Linux 基金会会员峰会(The Linux Foundation Member Summit)上宣布,Linux 基金会正式成立 N ...
- RS485 MODBUS RTU通信协议
1.RS485接口标准 RS485由RS232和RS422发展而来,弥补了抗干扰能力差.通信距离短.速率低的缺点,增加了多点.双向通信能力,即允许多个发送器连接在同一条主线上,同时增加了发送器的驱动能 ...
- csdn 不可复制代码的解决方法
简介 由于csdn需要登陆才可以复制,需要脚本才可以操作 脚本 直接在console控制台输入以下代码即可直接复制 $("#content_views pre").css(&quo ...
- 一文搞定Vue2组件通信
vue 组件通信方式 父组件将自己的状态分享给子组件使用: 方法:父组件通过子标签传递数据,子组件通过 props 接收 子组件改变父组件的状态; 方法:父组件在子标签上通过@abc 提供一个改变自身 ...
- 有一种密码学专用语言叫做ASN.1
目录 简介 ASN.1的例子 ASN.1中的内置类型 ASN.1中的限制语法 总结 简介 ASN.1是一种跨平台的数据序列化的接口描述语言.可能很多人没有听说过ASN.1, 但是相信有过跨平台编程经验 ...
- MYSQL常见可优化场景
1.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null 可以在num上设置 ...
- Apache DolphinScheduler 使用文档(5/8):使用与测试
本文章经授权转载,原文链接: https://blog.csdn.net/MiaoSO/article/details/104770720 目录 5. 使用与测试 5.1 安全中心(Security) ...