JAVA - 可变对象与不可变对象

2023-11-12

不可变对象在并发程序中比较有用,由于其状态无法改变,因此无法被线程的干扰损坏或者被视为不一致状态。

基本概念   

*不可变对象(Immutable Objects)即对象一旦被创建它的状态(对象的数据,也即对象属性值)就不能改变,任何对它的改变都应该产生一个新的对象

* 可变对象(Mutable Objects):相对于不可变类,可变类创建实例后可以改变其成员变量值,开发中创建的大部分类都属于可变类

* 不可变对象的类即为不可变类(Immutable Class)。JAVA平台类库中包含许多不可变类,如String、基本类型的包装类、BigInteger和BigDecimal等。

编写不可变类

可以遵照以下几点来编写一个不可变类:

A. 确保类不能被继承将类声明为final, 或者使用静态工厂并声明构造器为private。如果类可以被继承会破坏类的不可变性机制,只要继承类覆盖父类的方法并且继承类可以改变成员变量值,那么一旦子类以父类的形式出现时,不能保证当前类是否可变。

B. 使用private和final修饰符来修饰该类的属性

注:如果成员属性为变对象属性,不要使这些对象改变:

1)不要提供更改可变对象的方法

2)不要共享对可变对象的引用,不要存储传给构造器的外部可变对象的引用。因为引用可变对象的成员变量和外部可变对象的引用指向同一块内存地址,用户可以在不可变类之外通过修改可变对象的值

public final class ImmutableDemo {  
    private final int[] myArray;  
    public ImmutableDemo(int[] array) {  
        this.myArray = array; // wrong  
    }  
}

为了保证内部的值不被修改,可以采用深度拷贝的方法来复制一个对象并传入副本的引用来确保类的不可变

public final class MyImmutableDemo {  
    private final int[] myArray;  
    public MyImmutableDemo(int[] array) {  
        this.myArray = array.clone();   
    }   
}

有必要时类中的方法返回内部可变对象的副本而不是原对象

C. 不要提供任何可以修改对象状态的方法不仅仅是set方法, 还有任何其它可以改变状态的方法

举个例子:

import java.util.Date;

public final class Planet {
    //声明为final的基本类型数据总是不可变的
    private final double fMass;
    //不可变的对象属性 (String对象不可变)
    private final String fName;
    //可变对象的属性,因为可变属性只能被这个类改变,采用深度拷贝的方法来复制一个对象并传入副本的引用
    private final Date fDateOfDiscovery;
    
    public Planet(double aMass, String aName, Date aDateOfDiscovery) {
        fMass = aMass;
        fName = aName;
        //创建aDateOfDiscovery的一个私有拷贝
        //这是保持fDateOfDiscovery属性为private的唯一方式, 并且保护这个
        //类不受调用者对于原始aDateOfDiscovery对象所做任何改变的影响
        fDateOfDiscovery = new Date(aDateOfDiscovery.getTime());
    }
    //返回一个基本类型值.
    public double getMass() {
        return fMass;
    }
    //调用者得到内部属性的一个直接引用. 由于String是不可变的所以没什么影响
    public String getName() {
        return fName;
    }
    //返回一个可变对象的一个保护性拷贝.调用者可以任意改变返回的Date对象,但是不会
    //影响类的内部.为什么? 因为它们没有fDate的一个引用. 更准确的说, 它们
    //使用的是和fDate有着相同数据的另一个Date对象
    public Date getDateOfDiscovery() {
        return new Date(fDateOfDiscovery.getTime());
    }

    public static void main(String[] args) {
    	Planet planet = new Planet(1.0D, "earth", new Date());
    	Date date = planet.getDateOfDiscovery();
    	date.setTime(111111111L);
    	System.out.println("the value of fDateOfDiscovery of internal class : " + planet.fDateOfDiscovery.getTime());
    	System.out.println("the value of date after change its value : " + date.getTime());
	}
}

运行结果:

the value of fDateOfDiscovery of internal class : 1393943752205
the value of date after change its value : 111111111

不可变对象的优缺点

优点

* 构造、测试和使用都很简单

* 不可变对象是线程安全的,在线程之间可以相互共享,不需要利用特殊机制来保证同步问题,因为对象的值无法改变。可以降低并发错误的可能性,因为不需要用一些锁机制等保证内存一致性问题也减少了同步开销。

