缓存不可变类

不可变类

  • 不可变类的意思是创建该类实例后,该实例的实例变量是不可改变的

    • Java中的8个包装类java.lang.String类都是不可变类,当创建它们的实例后,其实例变量不可改变。
  • 如果需要创建自定义的不可变类,遵循以下规则:

    • 使用privatefinal修饰符来修饰该类的成员变量

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

    • 仅为该类的成员变量提供getter方法不要为该类的成员变量提供setter方法,因为普通方法无法修改final修饰的成员变量

    • 如果有必要,重写hashCode()和equals()。

      • equals()方法根据关键成员变量来作为两个对象是否相等的标准
      • 应保证两个用equals()方法判断为相等的对象的hashCode()也相等

      例如String类对象里的字符序列作为相等的标准,其hashCode()方法也是根据字符序列计算得到的。

      String类中的equals()方法源码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      public boolean equals(Object anObject) {
      if (this == anObject) {
      return true;
      }
      if (anObject instanceof String) {
      String anotherString = (String)anObject;
      int n = value.length;
      if (n == anotherString.value.length) {
      char v1[] = value;
      char v2[] = anotherString.value;
      int i = 0;
      while (n-- != 0) {
      if (v1[i] != v2[i])
      return false;
      i++;
      }
      return true;
      }
      }
      return false;
      }

      String类中的hashCode()方法的源码:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      public int hashCode() {
      int h = hash;
      if (h == 0 && value.length > 0) {
      char val[] = value;

      for (int i = 0; i < value.length; i++) {
      h = 31 * h + val[i];
      }
      hash = h;
      }
      return h;
      }
  • 可变类的含义是该类的实例变量可变的。大部分创建的类都是可变类,特别是JavaBean,因为总是为其实例变量提供了setter和getter方法。

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

  • 如果需要设计一个不可变类,尤其要注意其引用类型的成员变量,如果引用类型的成员变量的类是可变的,就必须采取必要的措施来保护该成员变量所引用的对象不会被修改,这样才能创建真正的不可变类。

缓存不可变类

  • 不可变类的实例状态不可改变,可以很方便的被多个对象共享。如果程序经常使用相同的不可变实例,就应该考虑缓存这种不可变类的实例。毕竟重复创建相同的对象没有意义,而且会加大系统开销

  • 数组创建缓存池,用于缓存实例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    class CacheImmutale
    {
    private static int MAX_SIZE = 10;
    // 使用数组来缓存已有的实例
    private static CacheImmutale[] cache
    = new CacheImmutale[MAX_SIZE];
    // 记录缓存实例在缓存中的位置,cache[pos-1]是最新缓存的实例
    private static int pos = 0;
    private final String name;
    private CacheImmutale(String name)
    {
    this.name = name;
    }
    public String getName()
    {
    return name;
    }
    public static CacheImmutale valueOf(String name)
    {
    // 遍历已缓存的对象,
    for (int i = 0 ; i < MAX_SIZE; i++)
    {
    // 如果已有相同实例,直接返回该缓存的实例
    if (cache[i] != null
    && cache[i].getName().equals(name))
    {
    return cache[i];
    }
    }
    // 如果缓存池已满
    if (pos == MAX_SIZE)
    {
    //先进先出
    // 把缓存的第一个对象覆盖,即把刚刚生成的对象放在缓存池的最开始位置。
    cache[0] = new CacheImmutale(name);
    // 把pos设为1
    pos = 1;
    }
    else
    {
    // 把新创建的对象缓存起来,pos加1
    cache[pos++] = new CacheImmutale(name);
    }
    return cache[pos - 1];

    }
    public boolean equals(Object obj)
    {
    if(this == obj)
    {
    return true;
    }
    if (obj != null && obj.getClass() == CacheImmutale.class)
    {
    CacheImmutale ci = (CacheImmutale)obj;
    return name.equals(ci.getName());
    }
    return false;
    }
    public int hashCode()
    {
    return name.hashCode();
    }
    }
    public class CacheImmutaleTest
    {
    public static void main(String[] args)
    {
    CacheImmutale c1 = CacheImmutale.valueOf("hello");
    CacheImmutale c2 = CacheImmutale.valueOf("hello");
    // 下面代码将输出true
    System.out.println(c1 == c2);
    }
    }
  • 是否需要隐藏缓存池类的构造器完全取决于系统需求。盲目乱用缓存也可能导致系统性能下降,缓存的对象会占用系统内存,如果某个对象只使用一次,重复使用的概率不大,缓存该实例就弊大于利;反之,如果某个对象需要频繁地重复使用,缓存该实例就利大于弊。

  • Java中Integer类就采取了上述CacheImmutale类相同的处理策略,如果采用new构造器来创建Integetr对象,则每次返回全新的Integer对象;如果采用valueOf()方法来创建Integer对象,则会缓存该方法创建的对象

  • 由于new构造器方式创建Integer对象不会启用缓存,因此性能较差,所以Java9中已经将该构造器标记为过时,全面采用valueOf()方法创建。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class IntegerCacheTest
    {
    public static void main(String[] args)
    {
    // 生成新的Integer对象
    Integer in1 = new Integer(6);
    // 生成新的Integer对象,并缓存该对象
    Integer in2 = Integer.valueOf(6);
    // 直接从缓存中取出Ineger对象
    Integer in3 = Integer.valueOf(6);
    System.out.println(in1 == in2); // 输出false
    System.out.println(in2 == in3); // 输出true
    // 由于Integer只缓存-128~127之间的值,
    // 因此200对应的Integer对象没有被缓存。
    Integer in4 = Integer.valueOf(200);
    Integer in5 = Integer.valueOf(200);
    System.out.println(in4 == in5); //输出false
    }
    }
    • 由于Integer只缓存-128~127之间的Integer对象,因此两次通过Integer.valueOf(200)方法生成的Integer对象不是同一个。