不可变类

2023-05-16


不可变类

先来科普2个概念,可变类和不可变类。

1),不可变类的意思就是创建该类的实例后,该实例的实例变量是不可改变的。Java提供的8个包装类和String类都是不可变类,当创建他们的实例后,其实例的实例变量是不

可改变的。

2),与不可变类对应的是可变类,可变类的含义是该类的实例变量是可变的。大部分时候所创建的类都是可变类,特别是JavaBean,因为总是在其实例变量提供了setter和

getter方法。

看下面的代码:

Double d = new Double(6.5);
		String linkin = "LinkinPark";
上面的程序创建了一个Double对象和一个String对象,并为这两个对象传入了6.5和"LinkinPark"字符串作为参数,那么Double类和String类肯定需要提供实例变量来保存这两个

参数,但程序无法修改这两个实例变量的值,因此Double类和String类没有提供修改它们的方法。


如果需要创建自定义的不可变类,要遵守以下的规则:

1),使用private和final修饰该类的成员变量

2),提供带参数的构造器,用于根据传入参数来初始化该类的成员变量

3),仅为该类提供getter方法,不要提供setter方法,因为普通的方法不能改变这个类的属性

4),如果有必要,重写equals和hashcode方法。

/**
 * 不可变类
 * 
 * @author LinkinPark
 * 
 *         <pre>
 *         1,属性使用private final修饰
 *         2,构造器对属性赋值
 *         3,只提供get方法,不提供set方法
 *         4,如果有需要就重写equals和hashCode方法
 *         </pre>
 */
public class LinkinPark
{

	private final String name;
	private final Integer age;

	public LinkinPark(String name, Integer age)
	{
		super();
		this.name = name;
		this.age = age;
	}

	public String getName()
	{
		return name;
	}

	public Integer getAge()
	{
		return age;
	}

	public static void main(String[] args)
	{
		new LinkinPark("LinkinPark", 25);
	}

}

与可变类相比,不可变类的实例在整个生命周期中永远出于初始化阶段,它的实例变量不可改变,因此对不可变类的实例的控制将更加简单。

前面介绍final关键字时提到,当使用final修饰引用类型变量时,仅表示这个引用类型变量不可被重新赋值,但引用类型变量所指向的对象依然可以改变。

这就产生了一个问题,当创建一个不可变类时,如果它包含成员变量的类型是可变的,那么其对象的成员变量的值依然是可变的,那这个不可变类其实是失败的

看下面的例子:

/**
 * 引用类型的变量导致不可变类失败
 * 
 * @author LinkinPark
 */
public class LinkinPark
{

	private final String name;
	private final Linkin linkin;

	public LinkinPark(String name, Linkin linkin)
	{
		super();
		this.name = name;
		this.linkin = linkin;
	}

	public String getName()
	{
		return name;
	}

	public Linkin getLinkin()
	{
		return linkin;
	}

	@Override
	public String toString()
	{
		return getClass().getSimpleName() + " [name=" + name + ", linkin=" + linkin + "]";
	}

	public static void main(String[] args)
	{
		Linkin linkin = new Linkin();
		linkin.setName("NightWish1");
		linkin.setAge(25);
		LinkinPark linkinPark = new LinkinPark("LinkinPark", linkin);
		System.out.println(linkinPark);

		linkin.setAge(24);
		linkin.setName("NightWish2");
		System.out.println(linkinPark);
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish1, age=25]]
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish2, age=24]]
	}

}

class Linkin
{
	private String name;
	private Integer age;

	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	public Integer getAge()
	{
		return age;
	}

	public void setAge(Integer age)
	{
		this.age = age;
	}

	@Override
	public String toString()
	{
		return getClass().getSimpleName() + " [name=" + name + ", age=" + age + "]";
	}

}
运行上面的代码,我们也看到了,引用类型的变量导致我创建了一个失败的不可变类。那应该要怎么做呢?看下面代码:

/**
 * 引用类型的变量导致不可变类失败
 * 所以要针对引用类型的变量做专门的处理
 * 
 * <pre>
 * 1,构造器中不要直接使用传入的引用类型变量,自己取值然后重新new一次
 * 2,引用类型的变量用来存储刚才那个初始化的对象
 * 3,防止get方法直接返回刚才那个变量从而改变引用的那个对象,同样的方式处理
 * </pre>
 * 
 * @author LinkinPark
 */
