Think Twice
IT技術メモ | Javaのメモ
Created: 2012-05-03 / Updated: 2021-02-23

Javaのenumは継承できないけどインタフェースが継承できる


当メモは2012-05-03に投稿されたものを加筆修正し、再掲したものです。

目次


はじめに

Javaのenumって便利ですよね。enumを使い続けていくとグルーピングしたくなってくる事があると思いますが、継承はできないので、そんな場合はインタフェースを実装してやるといいようです。

インタフェース

インタフェース
Copy
interface Colored {
    String getColor();
}

まず、こんな感じのenumに付けるインタフェースを用意します。Coloredは色付けされたものという意味で、#getColor()すると、色を文字列で取得できるイメージです。

enum定義

enum定義
Copy
enum Food implements Colored {
    rice { @Override public String getColor() { return "白"; } }, // こめ
    bread { @Override public String getColor() { return "白と茶"; } }, // ぱん
    noodle { @Override public String getColor() { return "白"; } }, // めん
    ;
}

enumの定義時にインタフェースを実装してあげます。これは直接実装していってますが、以下のようにプライベートコンストラクタで値を受け取る方法でも可能です。

enum定義 (プライベートコンストラクタ版)
Copy
enum Animal implements Colored {
    cat("白と黒と茶"), // ねこ
    tiger("黄と黒"), // とら
    lion("茶"), // らいおん
    goat("白"), // やぎ
    sheep("白"), // ひつじ
    ;
    private String color;
    private Animal(String color) { this.color = color; }
    @Override public String getColor() { return this.color; }
}

使ってみる

Colored型で取得
Copy
Colored colored1 = Animal.cat;
Colored colored2 = Food.rice;

これで、定義したenumはColoredとしても扱えるようになりました。あとはお好きな場面でColored型として利用するだけです。

補足

今回作ったインタフェースのColoredは#getColor()メソッドしか定義していませんので、enumにもとからあるメソッド(#name()とか)は当然の事ながら使えません。なので、必要に応じてinterfaceの方に加えておくとよいでしょう。

#nameメソッドの追加
Copy
interface Colored {
    String getColor();
    String name(); // ← 追加!
}

こうしておけば、enumはもとから#name()は持っているので、そのままenum側のコードには何も手を加えずに、Coloredとしても#name()を利用できるようになります。

テストを書いた

EnumTest.java
Copy
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

import java.util.List;
import java.util.ArrayList;

import org.junit.Test;

public class EnumTest {
  @Test
  public void enumでインタフェースの実装テスト() {
    // 用意したenumは 動物 と 食べ物
    assertThat(Animal.class.getSimpleName(), is("Animal"));
    assertThat(Food.class.getSimpleName(), is("Food"));

    // #values() で全要素が取得可能
    assertArrayEquals(new Animal[]{
        Animal.cat, Animal.tiger, Animal.lion,
        Animal.goat, Animal.sheep
    }, Animal.values());
    assertArrayEquals(new Food[]{
        Food.rice, Food.bread, Food.noodle
    }, Food.values());
    
    // さていよいよインタフェースを利用する。
    // 動物も食べ物も色がついているものを実装しているので、色がついているものとして扱える
    for (Colored whiteThing : new Colored[]{
        Animal.goat, Animal.sheep, Food.rice
     }) {
      assertThat(whiteThing.getColor(), is("白"));
    }
    
    // 茶色いものリストを用意して
    List<Colored> brownThings = new ArrayList<Colored>();
    brownThings.addAll(collectBrown(Animal.values()));
    brownThings.addAll(collectBrown(Food.values()));
    // 茶色いものを確認
    assertArrayEquals(new Colored[]{
        Animal.cat, Animal.lion, Food.bread
    }, brownThings.toArray(new Colored[brownThings.size()]));
  }
  
  /**
   * 茶色だけに絞って返却
   * @param Coloredな配列
   * @return 茶色なColoredのリスト
   */
  private List<Colored> collectBrown(Colored[] coloredList) {
    List<Colored> result = new ArrayList<Colored>();
    for (Colored colored : coloredList)
      if (colored.getColor().contains("茶"))
        result.add(colored);
    return result;
  }
}

// ↓ 色付けされたものを表現するインタフェース
interface Colored {
  String getColor();
}

// ↓ プライベートコンストラクタでごにょごにょする実装
enum Animal implements Colored {
  cat("白と黒と茶"), // ねこ
  tiger("黄と黒"),  // とら
  lion("茶"),      // らいおん
  goat("白"),      // やぎ
  sheep("白"),     // ひつじ
  ;
  private String color;
  private Animal(String color) { this.color = color; }
  @Override public String getColor() { return this.color; }
}

// ↓ 直接インタフェースを実装してしまってもいい
enum Food implements Colored {
  rice   { @Override public String getColor() { return "白";    } }, // こめ
  bread  { @Override public String getColor() { return "白と茶"; } }, // ぱん
  noodle { @Override public String getColor() { return "白";    } }, // めん
  ;
}

参考

関連記事

参考サイト