default メソッド

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

default メソッド とは

Java 8 で導入された interface の default メソッドの使い方と、後方互換性を保ちながら共通実装を提供する仕組みを学び、英語版あいさつメソッドを書いてみよう。

default メソッドとは何か

interface (インターフェース) は、それまでの Java では「抽象メソッドの宣言だけを並べる場所」でした。abstract キーワードがなくても自動的に public abstract になるので、実装を書けるのは class 側だけ、というのが鉄則だったのです。ところが Java 8 から、その鉄則を破る default メソッドという仕組みが追加されました。default を付ければ、interface の中にも本体付きのメソッドを定義できるようになります。

なぜ後から仕様を変えたのか、と疑問に思った人は鋭いです。きっかけは java.util.Collectionjava.util.Liststream() forEach() といったメソッドを追加したかったから、というのが大きな理由です。古い interface にメソッドを足すと、既存の実装クラスが全部コンパイルエラーになってしまうので、default で「未実装でも動く既定の振る舞い」を提供できるようにしたわけです。

このレッスンでは、default メソッドの目的と書き方を押さえ、Greeter という小さな interface に英語版あいさつを実装してみます。function_namegreetEnglish で、英語の Greeter 実装の greet("Java") の結果として "Hello, Java" を返す、というのがゴールです。

diagram (will load when visible)

default の書き方

書き方はとてもシンプルで、interface の中でメソッド宣言の前に default キーワードを付け、{} で本体を書くだけです。public は省略しても自動で public 扱いになります。

Java

public interface Greeter { default String greet(String name) { return "Hello, " + name; } }

この interfaceimplements したクラスは、greet を書かなくても default の実装をそのまま使えます。たとえば EnglishGreeter を作って何もメソッドを書かなくても、new EnglishGreeter().greet("Java")"Hello, Java" を返します。

Java

public class EnglishGreeter implements Greeter { // greet を書かなくても default が使われる }

default メソッドの本体では this が使えますし、同じ interface 内の他の default メソッドや abstract メソッドも呼べます。ただし interface には フィールド (インスタンス変数) を持てないので、状態を持つロジックは書けません。あくまで「引数と他のメソッドから値を組み立てる」純粋関数的な実装に留めるのが基本です。

必要なら実装クラス側で override して上書きもできます。@Override アノテーションを付けると、書き間違いをコンパイラが検出してくれるので付けるのがおすすめです。JapaneseGreeter で「こんにちは, Java」を返すようにしたいときはこう書きます。

Java

public class JapaneseGreeter implements Greeter { @Override public String greet(String name) { return "こんにちは, " + name; } }

abstract class との違い

「本体付きメソッドを持てる」と聞くと、abstract class (抽象クラス) と何が違うのか気になるはずです。一見似ていますが、根本的に違う点が 3 つあります。

  • interfaceフィールド を持てない (定数 public static final だけは持てる)。abstract class はインスタンス変数を自由に持てる
  • クラスは extends で 1 つの親クラスしか継承できないが、implements は複数の interface を同時に実装できる
  • interface のメソッドはデフォルトで publicabstract classprotected や package-private を自由に使える

つまり「状態」を持ちたいなら abstract class、「振る舞いの契約」だけを多重に組み合わせたいなら interface + default、という使い分けになります。Comparable Iterable AutoCloseable のような Java の標準 API も、振る舞いの契約として interface を使っているのを思い出してください。

多重継承の衝突解決

default メソッドの強力さと引き換えに、多重継承 の問題が再びやってきます。2 つの interface が同じシグネチャの default メソッドを持っていて、それを同時に implements するとどうなるか、というやつです。

Java

public interface Greeter { default String greet(String name) { return "Hello, " + name; } } public interface Farewell { default String greet(String name) { return "Bye, " + name; } } public class Bot implements Greeter, Farewell { // コンパイルエラー: greet の実装が衝突 }

この場合、Java は「どちらを使えばいいか分からない」とエラーにします。解決方法は 実装クラス側で greetoverride して明示する ことです。元の default を呼びたいときは Greeter.super.greet(name) のように interface 名.super.メソッド名(...) の構文を使います。

Java

public class Bot implements Greeter, Farewell { @Override public String greet(String name) { return Greeter.super.greet(name); } }
diagram (will load when visible)

解決順序のルールは「クラス優先 → 親クラス優先 → 具体的な interface 優先」です。クラス階層に同じシグネチャがあれば、そちらが必ず勝ちます。interfacedefault は、あくまでクラスが提供しないときの「フォールバック実装」と覚えるとイメージしやすいです。

interfacestatic メソッド

default と並んで、Java 8 から interface の中に static メソッドも書けるようになりました。static メソッドは interface 名から直接呼ぶユーティリティで、実装クラスからは継承されません。

Java

public interface Greeter { default String greet(String name) { return "Hello, " + name; } static Greeter english() { return new EnglishGreeter(); } } // 呼び出し側 Greeter g = Greeter.english();

List.of(1, 2, 3) Map.of("a", 1) Comparator.comparing(...) などはこの仕組みで提供されています。ファクトリーメソッドヘルパー関数interface に同居させたいときに便利です。

static メソッドは interface.メソッド名 形式でしか呼べないので、Greeter.english() のように書きます。実装クラスのインスタンスからは呼べないことに注意してください (new EnglishGreeter().english() はコンパイルエラー)。

よくある間違い

ここで、default メソッドを使い始めた人がよくハマる落とし穴を 3 つ紹介します。