public class LinkinPark
{

	private final String name;
	private final Linkin linkin;

	/**
	 * @param name
	 * @param linkin
	 */
	public LinkinPark(String name, Linkin linkin)
	{
		super();
		this.name = name;
		this.linkin = new Linkin(linkin.getName(), linkin.getAge());
	}

	public String getName()
	{
		return name;
	}

	public Linkin getLinkin()
	{
		return new Linkin(linkin.getName(), linkin.getAge());
	}

	@Override
	public String toString()
	{
		return getClass().getSimpleName() + " [name=" + name + ", linkin=" + linkin + "]";
	}

	public static void main(String[] args)
	{
		Linkin linkin = new Linkin("NightWish1", 25);
		LinkinPark linkinPark = new LinkinPark("LinkinPark", linkin);
		System.out.println(linkinPark);

		linkin.setAge(24);
		linkin.setName("NightWish2");
		System.out.println(linkinPark);
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish1, age=25]]
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish2, age=24]]
	}

}

class Linkin
{
	private String name;
	private Integer age;

	public Linkin(String name, Integer age)
	{
		super();
		this.name = name;
		this.age = age;
	}

	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	public Integer getAge()
	{
		return age;
	}

	public void setAge(Integer age)
	{
		this.age = age;
	}

	@Override
	public String toString()
	{
		return getClass().getSimpleName() + " [name=" + name + ", age=" + age + "]";
	}

}


缓存实例的不可变类

不可变类的实例状态不可改变,可以很方便的被多个对象所共享。如果程序经常需要使用相同的不可变实例,则应该考虑缓存这种不可变类的实例。毕竟重复创建相同的对象没

有太大的意义,而且加大系统开销。如果可能,应该将已经创建的不可变类的实例进行缓存。

缓存是软件设计中一个非常有用的模式,缓存的实现方式也有很多种,不同的实现方式可能存在较大的性能差别,关于缓存的性能问题和实现方式我会在后面的博客中整理一个

分类,此处不做赘述。

OK,前面我已经使用了不可变类LinkinPark,现在我自己用一个数组写一个缓存池,从而实现一个缓存LinkinPark实例的缓存池

当然也可以直接在LinkinPark类中写缓存,这样子将实现一个缓存自己实例的不可变类。

public class LinkinParkCache
{
	// 定义一个数组+一个下标+数组最大容量
	private static int POS_INDEX = 0;
	private static final int MAX_SIZE = 10;
	private static final LinkinPark[] cache = new LinkinPark[MAX_SIZE];

	// 定义一个name标示用来重写hashCode方法
	private final String name;

	private LinkinParkCache(String name)
	{
		this.name = name;
	}

	public String getName()
	{
		return name;
	}

	public static LinkinPark valueOf(String name)
	{
		// 1,循环获取缓存的实例
		for (int i = 0; i < cache.length; i++)
		{
			if (cache[i] != null && cache[i].getName().equals(name))
			{
				return cache[i];
			}
		}
		// 2,循环结束后没有找见实例,则向缓存中添加
		if (POS_INDEX == MAX_SIZE)
		{
			cache[0] = new LinkinPark(name, new Linkin("LinkinPark", 25));
			POS_INDEX = 1;
		}
		else
		{
			cache[POS_INDEX++] = new LinkinPark(name, new Linkin("LinkinPark", 25));
		}
		return cache[POS_INDEX - 1];
	}

	@Override
	public boolean equals(Object obj)
	{
		if (this == obj)
		{
			return true;
		}
		if (obj != null && obj.getClass() == this.getClass())
		{
			LinkinPark linkinPark = (LinkinPark) obj;
			return linkinPark.getName().equals(this.getName());
		}
		return false;
	}

	@Override
	public int hashCode()
	{
		return name.hashCode();
	}

	public static void main(String[] args)
	{
		LinkinPark linkin = LinkinParkCache.valueOf("林肯的缓存池");
		LinkinPark linkinPark = LinkinParkCache.valueOf("林肯的缓存池");
		// 下面代码输出true,使用了缓存
		System.out.println(linkin == linkinPark);
	}

}

