【Java Silver黒本】7章のつまづいたところまとめ

「サブ」と言ったら感覚的に「副」みたいなイメージだけど、Javaのサブクラスは継承元クラスを拡張するイメージ。

インターフェース

  • インターフェースのメンバは全てpublicにしなければならない
  • インターフェースのフィールドは自動的にstatic finalになる
  • インターフェースは多重継承が可能
  • インターフェースの抽象メソッドは具象クラスで全て実装すればよい
interface SampleInterface1 {
  String str1 = "hoge";
  public String str2 = "fuga";

  // インターフェース宣言の中で
  // public以外のアクセス修飾子を使用するとコンパイルエラーになる
  protected String str3 = "foo"; // コンパイルエラー

  void method1();
  public void method2();

  // インターフェース宣言の中で
  // public以外のアクセス修飾子を使用するとコンパイルエラーになる
  protected void method3(); // コンパイルエラー
}

interface SampleInterface2 {

}

// インターフェースは多重継承が可能
interface SampleInterface3
    extends SampleInterface1, SampleInterface2 {

}

// インターフェースを実装した中傷クラスでは
// インターフェースのメソッドを全てオーバーライドする必要はなく、
// 実装の一部をサブクラスに任せてもよい
abstract class SampleAbstractClass
    implements SampleInterface3 {
  // method1のみオーバーライドして
  // method2はサブクラスで定義する
  @Override
  public void method1() {

  }
}

public class Main extends SampleAbstractClass {
  public static void main(String[] args) {
    Main app = new Main();
  }

  public Main() {
    // インターフェースのメンバは全てpublic staticとなっている
    System.out.println(SampleInterface1.str1); // => hoge
  }

  @Override
  public void method2() {

  }
}

継承先に同名フィールドが存在する場合

  • フィールドを直接参照した場合は、型のクラスに定義された値を使う
  • メソッド経由でフィールドにアクセスする場合は、メソッドの指示に従う
class ClassA {
  int number = 10;

  public int getNumber() {
    return this.number;
  }
}

class ClassB extends ClassA {
  int number = 20;

  public int getNumber() {
    return this.number;
  }
}

public class Main {
  public static void main(String[] args) {
    Main app = new Main();
  }

  public Main() {
    ClassA hoge = new ClassB();

    // フィールドを直接参照した場合は、型のクラスに定義された値を使う
    System.out.println(hoge.number); // => 10

    // メソッド経由でフィールドにアクセスする場合は、メソッドの指示に従う
    System.out.println(hoge.getNumber()); // => 20
  }
}

継承関係にあるクラスのキャスト

  • BクラスがAクラスを継承しているとき、BインスタンスをA型にキャストすることは可能だが、逆は不可
class ClassA {

}

class ClassB extends ClassA {

}

public class Main {
  public static void main(String[] args) {
    Main app = new Main();
  }

  public Main() {
    ClassA a = new ClassA();
    ClassB b = new ClassB();

    // ClassBはClassAを継承しているため、ClassAの情報を持っている
    // そのためClassBのインスタンスはClassA型にキャストできる
    a = (ClassA)b;

    // しかし、ClassAは、クラス定義時にClassBの情報を与えられていないので
    // 互換性があるかどうかわからない
    // そのため、ClassAのインスタンスはClassB型にキャストできず
    // ClassCastExceptionがなげられる
    // b = (ClassB)a;
  }
}

サブクラスのコンストラクタ

  • サブクラスのコンストラクタ内にsuper()を記述しなかった場合、コンパイラによって自動的に追加される
  • super()の記述はコンストラクタの先頭行でなくてはならない
class ClassA {
  public ClassA() {
    System.out.println("A");
  }
}

class ClassB extends ClassA {
  public ClassB() {
    System.out.println("B");
  }

  public ClassB(int i) {
    this();
    
    System.out.println("B: " + i);

    // 先頭行以外にsuper()を書くとコンパイルエラーになる
    // super();
  }
}

public class Main {
  public static void main(String[] args) {
    Main app = new Main();
  }

  public Main() {
    new ClassB(0);
    // => A
    // => B
    // => B: 0
  }
}

オーバーライドのルール

  • シグニチャが同じであること
  • 戻り値型は同じか、サブクラスであること
  • アクセス修飾子は同じか、より緩いものであること

※クラス内のデフォルト修飾子がpackage privateであるのに対して、インターフェース内のデフォルト修飾子はpublicであることに注意。

class ClassA {

}

class ClassB extends ClassA {

}

abstract class ClassC {
  protected abstract ClassA method(int i);
}

class classD extends ClassC {
  public ClassB method(int i) {
    return new ClassB();
  }

  // 継承元メソッドのシグニチャと同じでなければならない。
  // 次のコードはコンパイルエラー
  @Override
  protected ClassA method(String s) {
    return new ClassA();
  }

  // 戻り値の型は継承元メソッドの戻り値の型と同じかサブクラスでなければならない。
  // 次のコードはコンパイルエラー
  @Override
  protected void method(int i) {

  }

  // 継承元メソッドのアクセス修飾子より緩い宣言でなければならない
  // 次のコードはコンパイルエラー
  @Override
  private ClassA method(int i) {
    return new ClassA();
  }
}

public class Main {
  public static void main(String[] args) {
    Main app = new Main();
  }
}