PR

【Java】静的バインディングと動的バインディングをわかりやすく

Java

この記事のゴール: 「変数は型で決まり、メソッドは場合によって実体で決まる」——この 1 行を実感として理解する。

Javaにおける「静的バインディング」と「動的バインディング」という言葉は、初心者にとって少しとっつきにくく感じられるかもしれません。これらは継承が関わるときにだけ登場する話です。

たとえば、Animal a = new Dog(); のように、親クラスの変数に子クラスのインスタンスを代入したとき、Javaは「変数を見るべきか?」「実体を見るべきか?」という判断をしなければなりません。このとき、

  • 変数(フィールドや static メソッド)は「型(参照型)」で判断される → 静的バインディング
  • インスタンスメソッド(オーバーライドされる可能性があるもの)は「中身(実体型)」で判断される → 動的バインディング

というルールが働くのです。

つまり、継承+多態性(ポリモーフィズム)の文脈で初めて「バインディング」という言葉が意味を持ちます。この記事ではその仕組みを、丁寧に・図解やサンプルコード付きで解説していきます。

動的バインディングと静的バインディングの違いは、「メソッドや変数の呼び先が、いつ決まるか」です。

  • 静的バインディング:コンパイル時に決まる(例:変数、static メソッド)
  • 動的バインディング:実行時に決まる(例:オーバーライドされたメソッド)

たとえば、Animal a = new Dog(); のとき、a.name は静的に Animal のものが使われ、a.speak() は動的に Dog のものが呼ばれる、というのがこのページで解説したい内容です。

バインディング決まるタイミング決め手主な対象
静的 (static)コンパイル時(プログラムを実行用に変換するとき)変数宣言の フィールド, static / private / final メソッド, オーバーロード
動的 (dynamic)実行時(プログラムが動いている最中)オブジェクトの 実体オーバーライドしたインスタンスメソッド

ポイント 覚え方

  • Field と Static は Static バインディング。
  • 残ったインスタンスメソッドは動的バインディング。

これだけでも問題は解けます。以下で仕組みをゆっくり深掘りしましょう。

スポンサーリンク

静的バインディング ― 型を見て早決め

静的バインディングとは、「コンパイル時に呼び先が決まる処理」のことです。
Javaでは次のようなケースが静的バインディングになります。

  • フィールド(変数)
  • static メソッド
  • privatefinal なメソッド
  • オーバーロード(同名で引数違い)

これらは変数の型(参照型)を見てコンパイル時に呼び先が決まるので、実行時に変わることはありません。

class Animal {
    String kind = "Animal";
    static void hello() { System.out.println("Hello from Animal"); }
}
class Dog extends Animal {
    String kind = "Dog";
    static void hello() { System.out.println("Hello from Dog"); }
}
Animal a = new Dog();
System.out.println(a.kind); // ①
a.hello();                  // ②
  • kind は変数なので 型 Animal を参照 → "Animal"。
  • hello()static メソッドなので型を参照 → "Hello from Animal"。

なぜコンパイル時に決められる?

コンパイラは上のコードを見ると「kindAnimal で offset いくつ」「hello()Animal のメモリアドレスいくつ」とバイトコードに固定します。プログラムが動くときに変更される可能性がないため最速です。

動的バインディング ― 実体を見て後決め

動的バインディングとは、「実行時に呼び先が決まる処理」のことです。
Java では、オーバーライドされたインスタンスメソッドに対して動的バインディングが行われます。

これは、変数の型(参照型)ではなく、実際の中身(実体型)を見て、実行時にどのメソッドを使うかを判断する仕組みです。

class Animal {
    void speak() { System.out.println("..."); }
}

class Dog extends Animal {
    @Override void speak() { System.out.println("Bowwow"); }
}

Animal a = new Dog();
a.speak(); // → Bowwow(実体が Dog なので)
  • speak() はオーバーライド可能なインスタンスメソッド。
  • 実行時に JVM が「実体は Dog だ」と判断し、Dog 版の speak() を呼びます。

このように、実行時に実体のクラスを探して処理を決めるため、「多態性(ポリモーフィズム)」が実現できます。
Java の柔軟性の根幹を支える仕組みです。

ざっくり内部図

Animal a → ヒープ内 Dog オブジェクト → Dog のメソッド表 (v‑table) → Dog.speak()

JVM はこのメソッド表を O(1) で検索するので速度ロスは最小。

初期化順序トラップ(null が出る理由)

class Parent {
  Parent() { show(); }
  void show() { System.out.println("Parent"); }
}
class Child extends Parent {
  String name = "Child";
  @Override void show() { System.out.println(name); }
}
new Child(); // → null
  1. Parent コンストラクタ内で show() 呼び出し。
  2. 動的バインディングで Child の show() が走る。
  3. しかし name まだ初期化前 → null

試験では 「なぜ null になる?」 を説明できれば OK です。

オーバーロードは静的、ボクシングはその次

static void f(int x)     { System.out.println("int"); }
static void f(Integer x) { System.out.println("Integer"); }
static void f(int... xs) { System.out.println("varargs"); }

f(5);                // int  ← 一番ぴったり
f(Integer.valueOf(5)); // Integer

可変長引数は “最後の手段” なので出番は少なめ。

タイトルとURLをコピーしました