/**
 * 不可变类
 * 
 * @author LinkinPark
 */
class LinkinPark
{

	private final String name;
	private final Linkin linkin;

	public LinkinPark(String name, Linkin linkin)
	{
		super();
		this.name = name;
		this.linkin = new Linkin(linkin.getName(), linkin.getAge());
	}

	public String getName()
	{
		return name;
	}

	public Linkin getLinkin()
	{
		return new Linkin(linkin.getName(), linkin.getAge());
	}

	@Override
	public String toString()
	{
		return getClass().getSimpleName() + " [name=" + name + ", linkin=" + linkin + "]";
	}

	public static void main(String[] args)
	{
		Linkin linkin = new Linkin();
		linkin.setName("NightWish1");
		linkin.setAge(25);
		LinkinPark linkinPark = new LinkinPark("LinkinPark", linkin);
		System.out.println(linkinPark);

		linkin.setAge(24);
		linkin.setName("NightWish2");
		System.out.println(linkinPark);
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish1, age=25]]
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish1, age=25]]
	}

}

/**
 * 不可变类中的引用类型变量的定义
 * 
 * @author LinkinPark
 */
class Linkin
{
	private String name;
	private Integer age;

	public Linkin()
	{
		super();
	}

	public Linkin(String name, Integer age)
	{
		super();
		this.name = name;
		this.age = age;
	}

	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	public Integer getAge()
	{
		return age;
	}

	public void setAge(Integer age)
	{
		this.age = age;
	}

	@Override
	public String toString()
	{
		return getClass().getSimpleName() + " [name=" + name + ", age=" + age + "]";
	}

}


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

不可变类 的相关文章

  • 在linux终端命令行显示本机IP

    在linux命令行显示本机IP vim etc profile 在最后增加下边语句 xff0c 网卡ens160 根据实际情况设置 有ip addr 命令的情况 span class token assign left variable I
  • 解决apt-get安装中的E: Sub-process /usr/bin/dpkg returned an error code (1)问题

    在用apt get安装软件包的时候遇到E Sub process usr bin dpkg returned an error code 1 问题 xff0c 解决方法如下 cd var lib dpkg sudo mv info info
  • mac 初始化工具

    mac 初始化工具 安装iterm2 xff1a https iterm2 com 安装命令行工具 xcode select install 安装brew bin bash c 34 span class token variable sp
  • odoo 达梦数据库SQL适配

    计算两个时间相差的天数 psql xff1a DATE PART 39 day 39 s date order timestamp s create date timestamp 达梦 xff1a DAYS BETWEEN s date o
  • 使用 rsync 将文件复制到 Docker 容器中的方法

    使用 rsync 将文件复制到 Docker 容器中的方法 确保您的 Docker 容器已安装 rsync xff0c 并定义此别名 xff1a span class token builtin class name alias span
  • 用rm递归递归删除子目录下所有“.”开头的隐藏文件

    find name 34 34 xargs rm 参考网址 xff1a http blog 163 com sweet hard blog static 66656838201162294812840 可以通过管道命令来操作 xff0c 先
  • 多线程压缩与解压缩

    Linux 下用得最普遍的两种压缩文件格式就是 gz 和 bz2 了 xff0c 分别由 gzip 和 bzip2 命令创建 说到压缩文件 xff0c 除了压缩率之外 xff0c 压缩和解压的速度也很关键 xff0c 在创建或解压比较大的压
  • 设置CPU频率和CPU运行核心数

    1 查看当前的CPU信息 span class token function cat span proc cpuinfo ums312 1h10 span class token comment cat proc cpuinfo span
  • 二叉搜索树的第K大节点

    二叉搜索树的第K大节点 第K大的节点就是中序遍历中的第K个节点 span class token keyword class span span class token class name Solution span span class
  • 设置eMMC和DDR的工作频率

    1 MTK 平台查看eMMC和DDR的工作频率 eMMC xff1a adb shell span class token function cat span sys kernel debug mmc0 clock DDR xff1a ad
  • 命令设置Android手机不进入休眠

    在Linux系统中 xff0c wake lock是一直锁机制 xff0c 只要有驱动占用这个锁 xff0c 系统就不会进入深度休眠 获取此锁的方式如下 xff1a adb shell span class token keyword ec
  • Kconfig文件的用途及解析

    1 Kconfig文件的作用 首先 xff0c 内核编译代码的大概过程如下 xff1a 遍历每个源码目录Makefile 61 gt 根据每个目录的Kconfig来配置Makefile xff0c 定制要编译的对象 61 gt 回到顶层目录
  • git format-patch用法

    1 命令介绍 git format patch用来对某次提交生成patch xff0c 方便发送给其他人员进行参考或者同步 2 生成patch用法 基于上几次内容打包 有几个 就会打几个patch xff0c 从最近一次打起 git for
  • SELinux权限添加

    1 avc denied log 如下 span class token number 01 span span class token operator span span class token number 01 span span
  • Android常见指令汇总

    1 查看UID对应的APK 一个UID对应一个APK adb pull data system packages list 2 adb开关机指令 adb span class token function reboot span span
  • WiFi Direct(WiFi P2P)

    Wi Fi Direct技术是Wi Fi产业链向蓝牙技术发起的挑战 xff0c 它试图完全取代蓝牙 Wi Fi Direct是一种点对点连接技术 xff0c 它可以在两台station之间直接建立tcp ip链接 xff0c 并不需要AP的
  • C++经典面试题(九)

    最近看一些面试题 xff0c 觉得如果自己被问到了 xff0c 并不能很利落的回答出来 一是从来没有这个意识 xff0c 二是没有认真的梳理下 下面对这些题做出分析 xff0c 哈 xff01 个人能力有限 xff0c 其中难免有疏漏 xf
  • 我的大学——学习生活总结

    纪念我终将逝去的青春 大一上學期 專業 1 C語言K amp R amp amp 習題 2 C語言經典習題 3 C語言趣味習題 4 C陷阱与缺陷 5 彙編語言 6 C 43 43 程序設計 7 C 程序設計
  • 基于Python的开源人脸识别库:离线识别率高达99.38%——新开源的用了一下感受一下

    该项目是要构建一款免费 开源 实时 离线的网络 app xff0c 支持组织者使用人脸识别技术或二维码识别所有受邀人员 有了世界上最简单的人脸识别库 xff0c 使用 Python 或命令行 xff0c 即可识别和控制人脸 该库使用 dli

