实现不可变类如何禁止子类化?

实现不可变类时要求禁止子类化。本文先讲禁止子类化的方式,最后解释为何要禁止子类化。java

JDK版本:oracle java 1.8.0_102git

如何禁止子类化

经常使用姿式

最简单的手段是将类声明为final,如String、Integer等经常使用的值类。但这样缺少灵活性:不只禁止了用户的子类化,开发者也没法利用子类化减小编码工做。github

尽管这种手段彻底没有变通,倒是咱们使用最多的一种。只有你须要上述灵活性的时候,再去考虑下述方式。web

不经常使用但你须要掌握的姿式

还有一种不经常使用,但更灵活的方法:静态工厂方法+私有构造器。oracle

彻底禁止子类化(效果相似于final修饰)

若是但愿Parent3彻底不可子类化,除了用final修饰Parent3之外,还能够用private修饰其全部构造方法,这样Child3因没法调用父类的构造方法,也没法经过编译:ide

public class Parent3 {
  private Parent3() {
  }
}

// 没法调用父类的构造方法,所以没法经过编译,即Parent3没法子类化
public class Child3 extends Parent3 {
  private Child3() {
    super();
  }
}

经过静态工厂方法构造Parent3的实例。svg

更灵活的子类化限制

可是,若是放松Parent3构造方法的访问权限, 咱们还能获得更灵活的子类化限制。好比容许包级私有的子类化:this

public class Parent5 {
  Parent5() {
  }
}

// 只要Child5与Parent5定义在同一个包内,就能够子类化
public class Child5 extends Parent5 {
  Child5() {
    super();
  }
}

须要注意的是,Java的覆写机制要求覆写方法(Child5())的权限不低于被覆写方法(Parent5())。这形成了一种危险:若是将Child5()声明为public,那么Child5变得可子类化,间接实现了Parent5的子类化编码

PS:以上实现不能定义为内部类,若是有疑问,你须要回忆private的语义。spa

为何要禁止子类化

若是容许子类化,在发生多态的状况下,经过覆写子类的访问器,可让子类冒充父类,让父类“看起来”是可变的:

public class ImmutableParent {
    private final int imVal;

    public ImmutableParent(int imVal) {
      this.imVal = imVal;
    }

    public int getImVal() {
      return imVal;
    }
  }

  …

  public class MutableChild extends ImmutableParent {
    private int mVal;

    public MutableChild(int imVal) {
      super(imVal);
      mVal = imVal;
    }

    // 覆写父类的不可变字段 imVal 的访问器, 发生多态时子类实例就能假装成父类实例, 让用户访问可变字段 mVal
    @Override
    public int getImVal() {
      return mVal;
    }

    // 而假装者撕下面具时(改用子类引用), 就能随意修改可变字段 mVal
    public void setImVal(int mVal) {
      this.mVal = mVal;
    }
  }

总结

上述方式从语法层面保证了不可变类的禁止子类化。尽管咱们能经过其余办法在多态访问时判断当前对象是父类仍是子类的实例,但哪一种方式更恰当呢?显然是前者,两个理由:

  1. 用合适的方式作合适的事
  2. 语法优于约定

本文连接:实现不可变类时如何禁止子类化?
做者:猴子007
出处:https://monkeysayhi.github.io
本文基于 知识共享署名-相同方式共享 4.0 国际许可协议发布,欢迎转载,演绎或用于商业目的,可是必须保留本文的署名及连接。