什么是不可变类

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. 配置svn,httpd启动报错 Job for httpd.service failed because the control process exited with error code. See "systemctl status httpd.service" and "journalctl -xe" for details.

    查看httpd的状态,发现80端口被占用,因为我的nginx的80端口. systemctl status httpd.service  解决: 把Apache的端口该成别的端口 vi /etc/ht ...

  2. Linux修改默认ssh22端口

    1.检查端口是否可用 使用下面的命令检查您想增加或修改的端口号是否被占用,执行命令后如果没有任何打印,说明这个端口没有被占用 netstat -lnp|grep 23456 2.修改配置文件 ssh配 ...

  3. 更好的Android多线程下载框架

    /** * 作者:Pich * 原文链接:http://me.woblog.cn/ * QQ群:129961195 * Github:https://github.com/lifengsofts */ ...

  4. 面试突击64:了解 HTTP 协议吗?

    HTTP(Hyper Text Transfer Protocol)超文本传输协议,下文简称 HTTP,它的作用是用于实现服务器端和客户端的数据传输的.它可以传输任意的数据类型,如文本.HTML.图片 ...

  5. Tomcat日志乱码解决方法

    将配置文件logging.properties所有含有UTF-8的删除

  6. 5-2 Nacos注册中心

    Nacos注册中心 什么Nacos Nacos是Spring Cloud Alibaba提供的一个软件 这个软件主要具有注册中心和配置中心的功能 我们先学习它注册中心的功能 微服务中所有项目都必须注册 ...

  7. ooday02构造方法_this_引用类型数组

    笔记: 构造方法:构造函数.构造器.构建器---------复用给成员变量赋初值代码 作用:给成员变量赋初始值 与类同名,没有返回值类型(连void都没有) 在创建(new)对象时被自动调用 若自己不 ...

  8. 对比学习下的跨模态语义对齐是最优的吗?---自适应稀疏化注意力对齐机制 IEEE Trans. MultiMedia

    论文介绍:Unified Adaptive Relevance Distinguishable Attention Network for Image-Text Matching (统一的自适应相关性 ...

  9. 整数分解和for循环

    整数的分解: 一个整数是由多位数字组成的,那么如何能分解出整数的各个位上的数字呢 对一个整数做%10的操作,就可以得到它的个位数 对一个整数做/10的操作,就去掉了他的个位数 然后再对2的结果做%10 ...

  10. react 吸顶实现

    今天获取到一个需求,其实就是吸顶的需求,页面下滑,某一块dom隐藏时发生吸顶现象.这种特效其实老生常谈了,但是在这次做的时候,突发奇想,能否将其做成一个 hook ,从而实现出传递ref即可使得 do ...