随机推荐

  • 5G智慧医疗十大应用场景,你知道多少?

    来源 xff1a 北京物联网智能技术应用协会 都说5G会改变千行百业 xff0c 其中 xff0c 5G医疗健康就是5G技术在医疗健康行业的一个重要应用领域 随着 5G 正式商用的到来以及与大数据 互联网 43 人工智能 区块链等前沿技术的
  • 全网最详细的排列组合系列算法题整理

    写在前面 LeetCode上面排列组合系列几乎所有题目放在一起整理了一下 面试题 08 07 无重复字符串的排列组合 无重复字符串的排列组合 编写一种方法 xff0c 计算某字符串的所有排列组合 xff0c 字符串每个字符均不相同 示例 输
  • 使用IDEA从git拉取分支

    一 打开IDEA xff0c 进入目录 xff1a File gt New gt Project from Version Control 二 打开git工程 xff0c 进行clone对应的链接 填充对应的链接 三 默认下载的是maste
  • 用了cloudflare后,网站提示Sorry, you have been blocked怎么解决?

    其实cloudflare还是非常智能的 xff0c 但有时候为了安全起见 xff0c 我们在网站后台修改参数的时候会被CF拦截 xff0c 我就遇到了好几次提示Sorry you have been blocked的情况 遇到这种情况后 x
  • 下载网页视频的方法

    随着技术的不断更新 xff0c 现在小视频越来越火 xff0c 有的时候想保存浏览的小视频 xff0c 可不知道如何下载 xff1f 对于一些非专业的视频网站的小视频应该通过浏览器的选项是可以下载和保存的 下面就介绍使用浏览器下载的方法 1
  • tomcat8.0.9的安装

    免安装版的tomcat http download csdn net detail u011731233 7632475 这个解压之后 xff0c 在myeclipse中指定到tomcat目录就可以用了 xff0c 也不用配置环境变量 下载
  • 【多线程/C++】阻塞队列的C++多线程 实现 BlockingQueue

    阻塞队列在存放和获取队列中的数据时需要使用多线程 xff0c 一个线程专门负责向队列中存放元素 xff0c 另一个线程专门从队列中获取元素 也可多开辟跟多的线程进行存取 规范的方法也正是存放和获取队列元素分别在不同的线程中实现 阻塞队列实现
  • Log4J使用详解(整理)

    1 Log4j是什么 Log4j是Apache的一个开源项目 xff0c 通过使用Log4j xff0c 我们可以控制日志信息输送的目的地是控制台 文件 GUI组件 xff0c 甚至是套接口服务器 NT的事件记录器 UNIX Syslog守
  • DelphiXE10.2.3实现线程安全访问数据和对象(四)——实现原子自旋锁的无锁对象池

    无锁对象池与无锁Hash是不同应用场景中使用 xff0c 无锁Hash只是预先创建好Hash表 xff08 当然也可以动态Add xff09 后 xff0c 供调用者通过Key值快速找到保存的数据 xff0c 并读取 xff08 这里就只能
  • init进程详细分析--基于android 10

    init进程详细分析 概述 android设备上电 xff0c 引导程序引导进入boot 通常是uboot xff0c 加载initramfs kernel镜像 xff0c 启动kernel后 xff0c 进入用户态程序 第一个用户空间程序
  • commons-logging的使用

    简介 commons logging是Apache commons类库中的一员 Apache commons类库是一个通用的类库 xff0c 提供了基础的功能 xff0c 比如说commons fileupload xff0c common
  • 年度最理性 AI 分析文章:预测 AI 未来,大部分人陷入了 7 大误区

    来源 xff1a 36氪 概要 xff1a 错误的预测会导致大家对不会发生的事情感到恐惧 为什么在人工智能和机器人的预测上总有人不断犯错呢 xff1f 想着预测未来 xff0c 却一不小心就陷入了yy 近年来图像识别突破 Waymo无人车上
  • slf4j的使用

    OK xff0c 现在我们来使用slf4j 概念 SLF4J xff0c 即简单日志门面 xff08 Simple Logging Facade for Java xff09 xff0c 不是具体的日志解决方案 xff0c 它只服务于各种各
  • Java日志管理最佳实践

    原文出处 xff1a http www ibm com developerworks cn java j lo practicelog 感谢原作者 xff0c 感谢ibm网站 xff0c 里面有好多的精华帖 日志记录是应用程序运行中必不可少
  • MySQL数据类型--浮点数类型和定点数类型

    MySQL中使用浮点数类型和定点数类型来表示小数 浮点数类型包括单精度浮点数 xff08 float型 xff09 和双精度浮点数 xff08 double型 xff09 定点数类型就是decimal型 OK xff0c 现在我们来看看这几
  • MySQL数据类型--日期和时间类型

    MySQL中的多种时间和格式数据类型 日期和时间类型是为了方便在数据库中存储日期和时间而设计的 MySQL中有多种表示日期和时间的数据类型 其中 xff0c year类型表示时间 xff0c date类型表示日期 xff0c time类型表
  • MySQL数据类型--二进制类型

    二进制类型是在数据库中存储二进制数据的数据类型 二进制类型包括binary xff0c varbinary xff0c bit xff0c tinyblob xff0c blob xff0c mediumblob xff0c longblo
  • 单行注释和多行注释

    我们在实际编码中 xff0c 总是需要为程序添加一些注释 什么是注释 xff1f 注释就是一段文字 xff0c 这段文字并不是必须的 xff0c 也不直接参与代码运行 注释用来说明某段代码的作用 xff0c 或者说明某个类的用途 xff0c
  • Integer源码解析

    这篇博客我来整理下以Integer为例整理下包装类的源码 首先来看一段代码 xff1a public class LinkinPark public static void main String args Integer a 61 1 I
  • 不可变类

    不可变类 先来科普2个概念 xff0c 可变类和不可变类 1 xff09 xff0c 不可变类的意思就是创建该类的实例后 xff0c 该实例的实例变量是不可改变的 Java提供的8个包装类和String类都是不可变类 xff0c 当创建他们