• JAVA中如何写一个不可变类


    Effective Java 第39条中提到:必要时进行保护性拷贝

    书中给到的一个例子,总结一句就是:成员变量不要被外部引用直接赋值,而是拷贝之后的赋值。具体看下面例子。

     1 public class Period {
     2     private final Date start;
     3     private final Date end;
     4     
     5     public Period(Date start, Date end){
     6         if(start.compareTo(end) > 0){
     7             throw new IllegalArgumentException(start + " after " + end);
     8         }
     9         this.start = start;
    10         this.end = end;
    11     }
    12 
    13     public Date getStart() {
    14         return start;
    15     }
    16 
    17     public Date getEnd() {
    18         return end;
    19     }
    20     
    21     public static void main(String[] args) {
    22         Date start = new Date();
    23         Date end = new Date();
    24         Period p = new Period(start, end);
    25         end.setYear(78);//这里改变了外部引用指向的值
    26         System.out.println(p.getStart());
    27         System.out.println(p.getEnd());
    28     }
    29     
    30     
    31 
    32 }
    View Code

     从代码中我们可以看到,成员变量是被final修饰的,值是不可变的,且只有一个构造函数初始化它们的值,而构造函数里面又有对start,end这两个进行判断,即不让end<start
    但是,构造函数中,对成员变量被外部引用直接赋值。因为传入的是引用,所以这个被final修饰的成员变量的值,不变的是引用,但是这个外部引用所指向的值还是可变的。

    从代码25行可以看出,外部引用指向的值被修改了,而成员变量end与外部引用end都是指向同一个内存的,所以成员变量end指向的值也变了。我们就可以很简单的把end<start

    这样代码就出现了bug.

    解决这个BUG的方法就是:把外部引用拷贝之后赋值

    代码修改如下

     1 public Period(Date start, Date end){
     2          this.start = new Date(start.getTime());//把外部引用拷贝后再赋值
     3          this.end = new Date(start.getTime());
     4          if(start.compareTo(end) > 0){
     5              throw new IllegalArgumentException(start + " after " + end);
     6          }
     7  /*******************修改前代码如下*****************/
     8  //        if(start.compareTo(end) > 0){
     9  //            throw new IllegalArgumentException(start + " after " + end);
    10  //        }
    11  //        this.start = start;//不要被外部引用直接赋值
    12  //        this.end = end;
    13      }
    View Code

     改到这里,还有一个问题。我们还把成员变量的引用通过getter直接暴露出来了。这还是会出现问题

    因为我们还可以通过getter获取成员变量的引用,再去改变它内部的值

    代码如下

    1 Date start = new Date();
    2         Date end = new Date();
    3         Period p = new Period(start, end);
    4         System.out.println(p.getStart());
    5         System.out.println(p.getEnd());
    6         //通过上面的改造,我们已经不能通过改变end.setYear();去影响成员变量的值了,但是下面这句还是可以改变成员变量的值,
    7         p.getStart().setYear(88);//这里getter返回的是一个引用,我们可以通过引用去改变成员变量的值
    View Code

    解决这个BUG的方法 就是:getter返回可变内部域的保护性拷贝

    把getter做如下修改即可

    1 public Date getStart() {
    2         return new Date(this.start.getTime());//正确做法:返回可变内部域的保护性拷贝
    3         //return this.start;//错误做法:直接返回引用
    4     }
    5 
    6     public Date getEnd() {
    7         return new Date(this.end.getTime());//正确做法:返回可变内部域的保护性拷贝
    8         //return this.end;//错误做法:直接返回引用
    9     }
    View Code

     总结

     1)对于赋值,我们不要把外部引用直接赋值给成员变量

     2)对于getter,我们不要把成员变量的引用直接返回

  • 相关阅读:
    使用ActivityGroup来切换Activity和Layout
    Fragment
    [Java]LeetCode297. 二叉树的序列化与反序列化 | Serialize and Deserialize Binary Tree
    [Swift]LeetCode298. 二叉树最长连续序列 $ Binary Tree Longest Consecutive Sequence
    [Swift]LeetCode296. 最佳开会地点 $ Best Meeting Point
    [Swift]LeetCode294. 翻转游戏之 II $ Flip Game II
    [Swift]LeetCode293. 翻转游戏 $ Flip Game
    [Swift]LeetCode291. 单词模式 II $ Word Pattern II
    [Postman]发出SOAP请求(18)
    [Postman]生成代码段(17)
  • 原文地址:https://www.cnblogs.com/baron89/p/3081249.html
Copyright © 2020-2023  润新知