java中Final关键字和Immutable Class以及Guava的不可变对象

孤街醉人 提交于 2019-12-02 17:09:25

大纲

  1. 写这篇文章的起因

  2. java中Final关键字

  3. 如何构建不可变对象

  4. Guava中不可变对象和Collections工具类的unmodifiableSet/List/Map/etc的区别

  5. 实验代码

写这篇文章的起因

    java项目在使用FindBugs扫描的时候报了一个不能使用可变对象,记得报的是类似如下的信息:

MS: Field is a mutable collection (MS_MUTABLE_COLLECTION)
官方解释:
A mutable collection instance is assigned to a final static field, thus can be changed by malicious code or by accident from another package. Consider wrapping this field into Collections.unmodifiableSet/List/Map/etc. to avoid this vulnerability.

参考FindBugs的描述:http://findbugs.sourceforge.net/bugDescriptions.html

由于最近学习的东西较多,对有些基本的概念略有生疏,所以又温故了一下,顺便提供下上面问题的解决方案,仅供参考。

java中Final关键字

    Final是java的一个关键字,可以修饰:变量、方法、类,大致用法,我写了一个测试类,注释也比较多。

package org.unrulylianzi.basis.javass.finalkeyword;


/**
 * java final 基本用法
 * <li>variable</li>
 * <li>method</li>
 * <li>class</li>
 * */
public class JavaFinalKeyWord {
	/**
	 * 基本类型
	 * */
	private final int a=3;
	
	private final int b;//must be initialized at the time of declaration or inside constructor or block

	
	{
		//a=4;不能重复对final的变量赋值
		System.out.println(a);
		b=4;
	}

	/**
	 * 引用类型
	 * */
	private final Person person = new Person("unrulylianzi");//must be initialized at the time of declaration or inside constructor
	
	{
		//person = new Person("unrulylianzi_change_final");//不能重复对final的变量赋值
		System.out.println(person);
		
		person.setName("unrulylianzi_change_value_not_reference");//可以修改引用类型的值,但是不能赋予新的Person对象
		
		System.out.println(person);
	}
	/**
	 * method
	 * */
	
	public  final String finalMethod(){
		return "unrulylianzi_final";
	}
	
	/**
	 * method not final
	 * */
	
	public  String notFinalMethod(){
		return "unrulylianzi_notfinal";
	}
}

final class  SubClass extends JavaFinalKeyWord {

	@Override
	public String notFinalMethod() {
		// TODO Auto-generated method stub
		return super.notFinalMethod();
	}
	
	/**
	 * final method can not be  overridden in Java
	   final的方法不能被继承
	@Override
	public String finalMethod() {
		// TODO Auto-generated method stub
		return super.finalMethod();
	}
	*/
	
}

/**
 *
 *Final class can not be inheritable in Java.
 Final修饰的类不能被继承

class ExtendsFinalClass extends SubClass {
	
}

*/

其中Person类是一个简单的bean
public class Person {
	private String name;
	//getter&setter
}

当然Final对性能也有很多好处,这次不会讲到。

如何构建不可变对象

    这个问题貌似没太在意过,也没深入思考过Final关键字和Immutable对象之间的关系。不可变对象就是对象一旦创建就是不能被改变的。下面列一下创建不可变类的步骤

  • 声明class为Final,以免class被子类继承,关于多态的概念就不讲啦。

  • 把所有类的属性声明成private,并且不提供set方法。

  • 把所有可变的变量声明为Final,使得这些变量只能初始化一次。

  • 暴露给外部的构造函数,在赋值的时候,要使用深拷贝。

  • 在变量对应的get方法的时候要提供引用类型变量的clone,而不是直接返回。

    我创建的一个不可变对象的示例代码如下:

package org.unrulylianzi.basis.javass.immutable;

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

/**
 * 不变对象
 * <li>1:Declare the class as final so it can’t be extended.</li>
 * <li>2:Make all fields private so that direct access is not allowed.</li>
 * <li>3:Don’t provide setter methods for variables</li>
 * <li>4:Make all mutable fields final so that it’s value can be assigned only once.</li>
 * <li>5:Initialize all the fields via a constructor performing deep copy.</li>
 * <li>6:Perform cloning of objects in the getter methods to return a copy rather than returning the actual object reference.</li>
 * */

public final class ImmutableClass {
	
	
	private final int age;
	private final String name;
	
	private final HashMap<String,String> testMap;

	/**
	 * shallow copy
	 * */
//	public ImmutableClass(int age, String name, HashMap<String, String> testMap) {
//		super();
//		this.age = age;
//		this.name = name;
//		this.testMap = testMap;
//	}
	
	/**
	 * deep copy
	 * */
	public ImmutableClass(int age, String name, HashMap<String, String> testMap) {
		super();
		this.age=age;
		this.name=name;
		HashMap<String,String> tempMap=new HashMap<String,String>();
		String key;
		Iterator<String> it = testMap.keySet().iterator();
		while(it.hasNext()){
			key=it.next();
			tempMap.put(key, testMap.get(key));
		}
		this.testMap=tempMap;
	}

	/**
	 * @return the age
	 */
	public int getAge() {
		return age;
	}

	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}

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

可见Final关键字是创建不可变对象的基础。

Guava中不可变对象和Collections工具类的unmodifiableSet/List/Map/etc的区别

    guava是google的一个库,弥补了java语言的很多方面的不足,很多在java8中已有实现,暂时不展开。Collections是jdk提供的一个工具类。两者之间的区别:

当Collections创建的不可变集合的wrapper类改变的时候,不可变集合也会改变,而Guava的Immutable集合保证确实是不可变的。

     测试代码如下:

//guava的实现
public static final List<String> immutableList;

	static {
		immutableList = ImmutableList.of("a");
	}

	/**
	 * 改变immutableList
	 * 
	 */
	public static void immutableListGuavaChange() {

		try {
			System.out.println("Try adding more elements to ImmutableList");
			/**
			 * 改变immutableList已存在元素的值,添加新的元素,都会报错,抛出异常
			 */
			immutableList.set(0, "b");
			// immutableList.add("b");
		} catch (UnsupportedOperationException e) {
			System.out.println(
					"Throws UnsupportedOperationException, ImmutableList is immutable, won't allow adding elements");
		}
	}

  Collections工具类的实现:

/**
 * Collections工具类实现的不可变list的示例
 */
public class ImmutableListCollections {

	public static final Collection unmodifiableList;
	public static final List list;

	static {
		list = Arrays.asList("Once", "upon", "time", "there", "used", "to", "be", "king");
		unmodifiableList = Collections.unmodifiableCollection(list);
	}

	/**
	 * 改变immutableList
	 * 
	 */
	public static void immutableListCollectionsChange() {

		System.out.println("Try adding more elements to ImmutableList");
		/**
		 * 改变unmodifiableList已存在元素的值,添加新的元素,都会报错,抛出异常
		 */
		list.set(0, "b");
		System.out.println("###修改wrapper类###" + unmodifiableList);
		// immutableList.add("b");

	}
}

Guava和Collections实现原理(只为说明不可变,未深入)

查看Guava和Collections的实现,发现add方法是如下设计的:

public final void add(int index, E element) {
  throw new UnsupportedOperationException();
}

1、定义Final,防止子类改变父类的行为。

2、执行修改操作的时候,直接抛异常。

实验代码

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!