Java中创建不可变类:终极指南与最佳实践

简介:Java中如何创建不可变类

本文将深入探讨如何在Java编程中创建一个不可变类。

当一个对象在初始化后其状态无法被修改时,我们称之为不可变对象。例如,Java中的String类就是一个典型的不可变类。一旦String对象被创建,其内部的值就不能再改变。如果您想了解更多关于为什么Java中的String类是不可变的,可以点击查看。

由于不可变对象一旦创建就无法更新,程序在需要改变其“状态”时,必须创建一个全新的对象。尽管如此,不可变对象依然具有显著的优势:

  • 不可变类非常适合用于缓存,因为您无需担心其值的意外改变。
  • 不可变类天生就是线程安全的,这在多线程环境中省去了处理线程同步的复杂性。

如果您对Java多线程感兴趣,可以了解更多Java多线程知识,并浏览Java多线程面试常见问题

在Java中创建不可变类的原则

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

  1. 将类声明为final,以防止其被继承和修改行为。
  2. 将所有的字段(成员变量)声明为private,以防止外部直接访问和修改。
  3. 不提供任何修改变量的setter方法。
  4. 将所有可变字段声明为final,以确保它们的值只能被赋值一次。
  5. 在构造方法中,对所有可变对象字段执行深拷贝(deep copy)进行初始化,而不是直接引用外部对象。
  6. getter方法中,对于返回可变对象字段时,应返回其副本(克隆对象),而不是实际的对象引用,以防止外部修改内部状态。

以下是一个示例类,旨在说明不可变性的基本概念。FinalClassExample类定义了字段,并提供了一个使用深拷贝来初始化对象的构造方法。FinalClassExample.java文件中的main方法将用于测试对象的不可变性。

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

// FinalClassExample.java 最终类示例
// ... (此处将是Java代码内容)

这是文章《如何在Java中创建一个不可变类》的第2部分(共5部分)。

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("正在为对象初始化执行深拷贝");

		// "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", "第一个");
		h1.put("2", "第二个");
		
		String s = "原始";
		
		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="已修改";
		h1.put("3", "第三个");
		// 再次打印值
		System.out.println("局部变量改变后 ce id: "+ce.getId());
		System.out.println("局部变量改变后 ce name: "+ce.getName());
		System.out.println"局部变量改变后 ce testMap: "+ce.getTestMap());
		
		HashMap<String, String> hmTest = ce.getTestMap();
		hmTest.put("4", "新的");
		
		System.out.println("通过 getter 方法改变变量后 ce testMap: "+ce.getTestMap());

	}

}

编译并运行程序:

  1. javac FinalClassExample.java
  2. java FinalClassExample

 

注意

注意:编译文件时可能会出现以下消息:

注意:FinalClassExample.java使用了未经检查或不安全的操作,因为getter方法将HashMap<String, String>强制转换为Object时进行了未经检查的转换。你可以忽略编译器警告,仅供本例使用。

你将得到以下输出:

输出
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;

现在示例文件应该像这样:

终态示例类.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("正在为对象初始化执行浅拷贝");
		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: "+ce.getId());
		System.out.println("局部变量改变后 ce name: "+ce.getName());
		System.out.println("局部变量改变后 ce testMap: "+ce.getTestMap());
		
		HashMap<String, String> hmTest = ce.getTestMap();
		hmTest.put("4", "new");
		
		System.out.println("通过 getter 方法改变变量后 ce testMap: "+ce.getTestMap());

	}

}

编译并运行程序。

  1. javac FinalClassExample.java
  2. java FinalClassExample

 

你将得到以下输出结果:

输出

执行对象的浅拷贝初始化

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教程。

bannerAds