先来科普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
*
*
* 1,属性使用private final修饰
* 2,构造器对属性赋值
* 3,只提供get方法,不提供set方法
* 4,如果有需要就重写equals和hashCode方法
*
*/
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 + "]";
}
}
运行上面的代码,我们也看到了,引用类型的变量导致我创建了一个失败的不可变类。那应该要怎么做呢?看下面代码:
/**
* 引用类型的变量导致不可变类失败
* 所以要针对引用类型的变量做专门的处理
*
*
* 1,构造器中不要直接使用传入的引用类型变量,自己取值然后重新new一次
* 2,引用类型的变量用来存储刚才那个初始化的对象
* 3,防止get方法直接返回刚才那个变量从而改变引用的那个对象,同样的方式处理
*
*
* @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 + "]";
}
}