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是编程中经常使用的字符串类,在上一篇博文中我 ...
 
随机推荐
- 单片机 MCU 固件打包脚本软件
			
 1 前言 开发完 MCU 软件后,通常都会生成 hex 文件或者 bin 文件,用来做固件烧录或者升级,如果用来做产品开发,就涉及到固件版本的问题,初学者通常采用固件文件重命名来区分版本. 如果需 ...
 - (数据库提权——Redis)Redis未授权访问漏洞总结
			
一.介绍 1.Redis数据库 Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key- ...
 - 《A Neural Algorithm of Artistic Style》理解
			
在美术中,特别是绘画,人类掌握了通过在图像的内容和风格间建立复杂的相互作用从而创造独特的视觉体验的技巧.到目前为止,这个过程的算法基础是未知的,也没有现存的人工系统拥有这样的能力.然而在视觉感知的其他 ...
 - idea  java 打包的方法
			
方法1: 在pom.xml 里面加上maven打包的配置 <plugin> <groupId>org.springframework.cloud</groupId> ...
 - lombok的常用注解
			
出处: https://blog.csdn.net/sunnyzyq/article/details/119992746 1. @Accessors 源码 我们打开 @Accessors 的源码可以看 ...
 - Codeforces Round #768 (Div. 2) D. Range and Partition // 思维 + 贪心 + 二分查找
			
The link to problem:Problem - D - Codeforces D. Range and Partition time limit per test: 2 second ...
 - 从零开始在centos搭建博客(一)
			
本篇为安装篇. 基于centos 7.9,大部分东西使用docker安装. 软件列表:docker + mysql + wordpress 安装docker yum install -y yum-ut ...
 - python 日志类
			
简介 在所有项目中必不可少的一定是日志记录系统,python为我们提供了一个比较方便的日志模块logging,通常,我们都会基于此模块编写一个日志记录类,方便将项目中的日志记录到文件中. loggin ...
 - Python 汽车之家 全系车型参数(包含历史停售车型) 最全
			
本文仅供学习交流使用,如侵立删!联系方式及demo下载见文末 汽车之家2021 全系车型参数(包含历史停售车型) 2021.10.21更新 增加参数:电动扰流板.无框设计车门.隐藏电动门把手.自动驾驶 ...
 - 那些舍不得删除的 MP3--批量修改mp3的ID3tag
			
整理电脑时发现很多mp3.那是大约2001年至2009年之间.那个时候大家听歌,还是习惯从网上下载mp3.虽然现在听歌比从前方便多了,简单到只需在APP中输入歌名,但用播放器听mp3的感觉是完全不同的 ...