什么是不可变类

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. 排名前三——python 开源 IDE

    写在前面的一些P话: Python无处不在 ,似乎它支持从主要网站到桌面实用程序到企业软件的所有功能. Python已经被用来编写流行的软件项目,如dnf / yum,OpenStack,OpenSh ...

  2. 虚拟机使用docker 外部机器无法访问端口问题

    1,排查防火墙firewall-cmd --state 如果输出的是"not running"则FirewallD没有在运行,且所有的防护策略都没有启动,那么可以排除防火墙阻断连接 ...

  3. Effective C++阅读笔记 较详细 复杂条款带样例

    一.让自己习惯C++ 条款01:视C++为一个语言联邦 C++可视为: C:以C为基础. 面向对象的C++:添加面向对象特性. 模板C++:泛型编程概念,使用模板. STL:使用STL的容器.迭代器. ...

  4. [BJDCTF2020]EasySearch-1

    1.打开之后界面如下: 2.在首界面审查源代码.抓包未获取到有效信息,就开始进行目录扫描,获取到index.php.swp文件,结果如下: 3.访问index.php.swp文件获取源代码信息,结果如 ...

  5. 我也是醉了,Eureka 延迟注册还有这个坑!

    Eureka 有个延迟注册的功能,也就是在服务启动成功之后不立刻注册到 Eureka Server,而是延迟一段时间再去注册,这样做的主要目的是因为虽然服务启动成功了,可能还有一些框架或者业务的代码没 ...

  6. python socket理解

    socket 所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象.一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制.从所处的地位来讲 ...

  7. 调和级数为什么是 O(logn) 的

    目录 调和级数 正片 调和级数 调和级数(Harmonic series)定义为 \[H(n)=\sum_{i=1}^n\dfrac 1i \] \(H\) 发散,证明看百度 . 正片 首先我们把 \ ...

  8. 【Azure 应用服务】部署Kafka Trigger Function到Azure Function服务中,解决自定义域名解析难题

    问题描述 经过前两篇文章,分别使用VM搭建了Kafka服务,创建了Azure Function项目,并且都在本地运行成功. [Azure Developer]在Azure VM (Windows) 中 ...

  9. Java中break、continue和return三者之间的区别

    控制流程语句 java关键字break.continue.return主要按三个纬度去区分. 作用不同 结束不同 紧跟不同  一.作用不同 1.break:执行break操作,跳出所在的当前整个循环, ...

  10. Apache DolphinScheduler 架构演进介绍及开源经验分享 - eBay 阮文俊

    引言 来自 eBay 的文俊同学在近期的上海开源大数据 Meetup 上做了十分精彩的 "Apache DolphinScheduler 的架构演进" 分享.本次分享有近 200 ...