  • default だらけで interface が肥大化する — 便利だからといって、振る舞いの 90% を default で書いてしまうと、もはや interface ではなく abstract class のような重い存在になります。interface は「実装すべきメソッドの契約」を表すのが基本なので、default は「後から追加する後方互換ヘルパー」「複数の実装が共通して使う簡単な定型処理」に絞るのがコツです
  • 多重継承の衝突を放置する — 2 つの interface が同じ default を持つと、コンパイルエラーが必ず出ます。Greeter.super.greet(name) 構文を覚えて、衝突したときは実装クラス側で明示的に override する習慣を付けましょう。エラーメッセージは class X inherits unrelated defaults のように出ます
  • abstract class と混同して フィールド を持たせようとするinterface には public static final の定数しか書けません。private int count; のようにフィールドを書こうとするとコンパイルエラーになります。状態を持ちたいなら abstract class を選ぶか、実装クラス側にフィールドを置いてください

もう 1 つ細かい話として、default メソッドを private にできるのは Java 9 以降だ、というのも覚えておくと便利です。interface 内の private メソッドは、複数の default メソッドから共通して呼ぶヘルパー関数を切り出すために使えますが、外からも実装クラスからも見えない完全な内部ヘルパーになります。

振る舞いの設計に迷ったときは「契約だけなら interface + abstract メソッド」「契約 + 共通ヘルパーなら interface + default」「状態を持つテンプレートなら abstract class」と覚えておくと、ほとんどのケースで迷わなくなります。

やってみよう

それでは課題に取り組みましょう。Solution.greetEnglish(String name) を書いて、"Hello, " + name を返すようにします。やることは次のとおりです。

  1. Solution の中に interface Greeter を定義し、default String greet(String name)"Hello, " + name を返す形で書く
  2. Greeterimplements する EnglishGreeter クラス (中身は空でも OK) を用意する
  3. greetEnglish(String name) の中で EnglishGreeter のインスタンスを作り、greet(name) を呼んでその戻り値を return する

テストでは greetEnglish("Java")"Hello, Java" 以外に greetEnglish("Alice")"Hello, Alice" のような別の引数でも呼ばれます。"Hello, Java" のように ハードコード していると別の名前で fail するので、必ず default 側の連結ロジックを通すようにしてください。スペースの位置も大事で、"Hello,Java" のようにカンマ後のスペースを抜くと比較に失敗します。

慣れてきたら、JapaneseGreeter を追加して "こんにちは, " + name を返すように override してみたり、2 つ目の interface を作って default を衝突させて Greeter.super.greet(name) で解決する練習をしてみてください。次のレッスンでは、functional interfaceラムダ式 を組み合わせて、Greeter のような小さな interface を 1 行で実装する書き方に踏み込んでいきます。

よくある質問

Q. 中級の内容は実務でどれくらい使いますか?

A. Collection(List/Map/Set)、Stream、例外処理、ジェネリクスは毎日のように登場します。Date/Time API、Files、try-with-resources も実務で頻出するため、本コースの内容は実プロジェクトでそのまま役立ちます。

Q. Stream と for ループはどっちで書くべき?

A. 可読性で選んでください。filter + map + collect が綺麗にハマるなら Stream、副作用や複雑な分岐が多いなら for ループの方が読みやすいです。一律にどちらを使うべきという正解はなく、チームのコーディング規約に合わせるのが現実的です。

Q. 次のステップでは何を学ぶべきですか?

A. 中級の基礎が固まったら、Spring Boot で Web API を作る、JUnit でテストを書く、Maven/Gradle でビルドを管理する、といった実プロジェクトのスキルに進むと効果的です。OSS のコードを読む経験も大きく成長を促します。

次のレッスン

次は instanceof と型パターン で、instanceof と型パターン を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. default の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. default とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. Solution の中に interface Greeter を定義し、default String greet(String name) を実装すること
  2. Greeterimplements する EnglishGreeter クラスを用意すること (中身は空でよい)
  3. greetEnglish(String name) の中で EnglishGreeter のインスタンスを生成し、greet(name) の戻り値を return すること

入出力例

test-cases.txt

greetEnglish("Java")"Hello, Java" greetEnglish("Alice")"Hello, Alice" greetEnglish("")"Hello, "

ヒント

main.java
main.java
学習モード

メモ

default メソッド

⌘S で保存