如何在Java中创建一个不可变类

介绍

本文概述了如何在Java编程中创建一个不可变类。

对象在初始化后其状态不会改变时被称为不可变。例如,String是一个不可变类,一旦实例化,String对象的值就不会改变。了解更多关于为什么String类在Java中是不可变的

因为不可变对象无法更新,程序需要为每个状态的更改创建一个新对象。然而,不可变对象也具有以下好处:

  • 不可变类非常适合缓存,因为你不必担心值的变化。
  • 不可变类在本质上是线程安全的,因此在多线程环境中不必担心线程安全性问题。

了解更多关于Java多线程的知识并浏览Java多线程面试题

在Java中创建一个不可变类

要在Java中创建一个不可变类,您需要遵循以下一般原则:

  1. 将类声明为final,以防止它被扩展。
  2. 将所有字段都设置为private,以防止直接访问。
  3. 不要为变量提供setter方法。
  4. 将所有可变字段设置为final,以便字段的值只能被分配一次。
  5. 使用执行深拷贝的构造函数方法初始化所有字段。
  6. 在getter方法中执行对象的克隆,以返回一个副本而不是返回实际对象引用。

以下是一个示例类,说明了不可变性的基本原理。 FinalClassExample 类定义了字段,并提供了使用深拷贝初始化对象的构造函数方法。 FinalClassExample.java 文件中的main方法中的代码测试了对象的不可变性。

创建一个名为FinalClassExample.java的新文件,并复制以下代码进去:

FinalClassExample.java
import java.util.HashMap;
import java.util.Iterator;

public final class FinalClassExample {

	// FinalClassExample类的字段
	private final int id;
	
	private final String name;
	
	private final HashMap<String,String> testMap;

	
	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	// 可变对象的Getter函数

	public HashMap<String, String> getTestMap() {
		return (HashMap<String, String>) testMap.clone();
	}

	// 执行深复制的构造方法
	
	public FinalClassExample(int i, String n, HashMap<String,String> hm){
		System.out.println("Performing Deep Copy for Object initialization");

		// "this"关键字指代当前对象
		this.id=i;
		this.name=n;

		HashMap<String,String> tempMap=new HashMap<String,String>();
		String key;
		Iterator<String> it = hm.keySet().iterator();
		while(it.hasNext()){
			key=it.next();
			tempMap.put(key, hm.get(key));
		}
		this.testMap=tempMap;
	}

	// 测试不可变类

	public static void main(String[] args) {
		HashMap<String, String> h1 = new HashMap<String,String>();
		h1.put("1", "first");
		h1.put("2", "second");
		
		String s = "original";
		
		int i=10;
		
		FinalClassExample ce = new FinalClassExample(i,s,h1);
		
		// 打印ce的值
		System.out.println("ce id: "+ce.getId());
		System.out.println("ce name: "+ce.getName());
		System.out.println("ce testMap: "+ce.getTestMap());
		// 更改局部变量的值
		i=20;
		s="modified";
		h1.put("3", "third");
		// 再次打印值
		System.out.println("ce id after local variable change: "+ce.getId());
		System.out.println("ce name after local variable change: "+ce.getName());
		System.out.println("ce testMap after local variable change: "+ce.getTestMap());
		
		HashMap<String, String> hmTest = ce.getTestMap();
		hmTest.put("4", "new");
		
		System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());

	}

}

编译并运行程序:

  1. javac FinalClassExample.java
  2. java FinalClassExample

注意:编译文件时可能会收到以下消息:注意:FinalClassExample.java使用了未经检查或不安全的操作,因为Getter方法使用了从HashMap<String,String>Object的未经检查的强制转换。在本示例中,您可以忽略编译器警告。

您将获得以下输出:

Output
Performing Deep Copy for Object initialization ce id: 10 ce name: original ce testMap: {1=first, 2=second} ce id after local variable change: 10 ce name after local variable change: original ce testMap after local variable change: {1=first, 2=second} ce testMap after changing variable from getter methods: {1=first, 2=second}

输出显示HashMap的值未更改,因为构造方法使用深复制,Getter函数返回原始对象的克隆。

不使用深拷贝和克隆会发生什么

您可以更改FinalClassExample.java文件,以展示在使用浅拷贝而不是深拷贝并返回对象而不是副本时会发生什么。对象不再是不可变的,可以进行更改。对示例文件进行以下更改(或从代码示例中复制粘贴):

  • 删除提供深拷贝的构造方法,并添加提供浅拷贝的构造方法,如下例所示。
  • 在getter函数中,删除return (HashMap<String, String>) testMap.clone();并添加return testMap;

现在示例文件应如下所示:

FinalClassExample.java
import java.util.HashMap;
import java.util.Iterator;

public final class FinalClassExample {

	// FinalClassExample类的字段
	private final int id;
	
	private final String name;
	
	private final HashMap<String,String> testMap;

	
	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	// 可变对象的Getter函数

	public HashMap<String, String> getTestMap() {
		return testMap;
	}

	// 执行浅拷贝的构造方法

	public FinalClassExample(int i, String n, HashMap<String,String> hm){
		System.out.println("Performing Shallow Copy for Object initialization");
		this.id=i;
		this.name=n;
		this.testMap=hm;
	}

	// 测试不可变类

	public static void main(String[] args) {
		HashMap<String, String> h1 = new HashMap<String,String>();
		h1.put("1", "first");
		h1.put("2", "second");
		
		String s = "original";
		
		int i=10;
		
		FinalClassExample ce = new FinalClassExample(i,s,h1);
		
		// 打印ce的值
		System.out.println("ce id: "+ce.getId());
		System.out.println("ce name: "+ce.getName());
		System.out.println("ce testMap: "+ce.getTestMap());
		// 更改局部变量的值
		i=20;
		s="modified";
		h1.put("3", "third");
		// 再次打印值
		System.out.println("ce id after local variable change: "+ce.getId());
		System.out.println("ce name after local variable change: "+ce.getName());
		System.out.println("ce testMap after local variable change: "+ce.getTestMap());
		
		HashMap<String, String> hmTest = ce.getTestMap();
		hmTest.put("4", "new");
		
		System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());

	}

}

编译并运行程序:

  1. javac FinalClassExample.java
  2. java FinalClassExample

你得到以下输出:

Output
Performing Shallow Copy for Object initialization ce id: 10 ce name: original ce testMap: {1=first, 2=second} ce id after local variable change: 10 ce name after local variable change: original ce testMap after local variable change: {1=first, 2=second, 3=third} ce testMap after changing variable from getter methods: {1=first, 2=second, 3=third, 4=new}

输出显示 HashMap 的值发生了变化,因为构造方法使用了浅拷贝,在 getter 函数中直接引用了原始对象。

结论

你学到了一些在 Java 中创建不可变类时应遵循的一般原则,包括深拷贝的重要性。继续学习更多的 Java 教程

Source:
https://www.digitalocean.com/community/tutorials/how-to-create-immutable-class-in-java