不可变类

Posted LinkinPark8林肯

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了不可变类相关的知识,希望对你有一定的参考价值。


不可变类

先来科普2个概念,可变类和不可变类。

1),不可变类的意思就是创建该类的实例后,该实例的实例变量是不可改变的。Java提供的8个包装类和String类都是不可变类,当创建他们的实例后,其实例的实例变量是不

可改变的。

2),与不可变类对应的是可变类,可变类的含义是该类的实例变量是可变的。大部分时候所创建的类都是可变类,特别是JavaBean,因为总是在其实例变量提供了setter和

getter方法。

看下面的代码:

Double d = new Double(6.5);
		String linkin = "LinkinPark";
上面的程序创建了一个Double对象和一个String对象,并为这两个对象传入了6.5和"LinkinPark"字符串作为参数,那么Double类和String类肯定需要提供实例变量来保存这两个

参数,但程序无法修改这两个实例变量的值,因此Double类和String类没有提供修改它们的方法。


如果需要创建自定义的不可变类,要遵守以下的规则:

1),使用private和final修饰该类的成员变量

2),提供带参数的构造器,用于根据传入参数来初始化该类的成员变量

3),仅为该类提供getter方法,不要提供setter方法,因为普通的方法不能改变这个类的属性

4),如果有必要,重写equals和hashcode方法。

/**
 * 不可变类
 * 
 * @author LinkinPark
 * 
 *         <pre>
 *         1,属性使用private final修饰
 *         2,构造器对属性赋值
 *         3,只提供get方法,不提供set方法
 *         4,如果有需要就重写equals和hashCode方法
 *         </pre>
 */
public class LinkinPark


	private final String name;
	private final Integer age;

	public LinkinPark(String name, Integer age)
	
		super();
		this.name = name;
		this.age = age;
	

	public String getName()
	
		return name;
	

	public Integer getAge()
	
		return age;
	

	public static void main(String[] args)
	
		new LinkinPark("LinkinPark", 25);
	


与可变类相比,不可变类的实例在整个生命周期中永远出于初始化阶段,它的实例变量不可改变,因此对不可变类的实例的控制将更加简单。

前面介绍final关键字时提到,当使用final修饰引用类型变量时,仅表示这个引用类型变量不可被重新赋值,但引用类型变量所指向的对象依然可以改变。

这就产生了一个问题,当创建一个不可变类时,如果它包含成员变量的类型是可变的,那么其对象的成员变量的值依然是可变的,那这个不可变类其实是失败的

看下面的例子:

/**
 * 引用类型的变量导致不可变类失败
 * 
 * @author LinkinPark
 */
public class LinkinPark


	private final String name;
	private final Linkin linkin;

	public LinkinPark(String name, Linkin linkin)
	
		super();
		this.name = name;
		this.linkin = linkin;
	

	public String getName()
	
		return name;
	

	public Linkin getLinkin()
	
		return linkin;
	

	@Override
	public String toString()
	
		return getClass().getSimpleName() + " [name=" + name + ", linkin=" + linkin + "]";
	

	public static void main(String[] args)
	
		Linkin linkin = new Linkin();
		linkin.setName("NightWish1");
		linkin.setAge(25);
		LinkinPark linkinPark = new LinkinPark("LinkinPark", linkin);
		System.out.println(linkinPark);

		linkin.setAge(24);
		linkin.setName("NightWish2");
		System.out.println(linkinPark);
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish1, age=25]]
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish2, age=24]]
	



class Linkin

	private String name;
	private Integer age;

	public String getName()
	
		return name;
	

	public void setName(String name)
	
		this.name = name;
	

	public Integer getAge()
	
		return age;
	

	public void setAge(Integer age)
	
		this.age = age;
	

	@Override
	public String toString()
	
		return getClass().getSimpleName() + " [name=" + name + ", age=" + age + "]";
	

运行上面的代码,我们也看到了,引用类型的变量导致我创建了一个失败的不可变类。那应该要怎么做呢?看下面代码:

/**
 * 引用类型的变量导致不可变类失败
 * 所以要针对引用类型的变量做专门的处理
 * 
 * <pre>
 * 1,构造器中不要直接使用传入的引用类型变量,自己取值然后重新new一次
 * 2,引用类型的变量用来存储刚才那个初始化的对象
 * 3,防止get方法直接返回刚才那个变量从而改变引用的那个对象,同样的方式处理
 * </pre>
 * 
 * @author LinkinPark
 */
public class LinkinPark


	private final String name;
	private final Linkin linkin;

	/**
	 * @param name
	 * @param linkin
	 */
	public LinkinPark(String name, Linkin linkin)
	
		super();
		this.name = name;
		this.linkin = new Linkin(linkin.getName(), linkin.getAge());
	

	public String getName()
	
		return name;
	

	public Linkin getLinkin()
	
		return new Linkin(linkin.getName(), linkin.getAge());
	

	@Override
	public String toString()
	
		return getClass().getSimpleName() + " [name=" + name + ", linkin=" + linkin + "]";
	

	public static void main(String[] args)
	
		Linkin linkin = new Linkin("NightWish1", 25);
		LinkinPark linkinPark = new LinkinPark("LinkinPark", linkin);
		System.out.println(linkinPark);

		linkin.setAge(24);
		linkin.setName("NightWish2");
		System.out.println(linkinPark);
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish1, age=25]]
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish2, age=24]]
	



