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是编程中经常使用的字符串类,在上一篇博文中我 ...
随机推荐
- ssh-免密钥登陆
实现openssh免密钥登陆(公私钥验证) 在主机A上,通过root用户,使用ssh-keygen生成的两个密钥:id_rsa和id_rsa.pub 私钥(id_rsa)保存在本地主机,公钥(id_r ...
- NC212914 牛牛与后缀表达式
NC212914 牛牛与后缀表达式 题目 题目描述 给定牛牛一个后缀表达式 \(s\) ,计算它的结果,例如,1+1对应的后缀表达式为1#1#+,'#'作为操作数的结束符号. 其中,表达式中只含有'+ ...
- CF989C A Mist of Florescence 题解
因为 \(1 \leq a,b,c,d \leq 100\) 所以每一个颜色都有属于自己的联通块. 考虑 \(a = b=c=d=1\) 的情况. AAAAAAAAAAAAAAAAAAAAAAAAAA ...
- StarGAN论文及代码理解
StarGAN的引入是为了解决多领域间的转换问题的,之前的CycleGAN等只能解决两个领域之间的转换,那么对于含有C个领域转换而言,需要学习C*(C-1)个模型,但StarGAN仅需要学习一个,而且 ...
- springboot集成redis集群
1.引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId> ...
- APISpace 绕口令API接口 免费好用
绕口令又称急口令.吃口令.拗口令等.是一种民间传统的语言游戏 ,由于它是将若干双声.叠韵词或发音相同.相近的语.词有意集中在一起,组成简单.有趣的语韵,要求快速念出,所以读起来使人感到节奏感强,妙趣横 ...
- Solution -「营业」「CF567D」One-Dimensional Battle Ships
题目大意 - 翻译 Alice 和 Bob喜欢在 \(1\times n\) 的表格中玩战舰游戏.游戏开始时,Alice 有 \(k\) 艘战舰,每艘战舰长度为 \(a\),她需要把这些战舰不重叠 ...
- 使用 Abp.Zero 搭建第三方登录模块(三):网页端开发
简短回顾一下网页端的流程,总的来说网页端的职责有三: 生成一个随机字符作为鉴权会话的临时Token, 生成一个小程序码, Token作为参数固化于小程序码当中 监控整个鉴权过程状态,一旦状态变为AU ...
- 出现 Expected 0 arguments but found 1 的bug原因
问题:在给FileInputStream传入参数时报错 原以为是导错了包,结果试了几次都不行,最后才发现是项目名和这个方法名重复了,修改项目名就可以了! 红线出只是异常,抛出即可解决
- 分享一个WPF 实现 Windows 软件快捷小工具
分享一个WPF 实现 Windows 软件快捷小工具 Windows 软件快捷小工具 作者:WPFDevelopersOrg 原文链接:https://github.com/WPFDevelopers ...