详解Java如何实现一个像String一样不可变的类
作者:鸭血粉丝Tang 发布时间:2022-01-12 16:17:11
如果问你在日常开发中用到的最多的一个 Java
类是什么,阿粉敢打赌绝对是 String.class
。说到 String
大家都知道 String
是一个不可变的类;虽然用的很多,那不知道小伙伴们有没有想过怎么样创建一个自己的不可变的类呢?这篇文章阿粉就带大家来实践一下,创建一个自己的不可变的类。
特性
在手动编写代码之前,我们先了解一下不可变类都有哪些特性,
定义类的时候需要使用
final
关键字进行修饰:之所以使用final
进行修饰是因为这样可以避免被其他类继承,一旦有了子类继承就会破坏父类的不可变性机制;成员变量需要使用
fina
l 关键词修饰,并且需要是private
的:避免属性被外部修改;成员变量不可提供
setter
方法,只能提供getter
方法:避免被外部修改,并且避免返回成员变量本身;提供所有字段的构造函数;
实操
知道了不可变类的一些基本特性之后,我们来实际写代码操作一下,以及我们会验证一下,如果不按照上面的要求来编写的话,会出现什么样的问题。
这里我们定义一个 Teacher
类来测试一下,按照我们上面提到的几点,我们给类和属性的定义都加上 final
代码如下所示。
package com.example.demo.immutable;
import java.util.List;
import java.util.Map;
public final class Teacher {
private final String name;
private final List<String> students;
private final Address address;
private final Map<String, String> metadata;
public Teacher(String name, List<String> students, Address address, Map<String, String> metadata) {
this.name = name;
this.students = students;
this.address = address;
this.metadata = metadata;
}
public String getName() {
return name;
}
public List<String> getStudents() {
return students;
}
public Address getAddress() {
return address;
}
public Map<String, String> getMetadata() {
return metadata;
}
}
package com.example.demo.immutable;
public class Address {
private String country;
private String city;
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
我们思考一下,上面的代码是否真正的做到了不可变,好,我们思考三秒钟,心里默默的数三下。为了回答这个问题,我们看下下面的测试代码。
package com.example.demo;
import com.example.demo.immutable.Address;
import com.example.demo.immutable.Teacher;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* <br>
* <b>Function:</b><br>
* <b>Author:</b>@author Silence<br>
* <b>Date:</b>2022-11-22 21:17<br>
* <b>Desc:</b>无<br>
*/
public class ImmutableDemo {
public static void main(String[] args) {
List<String> students = new ArrayList<>();
students.add("鸭血粉丝 1");
students.add("鸭血粉丝 2");
students.add("鸭血粉丝 3");
Address address = new Address();
address.setCountry("中国");
address.setCity("深圳");
Map<String, String> metadata = new HashMap<>();
metadata.put("hobby", "篮球");
metadata.put("age", "29");
Teacher teacher = new Teacher("Java极客技术", students, address, metadata);
System.out.println(teacher.getStudents().size());
System.out.println(teacher.getMetadata().size());
System.out.println(teacher.getAddress().getCity());
// 修改属性
teacher.getStudents().add("小明");
teacher.getMetadata().put("weight", "120");
teacher.getAddress().setCity("广州");
System.out.println(teacher.getStudents().size());
System.out.println(teacher.getMetadata().size());
System.out.println(teacher.getAddress().getCity());
}
}
运行的结果如下截图所示,通过测试我们可以发现,简单的只添加 final
关键字是不能解决不可变性的,我们当前的 teacher
实例已经被外层修改掉了成员变量。
为了解决这个问题,我们还需要对我们的 Teacher
类进行改造,首先我们可以想到的就是需要将 students
和 metadata
两个成员变量不能直接返回给外层,否则外层的修改会直接影响到我们的不可变类,那么我们就可以修改 getter
方法,拷贝一下成员变量进行返回,而不是直接返回,修改代码如下
public List<String> getStudents() {
return new ArrayList<>(students);
//return students;
}
public Map<String, String> getMetadata() {
return new HashMap<>(metadata);
//return metadata;
}
我们再次运行上面的测试代码,可以看到这次的返回数据如下,这次我们的 students
和 metadate
成员变量并没有被外层修改掉了。但是我们的 address
成员变量还是有问题,没关系,我们接着往下看。
很自然的为了解决 address
的问题,我们想到了也是进行一个拷贝,再调用 getter
方法的时候返回一个拷贝对象,而不是直接返回成员变量。那我们就需要改造 Address
类,将其变成 Cloneable
的即可,我们实现 接口,然后覆盖一个 clone
方法,代码如下
package com.example.demo.immutable;
public class Address implements Cloneable{
...// 省略
@Override
public Address clone() {
try {
return (Address) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
再修改 Teacher
的 getAddress
方法
public Address getAddress() {
//return address;
return address.clone();
}
接下来我们再运行一下测试代码,结果如下,可以看到这次我们的 teacher
实例的成员变量并没有被修改掉了,至此我们完成了一个不可变对象的创建!
String 的实现
前面我们看的是自定义实现不可变类的操作,接下来我们简单看一下 String
类是如何实现不可变的,通过源码我们可以看到 String
也使用了关键字 final
来避免被子类继承,以及对应存放具体值的成员变量也使用了 final
关键字。
并且对外提供的方法 substring
也是通过复制的形式对外提供的新的 String
对象。
来源:https://mp.weixin.qq.com/s/fOXeblVcw9Ph5ug5eehb-A


猜你喜欢
- Swing组件中的事件处理专门用于响应用户的操作,例如,响应用户的鼠标单击、按下键盘等操作。在Swing事件处理的过程中,主要涉及三类对象:
- 在默认情况下,对象的Equals(object o)方法(基类Object提供),是比较两个对象变量是否引用同一对象。我们要必须我自己的对象
- 在系统的管理员有着实际的应用,对于一个数据库管理系统来说,数据库安全还是挺重要的,所以在存入到数据库的密码通常都是加密的。即使有着hack攻
- 本文实例讲述了Android利用jsoup解析HTML页面的方法。分享给大家供大家参考,具体如下:这节主要是讲解jsoup解析HTML页面。
- 一.前言解决服务雪崩效应,都是避免application client请求application service时,出现服务调用错误或网络问
- 这里使用 Maven 项目管理工具构建项目初始化项目打开 Intellij IDEA,点击 Create New Project选择 Mav
- 前面关于spring Boot的文章已经介绍了很多了,但是一直都没有涉及到数据库的操作问题,数据库操作当然也是我们在开发中无法回避的问题,那
- Java 1.0 IO系统介绍1 Java IO版本Java库的IO分为输入/输出两部分。早期的Java 1.0版本的输入系统是InputS
- 无限滚动复用列表Demo展示前言游戏中有非常多的下拉滚动菜单,比如成就列表,任务列表,以及背包仓库之类;如果列表内容非常丰富,会占用大量内存
- 本文实例为大家分享了C#添加读取Word脚注尾注的具体代码,供大家参考,具体内容如下脚注和尾注是对文本的补充说明。脚注一般位于页面的底部,可
- 1、相差秒public double DiffSeconds(DateTime startTime,DateTime endTime){ &
- 近日有朋友问我有没有如下图效果的开源控件相信大家无论是用IOS还是Android,都对这种效果不陌生,很多主流APP都会有这样或类似的效果,
- 依赖让我们先把 zip4j 依赖关系添加到我们的 pom.xml 文件中。<dependenc
- Android 图片选择可以达到的效果:1.第一个图片的位置放照相机,点击打开照相机2.其余的是显示全部存储的图片,点击一次是查
- 本文实例为大家分享了C#异步调用的具体代码,供大家参考,具体内容如下using System;using System.Collection
- 1.功能介绍Spring框架提供了线程池和定时任务执行的抽象接口:TaskExecutor和TaskScheduler来支持异步执行任务和定
- 1、对属性进行封装,使用户不能直接输入数据,我们需要避免用户再使用"对象.属性"的方式对属性进行赋值。则需要将属性声明为
- IDEA设置文档注释模板创建Class文件时自动生成的头部注释如图如何配置idea的头部注释格式,可以生成像之前的注释格式一样的文档注释?F
- 过去的每一year,涌现出越来越多的Java框架。就像JavaScript,每个人都认为他们知道一个好的框架的功能应该是怎么样的。连我的老祖
- 本文实例讲述了应用Java泛型和反射导出CSV文件的方法。分享给大家供大家参考。具体如下:项目中有需求要把数据导出为CSV文件,因为不同的类