详解Java如何实现一个像String一样不可变的类

 更新时间:2022年11月28日 09:36:27   作者:鸭血粉丝Tang  
说到 String 大家都知道 String 是一个不可变的类;虽然用的很多,那不知道小伙伴们有没有想过怎么样创建一个自己的不可变的类呢?这篇文章就带大家来实践一下,创建一个自己的不可变的类

如果问你在日常开发中用到的最多的一个 Java 类是什么,阿粉敢打赌绝对是 String.class。说到 String 大家都知道 String 是一个不可变的类;虽然用的很多,那不知道小伙伴们有没有想过怎么样创建一个自己的不可变的类呢?这篇文章阿粉就带大家来实践一下,创建一个自己的不可变的类。

特性

在手动编写代码之前,我们先了解一下不可变类都有哪些特性,

  • 定义类的时候需要使用 final 关键字进行修饰:之所以使用 final 进行修饰是因为这样可以避免被其他类继承,一旦有了子类继承就会破坏父类的不可变性机制;
  • 成员变量需要使用 final 关键词修饰,并且需要是 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 对象。

到此这篇关于详解Java如何实现一个像String一样不可变的类的文章就介绍到这了,更多相关Java不可变的类内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot+Kotlin中使用GRPC实现服务通信的示例代码

    SpringBoot+Kotlin中使用GRPC实现服务通信的示例代码

    本文主要介绍了SpringBoot+Kotlin中使用GRPC实现服务通信的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • 详解JAVA里面获取map的key和value的方法

    详解JAVA里面获取map的key和value的方法

    这篇文章主要介绍了详解JAVA里面获取map的key和value的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • Java 线程死锁的问题解决办法

    Java 线程死锁的问题解决办法

    这篇文章主要介绍了 Java 线程死锁的问题解决办法的相关资料,希望通过本大家能帮助到大家,遇到类似问题能够解决,需要的朋友可以参考下
    2017-09-09
  • Java实现Map遍历key-value的四种方法

    Java实现Map遍历key-value的四种方法

    本文主要介绍了Java实现Map遍历key-value的四种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • 关于Java中try finally return语句的执行顺序浅析

    关于Java中try finally return语句的执行顺序浅析

    这篇文章主要介绍了关于Java中try finally return语句的执行顺序浅析,需要的朋友可以参考下
    2017-08-08
  • Java获取项目路径的多种方式

    Java获取项目路径的多种方式

    这篇文章主要介绍了Java获取项目路径的多种方式,这时候就需要用java给我们提供的一些获取相对路径方法了,本文通过实例代码给大家介绍的非常详细,需要的朋友参考下吧
    2022-01-01
  • 解决@PathVariable对于特殊字符截断的问题

    解决@PathVariable对于特殊字符截断的问题

    这篇文章主要介绍了解决@PathVariable对于特殊字符截断的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • Maven访问仓库顺序代码实例解析

    Maven访问仓库顺序代码实例解析

    这篇文章主要介绍了Maven访问仓库顺序实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • kafka 重新分配partition和调整replica的数量实现

    kafka 重新分配partition和调整replica的数量实现

    当需要提升Kafka集群的性能和负载均衡时,可通过kafka-reassign-partitions.sh命令手动重新分配Partition,增加节点后,可以将Topic的Partition的Leader节点均匀分布,以提高写入和消费速度,感兴趣的可以了解一下
    2022-03-03
  • Spring MVC温故而知新系列教程之从零开始

    Spring MVC温故而知新系列教程之从零开始

    Spring MVC 框架在 Java 的 Web 项目中应该是无人不知的吧,你不会搭建一个 Spring 框架?作为身为一个刚刚学习Java的我都会,如果你不会的话,那可真令人忧伤。下面这篇文章主要给大家介绍了关于Spring MVC从零开始的相关资料,需要的朋友可以参考下
    2018-05-05

最新评论