什么是不可变类

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)类的更多相关文章

  1. Java中如何创建进程(转)

    在Java中,可以通过两种方式来创建进程,总共涉及到5个主要的类. 第一种方式是通过Runtime.exec()方法来创建一个进程,第二种方法是通过ProcessBuilder的start方法来创建进 ...

  2. Java中如何创建线程

    Java中如何创建线程 两种方式:1)继承Thread类:2)实现Runnable接口. 1.继承Thread类 继承Thread类,重写run方法,在run方法中定义需要执行的任务. class M ...

  3. java中如何创建带路径的文件

    请教各位大侠了,java中如何创建带路径的文件,说明下 这个路径不存在 ------回答--------- ------其他回答(2分)--------- Java code File f = new ...

  4. java 中操作字符串都有哪些类?(未完成)它们之间有什么区别?(未完成)

    java 中操作字符串都有哪些类?(未完成)它们之间有什么区别?(未完成)

  5. Java 中能创建 volatile 数组吗?

    能,Java 中可以创建 volatile 类型数组,不过只是一个指向数组的引用,而不 是整个数组.我的意思是,如果改变引用指向的数组,将会受到 volatile 的保护, 但是如果多个线程同时改变数 ...

  6. 什么是不可变对象(immutable object)?Java 中怎么 创建一个不可变对象?

    不可变对象指对象一旦被创建,状态就不能再改变.任何修改都会创建一个新的对象,如 String.Integer 及其它包装类. 详情参见答案,一步一步指导你在 Java中创建一个不可变的类.

  7. java中只能有一个实例的类的创建

    Java中,如果我们创建一个类,想让这个类只有一个对象,那么我们可以 1:把该类的构造方法设计为private 2:在该类中定义一个static方法,在该方法中创建对象 package test; / ...

  8. 为什么Java中字符串是不可变的

    前言 在Java中,字符串是一个不可变的类,一个不可变的类指的是它的实例对象不能被修改,所有关于这个对象的信息在这个对象被创建时已初始化且不能被改变. 不可变类有很多优势,这篇文章总结了字符串类之所以 ...

  9. 全面解释java中StringBuilder、StringBuffer、String类之间的关系

    StringBuilder.StringBuffer.String类之间的关系 java中String.StringBuffer.StringBuilder是编程中经常使用的字符串类,在上一篇博文中我 ...

随机推荐

  1. JavaScript扩展原型链浅析

    前言 上文对原型和原型链做了一些简单的概念介绍和解析,本文将浅析一些原型链的扩展. javaScript原型和原型链 http://lewyon.xyz/prototype.html 扩展原型链 使用 ...

  2. Java获取当天或者明天等零点时间(00:00:00)0时0分0秒的方法

    SimpleDateFormat sdfYMD = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Calendar calendar = ...

  3. scrapy框架入门

    scrapy迄今为止依然是世界上最好用,最稳定的爬虫框架,相比于其他直接由函数定义的程序, scrapy使用了面向对象并对网页请求的过程分成了很多个模块和阶段,实现跨模块和包的使用,大大提升了代码的稳 ...

  4. osx系统使用技巧 -- 虚拟机virtualbox

    p.p1 { margin: 0; font: 18px Menlo; color: rgba(255, 255, 255, 1); background-color: rgba(102, 130, ...

  5. .NET GC工作流程

    前言 在上文[如何获取GC的STW时间]一文中,我们聊到了如何通过监听GC发出的诊断事件来计算STW时间.里面只简单的介绍了几种GC事件和它的流程. 群里就有小伙伴在问,那么GC事件是什么时候产生的? ...

  6. 攻防世界MISC进阶区---41-45

    41.Get-the-key.txt 得到无类型文件,扔进kali中,strings一下,得到了一堆像flag的内容 扔进010 Editor中,搜索关键字,发现一堆文件,改后缀为zip 打开,直接得 ...

  7. NOI / 2.1基本算法之枚举2673:比赛排名

    总时间限制: 1000ms 内存限制: 65536kB 描述 5名运动员参加100米赛跑,各自对比赛结果进行了预测: A说:E是第1名. B说:我是第2名. C说:A肯定垫底. D说:C肯定拿不了第1 ...

  8. TFrecord写入与读取

    Protocol buffers are Google's language-neutral, platform-neutral, extensible mechanism for serializi ...

  9. 转:mysql保留关键字

    原文链接:http://www.tuicool.com/articles/Brauq2e 从网上找了一个mysql的保留字列表,仅供参考. ADD ALL ALTER ANALYZE AND AS A ...

  10. 利用Docker挂载Nginx-rtmp(服务器直播流分发)+FFmpeg(推流)+Vue.js结合Video.js(播放器流播放)来实现实时网络直播

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_75 众所周知,在视频直播领域,有不同的商家提供各种的商业解决方案,其中比较靠谱的服务商有阿里云直播,腾讯云直播,以及又拍云和网易云 ...