サンプルコード
5章

5 章

ポリモフィズム

要点

  • ポリモフィズムの動きをするのはインスタンスメソッドのみ
  • インスタンス変数, staticメンバは参照型に依存するため注意
public class Main {
  public static void main(String[] args) {
    Super o = new Sub();
    o.print();                  // 1 Sub
    o.sPrint();                 // 2 Super
    System.out.println(o.name); // 3 Super
  }
}
 
class Super {
  String name  = "Super";
  void print() { System.out.println(name); }
  static void sPrint() { new Super().print(); }
}
class Sub extends Super {
  String name = "Sub";
  void print() { System.out.println(name); }
  static void sPrint() { new Sub().print(); }
}

解説

  1. print() はインスタンスメソッドのため、ポリモフィズムの動きをします。よって Sub が出力
  2. sPrint() は static メソッドのため、参照型に依存するため、13 行目が呼ばます。よって Super が出力
  3. インスタンス変数も参照型に依存します。よって Super が出力

まとめ

  • インスタンスメソッド:注視する箇所は参照先のオブジェクト
    • サンプルコード:3 行目の new Sub();
  • インスタンスメソッド以外:注視する箇所は参照型
    • サンプルコード:3 行目の Super o

super() と コンストラクタ

super() でスーパークラスのコンストラクタを呼び出す際、明示的にコンストラクタを指定しない場合、常に引数なしのコンストラクタが呼ばれるため注意。

コンストラクタを指定しない場合

public class Main {
  public static void main(String[] args) {
    new Sub(); new Sub(555);  // Super() Sub() Super() Sub(int n)
  }
}
 
class Super {
  public Super() { System.out.print("Super() "); }
  public Super(int n) { System.out.print("Super(int n) "); }
}
class Sub extends Super {
  public Sub() { System.out.print("Sub() "); }
  public Sub(int n) {
    System.out.print("Sub(int n) ");
  }
}

コンストラクタを指定する場合

public class Main {
  public static void main(String[] args) {
    new Sub(); new Sub(555); // Super() Sub() Super(int n) Sub(int n)
  }
}
 
// ... 変更なしのため割愛
 
class Sub extends Super {
  public Sub() { System.out.print("Sub() "); }
  public Sub(int n) {
    super(555); // 明示的にコンストラクタを指定
    System.out.print("Sub(int n) ");
  }
}

抽象クラス

要点

  • 抽象メソッドがメンバに存在する場合、クラスは抽象クラスでなければならない
  • 抽象クラスの具象メソッドはオーバーライド任意
  • 抽象メソッドは必ずサブクラスでオーバーライドしないといけない
  • ただし、継承先のサブクラスも抽象クラスの場合はオーバーライドは任意になる
class Super {
  abstract void method();           // 1
}
 
abstract class Super {
  public abstract void method();
}
abstract class Sub extends Super {} // 2

解説

  1. 抽象メソッドがメンバに存在しているため、クラスは抽象クラスでなければならない
  2. 継承先の Sub も抽象クラスのため、継承元の Super の抽象メソッドをオーバーライドしなくてもコンパイルエラーにはならない

インタフェース

要点

// 【1】 インタフェースのアクセス修飾子は 'public' のみ
protected interface MyInter {} // 【😡】 protected は指定できない
 
// 【2】 メンバ変数は強制的に 'public static final' になるため '初期化' を行う必要がある
interface MyInter {
  int num;                     // 【😡】 初期化を行っていない
}
 
// 【3】 抽象メソッドには強制的に 'public abstract' が付く
interface MyInter {
  protected void method();     // 【😡】 抽象メソッドは 'public' のみしか許可されていない
}
 
// 【4】 デフォルトメソッドのアクセス修飾子は 'public' のみ
interface MyInter {
  protected default void method() {}        // 【😡】 指定できるのは public のみ
  default static void method(int num) {}    // 【😡】 デフォルトメソッドでは static修飾子 は使えない
}
 
// 【5】 staticメソッドのアクセス修飾子は、指定されていなければ 'public' が付く
interface MyInter {
  protected static void method(int num) {}  // 【😡】 protectedは使用できない
  private static void method(String str) {} // 【😊】 private は使用可能
}

ひっかけ問題で多いパターン

interface A {
  void x(int num);
}
abstract class B {
  public void x(int num) {} // 1
  public abstract void y();
}
class C extends B implements A {
  public void y() {}        // 2
}

解説

  1. インタフェース A の 抽象メソッド x() は 抽象クラス B で実装されていると解釈されます
  2. よって、具象クラス C では 抽象クラス B の 抽象メソッド y() を実装するだけとなる

List インタフェース

List<String> list = new ArrayList<>(Arrays.asList("foo", "bar", "baz"));

要点

  • 要素の追加や削除ができる
  • 要素の重複ができる

Arrays.asList() について

  • 引数で配列を受け取り、リストを返す
  • 固定サイズのリストに変換する
  • 上書きは可能だが、追加や削除はできない(もし、追加や削除を行った場合は、実行時エラーが発生する)

😡

public class Main {
  public static void main(String[] args) {
    String[] ary = { "foo", "bar", "baz", "hogehoge" };
    List<String> list = Arrays.asList(ary);        // 固定サイズのリスト
    if (list.removeIf(str -> str.length() >= 3)) { // 要素を削除すると実行時エラーが発生
      System.out.println(list);
    }
  }
}

😊

public class Main {
  public static void main(String[] args) {
    String[] ary = { "foo", "bar", "baz", "hoge" };
    List<String> list = new ArrayList<>(Arrays.asList(ary)); // ArrayList に変換することで要素の追加や削除ができる
    if (list.removeIf(str -> str.length() <= 3)) {
      System.out.println(list);
    }
  }
}

ラムダ式

要点

  • ラムダ式から参照されるローカル変数は final(定数) でなければならない
  • そのため、ラムダ式のスコープ外で参照元の値が変更することはできない
public class Main {
  public static void main(String[] args) {
    int i = 555;
    Supplier<Integer> s = () -> i;
    i++; // ラムダ式のスコープ外で参照元の値が変更することはできない
    System.out.println(s.get()); // 555
  }
}