* 不可变对象可以被重复使用,可以将它们缓存起来重复使用,就像字符串字面量和整型数字一样。可以使用静态工厂方法来提供类似于valueOf()这样的方法,它可以从缓存中返回一个已经存在的Immutable对象,而不是重新创建一个。

public class CacheImmutale {  
    private final String name;  
    private static CacheImmutale[] cache = new CacheImmutale[10];  
    private static int pos = 0;  
  
    public CacheImmutale(String name) {  
        super();  
        this.name = name;  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public static CacheImmutale valueOf(String name) {  
        // 遍历已缓存的对象  
        for (int i = 0; i < pos; i++) {  
            // 如果已有相同实例,直接返回该缓存的实例  
            if (cache[i] != null && cache[i].getName().equals(name)) {  
                return cache[i];  
            }  
        }  
        // 如果缓冲池已满  
        if (pos == 10) {  
            // 把缓存的第一个对象覆盖  
            cache[0] = new CacheImmutale(name);  
            pos = 1;  
            return cache[0];  
        } else {  
            // 把新创建的对象缓存起来,pos加1  
            cache[pos++] = new CacheImmutale(name);  
            return cache[pos - 1];  
        }  
    }  
  
    @Override  
    public int hashCode() {  
        return name.hashCode();  
    }  
  
    @Override  
    public boolean equals(Object obj) {  
        if (obj instanceof CacheImmutale) {  
            CacheImmutale ci = (CacheImmutale) obj;  
            if (name.equals(ci.getName())) {  
                return true;  
            }  
        }  
        return false;  
    }  
  
    public static void main(String[] args) {  
        CacheImmutale c1 = CacheImmutale.valueOf("hello");  
        CacheImmutale c2 = CacheImmutale.valueOf("hello");  
        System.out.println(c1 == c2);// 输出结果为true  
    }  
}

缺点:

* 不可变对象最大的缺点就是创建对象的开销,因为每一步操作都会产生一个新的对象,制造大量垃圾,由于他们不能被重用而且对于它们的使用就是”用“然后”扔“,会创造很多的垃圾,给垃圾收集带来很大的麻烦

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JAVA - 可变对象与不可变对象 的相关文章

随机推荐

  • QT-子线程访问主界面UI控件的有效方法

    引言 当软件需要导入大量数据文本时 若显示控件操作放在主界面UI线程中 会导致在导入数据的过程中存在界面卡死的现象 这种现象在工业控制工况中中是致命的 因此本文章旨在实现非UI子线程对UI主线程的操作 这里举一个简单的Demo 大文本数据通
  • 三款很酷很骚气的底部导航

    早上好 骚年 我是小菜 我的公众号 菜鸟翻身 会推荐 GitHub 上好玩的项目 一分钟 get 一个优秀的开源项目 挖掘开源的价值 欢迎关注我 底部导航栏是 APP 最常用的功能之一 想最初都是自己自定义 ViewGroup 实现 后来
  • PMBOK(第六版) PMP笔记——《第六章 项目进度管理》

    第 6 6 章 项目进度管理 项目进度管理包括为管理项目按时完成所需的各个过程 项目进度计划 Schedule 说明了项目如何以及何时交付项目范围中定义的产品 服务 和成果 创建 WBS 最底层得到的是工作包 但是为了更好的估算活动持续时间
  • SpringAOP学习--SpringAOP简介及原理

    前文对AOP做了介绍 实际项目中 一般不会直接上手手动实现aop 而是使用一些高级封装的aop实现 如SpringAOP Spring是一个广泛应用的框架 SpringAOP则是Spring提供的一个标准易用的aop框架 依托Spring的
  • vue props传Array/Object类型值,子组件报错解决办法(已解决)

    问题 Props with type Object Array must use a factory function to return the default value 1 在vue中如果当在父组件通过props传Array Obje
  • IPv6地址格式简介以及常见的IP地址

    toc 文章目录 1 IPv6地址格式 1 1首选格式 1 2 压缩格式 1 3 内嵌IPv4地址的IPv6 2 常见的IP地址 2 1 128 2 2 1 128 2 3 A B C D 2 4 FFFF A B C D 2 5 fe80
  • ElasticSearch入门学习笔记(2)--------Kibana语法学习