class Linkin

	private String name;
	private Integer age;

	public Linkin(String name, Integer age)
	
		super();
		this.name = name;
		this.age = age;
	

	public String getName()
	
		return name;
	

	public void setName(String name)
	
		this.name = name;
	

	public Integer getAge()
	
		return age;
	

	public void setAge(Integer age)
	
		this.age = age;
	

	@Override
	public String toString()
	
		return getClass().getSimpleName() + " [name=" + name + ", age=" + age + "]";
	



缓存实例的不可变类

不可变类的实例状态不可改变,可以很方便的被多个对象所共享。如果程序经常需要使用相同的不可变实例,则应该考虑缓存这种不可变类的实例。毕竟重复创建相同的对象没

有太大的意义,而且加大系统开销。如果可能,应该将已经创建的不可变类的实例进行缓存。

缓存是软件设计中一个非常有用的模式,缓存的实现方式也有很多种,不同的实现方式可能存在较大的性能差别,关于缓存的性能问题和实现方式我会在后面的博客中整理一个

分类,此处不做赘述。

OK,前面我已经使用了不可变类LinkinPark,现在我自己用一个数组写一个缓存池,从而实现一个缓存LinkinPark实例的缓存池

当然也可以直接在LinkinPark类中写缓存,这样子将实现一个缓存自己实例的不可变类。

public class LinkinParkCache

	// 定义一个数组+一个下标+数组最大容量
	private static int POS_INDEX = 0;
	private static final int MAX_SIZE = 10;
	private static final LinkinPark[] cache = new LinkinPark[MAX_SIZE];

	// 定义一个name标示用来重写hashCode方法
	private final String name;

	private LinkinParkCache(String name)
	
		this.name = name;
	

	public String getName()
	
		return name;
	

	public static LinkinPark valueOf(String name)
	
		// 1,循环获取缓存的实例
		for (int i = 0; i < cache.length; i++)
		
			if (cache[i] != null && cache[i].getName().equals(name))
			
				return cache[i];
			
		
		// 2,循环结束后没有找见实例,则向缓存中添加
		if (POS_INDEX == MAX_SIZE)
		
			cache[0] = new LinkinPark(name, new Linkin("LinkinPark", 25));
			POS_INDEX = 1;
		
		else
		
			cache[POS_INDEX++] = new LinkinPark(name, new Linkin("LinkinPark", 25));
		
		return cache[POS_INDEX - 1];
	

	@Override
	public boolean equals(Object obj)
	
		if (this == obj)
		
			return true;
		
		if (obj != null && obj.getClass() == this.getClass())
		
			LinkinPark linkinPark = (LinkinPark) obj;
			return linkinPark.getName().equals(this.getName());
		
		return false;
	

	@Override
	public int hashCode()
	
		return name.hashCode();
	

	public static void main(String[] args)
	
		LinkinPark linkin = LinkinParkCache.valueOf("林肯的缓存池");
		LinkinPark linkinPark = LinkinParkCache.valueOf("林肯的缓存池");
		// 下面代码输出true,使用了缓存
		System.out.println(linkin == linkinPark);
	



/**
 * 不可变类
 * 
 * @author LinkinPark
 */
class LinkinPark


	private final String name;
	private final Linkin linkin;

	public LinkinPark(String name, Linkin linkin)
	
		super();
		this.name = name;
		this.linkin = new Linkin(linkin.getName(), linkin.getAge());
	

	public String getName()
	
		return name;
	

	public Linkin getLinkin()
	
		return new Linkin(linkin.getName(), linkin.getAge());
	

	@Override
	public String toString()
	
		return getClass().getSimpleName() + " [name=" + name + ", linkin=" + linkin + "]";
	

	public static void main(String[] args)
	
		Linkin linkin = new Linkin();
		linkin.setName("NightWish1");
		linkin.setAge(25);
		LinkinPark linkinPark = new LinkinPark("LinkinPark", linkin);
		System.out.println(linkinPark);

		linkin.setAge(24);
		linkin.setName("NightWish2");
		System.out.println(linkinPark);
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish1, age=25]]
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish1, age=25]]
	



/**
 * 不可变类中的引用类型变量的定义
 * 
 * @author LinkinPark
 */
class Linkin

	private String name;
	private Integer age;

	public Linkin()
	
		super();
	

	public Linkin(String name, Integer age)
	
		super();
		this.name = name;
		this.age = age;
	

	public String getName()
	
		return name;
	

	public void setName(String name)
	
		this.name = name;
	

	public Integer getAge()
	
		return age;
	

	public void setAge(Integer age)
	
		this.age = age;
	

	@Override
	public String toString()
	
		return getClass().getSimpleName() + " [name=" + name + ", age=" + age + "]";
	



以上是关于不可变类的主要内容,如果未能解决你的问题,请参考以下文章

11.scala案例类(Case Classes)

可变类还是不可变类?

Java 不可变类

java中一个类是不可变类的条件是啥?求详细解答。

JAVA不可变类(immutable)机制与String的不可变性

不可变类