Java并发设计模式--不可变模式(immutable)

3,441 阅读3分钟

一、什么是不可变模式?

不可变,顾名思义,就是对象创建之后就不能够变化嘛!更具体地说,就是对象创建之后它的属性值不能够发生变化!所有对原对象的操作都会返回原对象的拷贝。那么在java中怎么做到这一点呢?答案就是使用final关键字。下面我将讲讲如何设计出一个`immutable``对象。

设计一个不可变类应该遵循以下几点:

  • 1、类的所有属性声明为private,去除掉所有的setter方法,防止外界直接对其进行修改
  • 2、类的声明采用final进行修饰,保证没有父类对其修改
  • 3、类的属性声明为final,如果对象类型为可变类型,应对其重新包装,重新new一个对象返回

下面是一个不可变类实例:

package com.wokao66;

/**
 * 不可变类
 * @author: huangjiawei
 * @since: 2018年4月2日
 * @version: $Revision$ $Date$ $LastChangedBy$
 *
 */
//采用fianl修饰,防止子类继承
public final class Immutable {

	/**
	 * 所有的属性private且final
	 */
	private final String name;
	private final int age;

	/**
	 * 构造方法
	 * @param name
	 * @param age
	 */
	public Immutable(String name, int age) {
		this.name = name;
		this.age = age;
	}

	/**
	 * 去除所有的setter方法
	 */
	public String getName() {
		return name;
	}

	public int getAge() {
		return age;
	}

	/**
	 * 将年龄增加10岁
	 * @param newAge
	 * @return
	 */
	public Immutable addAge(int newAge) {
		/**
		 * 重新返回一个对象
		 */
		return new Immutable(this.getName(), newAge + this.getAge());
	}

	public static void main(String[] args) {
		Immutable immutable = new Immutable("a", 12);
		System.err.println(immutable.getAge());
		Immutable newImmutable = immutable.addAge(10);
		System.err.println(immutable.getAge());
		System.err.println(newImmutable.getAge());
	}
}

运行结果:

12
12
22

二、一不小心就设计成可变对象了!

如果上面的不可变类这样设计,那么就变成可变的了!

package com.wokao66;

import java.util.Date;

/**
 * 人生处处有惊喜,一不小心就掉进陷阱里
 * @author: huangjiawei
 * @since: 2018年4月2日
 * @version: $Revision$ $Date$ $LastChangedBy$
 */
public final class Mutable {

	/**
	 * 所有的属性private且final
	 */
	private final String name;
	private final int age;
	private final Date birthday;

	/**
	 * 构造方法
	 * @param name
	 * @param age
	 */
	public Mutable(String name, int age, Date birthday) {
		this.name = name;
		this.age = age;
		this.birthday = birthday;
	}

	/**
	 * 去除所有的setter方法
	 */
	public String getName() {
		return name;
	}

	public int getAge() {
		return age;
	}

	public Date getBirthday() {
		return birthday;
	}

	/**
	 * 将年龄增加10岁
	 * @param newAge
	 * @return
	 */
	public Mutable addAge(int newAge) {
		/**
		 * 重新返回一个对象
		 */
		return new Mutable(this.getName(), newAge + this.getAge(), this.birthday);
	}

	public static void main(String[] args) {
		Date birthday = new Date();
		Mutable xiaoming = new Mutable("小明", 21, birthday);
		System.err.println("小明的生日为 : " + xiaoming.getBirthday());

		//我设置下我的生日,你会发现我的生日居然可以改变
		birthday.setTime(System.currentTimeMillis() + 1000000000);
		System.err.println("小明的生日为 : " + xiaoming.getBirthday());
	}
}

输出如下:

小明的生日为 : Mon Apr 02 15:33:44 CST 2018
小明的生日为 : Sat Apr 14 05:20:24 CST 2018

可见结果发生了变化,因为Date是可变类型的。将构造方法修改如下:

	/**
	 * 构造方法
	 * @param name
	 * @param age
	 */
	public Mutable(String name, int age, Date birthday) {
		this.name = name;
		this.age = age;
		//对于可变类型的属性,初始化的时候应该重新生成一个
		//this.birthday = new Date(birthday.getTime());
		this.birthday = birthday;
	}

输出如下:

小明的生日为 : Mon Apr 02 15:36:24 CST 2018
小明的生日为 : Mon Apr 02 15:36:24 CST 2018

二、不可变模式优缺点及应用场景

优点:

  • 1、因为是不可变的,所以不允许程序对其进行修改,避免了程序中修改数据带来的异常产生
  • 2、由于对象是不可变的,减少了线程同步带来的开销

缺点:

  • 1、每次返回都创建新的对象,内存会有一定的开销,不容易被垃圾回收器回收,造成资源的浪费

应用场景:

  • 1、不适合大对象、且创建频繁的场景,因为对象大且创建频繁会容易导致内存泄漏
  • 2、适合表示抽象数据类型(如数字、枚举类型或颜色)的值
  • 3、适合在多线程环境中进行同步而不需要考虑线程同步