    ElasticSearch的ResultFul风格 基于Rest命令说明 原文链接 基础测试 创建一个索引 put 索引名 类型名 文档id 请求体 可以再head里面看到里面多了一个test1 从下面的图中可以看到本质上就是往里面put了
  • 如何在两个相关泛型类之间创建类似子类型的关系

    本文正在参加 金石计划 瓜分6万现金大奖 哈喽大家好 我是阿Q 事情是这个样子的 对话中的截图如下 看了阿Q的解释 你是否也和 马小跳 一样存在疑问呢 请往 看 我们都知道在java中 只要是类型兼容 就可以将一种类型的对象分配给另一种类型
  • vmware启动报错0xc000007b 解决方式

    1 官网下载vmwre 最新版后安装 2 运行vmwre报错结果如下 解决方法 安装最新版本C 运行库 VisualC2022 64 86 64位操作系统安装VisualC2022 64 86 32未操作系统安装VisualC2022 86
  • 【华为OD统一考试B卷

    在线OJ 已购买本专栏用户 请私信博主开通账号 在线刷题 运行出现 Runtime Error 0Aborted 请忽略 华为OD统一考试A卷 B卷 新题库说明 2023年5月份 华为官方已经将的 2022 0223Q 1 2 3 4 统一
  • c++中的vector的用法

    参考博客 https blog csdn net msdnwolaile article details 52708144 基本用法 如下代码 include
  • Flutter Image图片显示

    目录 参数详解 代码示例 效果图 完整代码 使用资源图片前必做两个步骤 1 在根目录下创建子目录 子目录中创建2 0x和3 0x 也可以创建4 0x 5 0x 但是2 0和3 0是必须的 目录 在对应目录中添加对应分辨率图片 图1 2 打开
  • 2020笔记本性价比之王_2020十大笔记本电脑性价比排行(最新笔记本电脑推荐)...

    2020十大笔记本电脑性价比排行 最新笔记本电脑推荐 1 HP 惠普 战99 15 6英寸笔记本电脑 2 Acer 宏碁 墨舞P50 15 6英寸笔记本电脑 3 HONOR 荣耀 MagicBook Pro 16 1英寸笔记本电脑 4 AS
  • C++中的Unicode编码:wchar, UTF-8,UTF-16,UTF-32

    C 在C11标准中加入了对Unicode编码的支持 新增了wchar t char16 t char32 t内置数据类型 cout lt lt sizeof char lt lt endl cout lt lt sizeof wchar t
  • 上传新文件项目到svn上

    一 在之前有svn项目的文件夹中检出 这一步主要是为了获得svn的仓库地址 二 在一个之前有的svn项目里面右键tortoiseSvn gt 版本库浏览器 就会出现这样的界面 然后在这个界面里右键里选择加入文件夹 选择本地的文件夹就可以了
  • Python剪刀石头布

    这是以前刚刚开始学习python时自己编的第一个小游戏 很简单 分享给大家 import random 胜 0 负 0 平 0 while True 对方 str random randint 1 3 我方 input 请选择你的出拳 剪刀
  • [C]编译器对char数组声明的一个行为

    1 概述 如果使用char 来声明char数组 那么编译器会自动计算后面的字面量字符数 再加上一个空字符 作为它的长度 实际上这个数组最后一位被编译器强行加上了 0 include
  • VS里面关于.cpp文件与.cu文件混合编译问题---不要在.cpp文件声明核函数(__device__()和global__())

    原文出处 点击打开链接 不要在 cpp文件声明核函数 否则会报错 具体可以参考下面两个链接 http blog csdn net lingerlanlan article details 25063331 utm source tuicoo
  • 如何自己手动搭建一个RSS订阅机器人(rssbot),自己做一个RSS阅读器

    当你想RSS订阅一些自己感兴趣的博客 却又苦于免费的RSS阅读器广告很多时 可以自己借助Telegram机器人搭建一个RSS订阅机器人 本文老王介绍下如何搭建一个Telegram RSS订阅机器人 以及如何把RSS订阅机器人拖到Telegr
  • JAVA - 可变对象与不可变对象

    不可变对象在并发程序中比较有用 由于其状态无法改变 因此无法被线程的干扰损坏或者被视为不一致状态 基本概念 不可变对象 Immutable Objects 即对象一旦被创建它的状态 对象的数据 也即对象属性值 就不能改变 任何对它的改变都应