enumのおさらいメモ

# 概要

Java 1.5より導入されたenumのおさらいメモです。

**環境**

* Windows 10 Professional
* OpenJDK 11

**参考**

* [Class Enum] (https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Enum.html)
* [Class EnumSet] (https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/EnumSet.html)
* [Class EnumMap] (https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/EnumMap.html)

# JDKのおさらい

## Enum

**サンプル**

```java
public enum Fruits {
  Apple,
  Banana,
  Cherry,
  Durian,
  Elderberry,
  Feijoa,
  Guava {
    @Override
    public String toString() {
      return name().toLowerCase();
    }
  };

  @Override
  public String toString() {
    return this.name().toUpperCase();
  }

}
```

### name()

enum定数の名前を返します。(Returns the name of this enum constant)

### ordinal()

列挙定数の序数（宣言された位置）を返します。(Returns the ordinal of this enumeration constant)

(ほとんどはenum定数(enum constant)と表記されていますが、ordinalのところだけ列挙定数(enumeration constant)と表記されています。)

### valueOf(String)

enum定数の名前から対応するenum定数を返します。
valueOf(String)はコンパイラによって合成される暗黙宣言メソッド(public static)です。

### values()

enum typeの全ての定数を返します。
values()はコンパイラによって合成される暗黙宣言メソッド(public static)です。

### equals()

enum定数同士を比較する場合、通常は`==`を使います。
なおEnumクラスのequalsメソッドは下記のようにオーバーライドしています。

```java
public final boolean equals(Object other) {
    return this==other;
}
```

## EnumSet

> 列挙型と一緒に使用するための特殊なSet実装です。enumセット内の要素はすべて、セットの作成時に、指定された単一のenum型から明示的または暗黙的に生成される必要があります。

コンストラクタは無いので次のファクトリメソッドでインスタンスを生成します。

### allOf(Class\<E\>)

指定された列挙型のすべての列挙子を含むEnumSetを作成します。

```java
EnumSet<Fruits> all = EnumSet.allOf(Fruits.class);
System.out.println(all.size());
// → 7
all.forEach(System.out::println);
// → APPLE
// → BANANA
// → CHERRY
// → DURIAN
// → ELDERBERRY
// → FEIJOA
// → guava
```

### noneOf()

指定された列挙型の空のEnumSetを作成します。

```java
EnumSet<Fruits> none = EnumSet.noneOf(Fruits.class);
System.out.println(none.size());
// → 0
none.forEach(System.out::println);
```

### of(E)

指定された列挙子を最初に含むEnumSetを作成します。

```java
EnumSet<Fruits> one = EnumSet.of(Fruits.Durian);
System.out.println(one.size());
// → 1
one.forEach(System.out::println);
// → DURIAN
```

### range(E, E)

指定された2つの列挙子の範囲内のすべての列挙子を含むEnumSetを作成します。

```java
EnumSet<Fruits> range = EnumSet.range(Fruits.Cherry, Fruits.Feijoa);
System.out.println(range.size());
// → 4
range.forEach(System.out::println);
// → CHERRY
// → DURIAN
// → ELDERBERRY
// → FEIJOA
```

## EnumMap

> 列挙型のキーと一緒に使用するための特殊なMap実装です。enumマップ内のキーはすべて、マップの作成時に、指定された単一のenum型から明示的または暗黙的に生成される必要があります。

EnumSetとは違いコンストラクタを使ってインスタンスを生成します。

### EnumMap​(Class\<k\>)

指定した列挙型の空のEnumMapを作成します。

```java
EnumMap<Fruits, String> enumMap = new EnumMap<>(Fruits.class);
enumMap.put(Fruits.Apple, "リンゴ");
enumMap.put(Fruits.Banana, "バナナ");
enumMap.put(Fruits.Cherry, "チェリー");
enumMap.forEach((k, v) -> System.out.println(k + ":" + v));
// → APPLE:リンゴ
// → BANANA:バナナ
// → CHERRY:チェリー
```

### EnumMap​(EnumMap\<K,​? extends V\> m)

指定したEnumMapからEnumMapを作成します。
コードは割愛します。

### EnumMap​(Map\<K,​? extends V\> m)

指定したMapからEnumMapを作成します。

```java
Map<Fruits, String> map = new HashMap<>();
map.put(Fruits.Apple, "リンゴ");
map.put(Fruits.Banana, "バナナ");
map.put(Fruits.Cherry, "チェリー");
EnumMap<Fruits, String> enumMap = new EnumMap<>(map);
enumMap.forEach((k, v) -> System.out.println(k + ":" + v));
// → APPLE:リンゴ
// → BANANA:バナナ
// → CHERRY:チェリー
```

## Enhanced Enums

OpenJDKのProject Amberで検討されているenum拡張([JEP 301: Enhanced Enums] (https://openjdk.java.net/jeps/301))ですが、現在(2019/01)のステータスはOn Hold(保留)ということでまだリリースされていません。

# 定数固有メソッド実装

enum型で抽象メソッドを宣言し定数固有クラス本体(constant-specific class body)で、enum定数ごとにその抽象メソッドをオーバーライドすることを定数固有メソッド実装と呼びます。(「Effective Java 第2版, 項30 int定数の代わりにenumを使用する」より)

以下は、OSSにみる定数固有メソッド実装(constant-specific method implementation)の例です。

## Spring Boot

### org.springframework.boot.convert.DurationStyle

https://github.com/spring-projects/spring-boot/blob/2.1.x/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DurationStyle.java

```java
public enum DurationStyle {

  SIMPLE("^([\\+\\-]?\\d+)([a-zA-Z]{0,2})$") {
    @Override
    public Duration parse(String value, ChronoUnit unit) {
      // ...省略...
    }
    @Override
    public String print(Duration value, ChronoUnit unit) {
      // ...省略...
    }
  },
  ISO8601("^[\\+\\-]?P.*$") {
    @Override
    public Duration parse(String value, ChronoUnit unit) {
      //
    }
    @Override
    public String print(Duration value, ChronoUnit unit) {
      //
    }
  }

  private final Pattern pattern;

  DurationStyle(String pattern) {
    this.pattern = Pattern.compile(pattern);
  }

  public abstract Duration parse(String value, ChronoUnit unit);
  public abstract String print(Duration value, ChronoUnit unit);
}
```

## Hibernate-ORM

### org.hibernate.envers.query.criteria.MatchMode

https://github.com/hibernate/hibernate-orm/blob/5.3/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/MatchMode.java

```java
public enum MatchMode {

  EXACT {
    @Override
    public String toMatchString(String pattern) {
      return pattern;
    }
  },
  START {
    @Override
    public String toMatchString(String pattern) {
      return pattern + '%';
    }
  },
  // ...省略...

  public abstract String toMatchString(String pattern);
}
```

### org.hibernate.dialect.Database

https://github.com/hibernate/hibernate-orm/blob/5.3/hibernate-core/src/main/java/org/hibernate/dialect/Database.java

```java
public enum Database {

  CACHE {
    // ...省略...
  },
  // ...省略...
  MYSQL {
    @Override
    public Class<? extends Dialect> latestDialect() {
      return MySQL57Dialect.class;
    }

    @Override
    public Dialect resolveDialect(DialectResolutionInfo info) {
      final String databaseName = info.getDatabaseName();

      if ( "MySQL".equals( databaseName ) ) {
        final int majorVersion = info.getDatabaseMajorVersion();
        final int minorVersion = info.getDatabaseMinorVersion();

        if ( majorVersion < 5 ) {
          return new MySQLDialect();
        }
        else if ( majorVersion == 5 ) {
          if ( minorVersion < 5 ) {
            return new MySQL5Dialect();
          }
          else if ( minorVersion < 7 ) {
            return new MySQL55Dialect();
          }
          else {
            return new MySQL57Dialect();
          }
        }

        return latestDialectInstance( this );
      }

      return null;
    }
  },
  ORACLE {
    // ...省略...
  }
  // ...省略...

  public abstract Class<? extends Dialect> latestDialect();
  public abstract Dialect resolveDialect(DialectResolutionInfo info);
}
```

## elasticsearch

### org.elasticsearch.common.unit.SizeUnit

https://github.com/elastic/elasticsearch/blob/6.6/server/src/main/java/org/elasticsearch/common/unit/SizeUnit.java

```java
public enum SizeUnit {

  SINGLE {
    @Override
    public long toSingles(long size) {
      return size;
    }
    @Override
    public long toKilo(long size) {
      return size / (C1 / C0);
    }
    @Override
    public long toMega(long size) {
      return size / (C2 / C0);
    }
    @Override
    public long toGiga(long size) {
      return size / (C3 / C0);
    }
    @Override
    public long toTera(long size) {
      return size / (C4 / C0);
    }
    @Override
    public long toPeta(long size) {
      return size / (C5 / C0);
    }
  },
  KILO {
    // ...省略...
  },
  MEGA {
    // ...省略...
  },
  // ...省略...

  public abstract long toSingles(long size);
  public abstract long toKilo(long size);
  public abstract long toMega(long size);
  public abstract long toGiga(long size);
  public abstract long toTera(long size);
  public abstract long toPeta(long size);
}
```

### org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringNumericProcessor

https://github.com/elastic/elasticsearch/blob/6.6/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/string/BinaryStringNumericProcessor.java

```java
public class BinaryStringNumericProcessor extends FunctionalBinaryProcessor<String, Number, String, BinaryStringNumericOperation> {

  public enum BinaryStringNumericOperation implements BiFunction<String, Number, String> {

    LEFT((s,c) -> {
      int i = c.intValue();
      if (i < 0) {
        return "";
      }
      return i > s.length() ? s : s.substring(0, i);
    }),
    RIGHT((s,c) -> {
      // ...省略...
    }),
    REPEAT((s,c) -> {
      // ...省略...
    });

    BinaryStringNumericOperation(BiFunction<String, Number, String> op) {
      this.op = op;
    }

    private final BiFunction<String, Number, String> op;

    @Override
    public String apply(String stringExp, Number count) {
      if (stringExp == null || count == null) {
        return null;
      }
      return op.apply(stringExp, count);
    }
  }

  // ...省略...
}
```

## logback

### ch.qos.logback.core.joran.spi.ConsoleTarget

https://github.com/qos-ch/logback/blob/1.1.11/logback-core/src/main/java/ch/qos/logback/core/joran/spi/ConsoleTarget.java

```java
public enum ConsoleTarget {

  SystemOut("System.out", new OutputStream() {
    @Override
    public void write(int b) throws IOException {
      System.out.write(b);
    }
    @Override
    public void write(byte b[]) throws IOException {
      System.out.write(b);
    }
    @Override
    public void write(byte b[], int off, int len) throws IOException {
      System.out.write(b, off, len);
    }
    @Override
    public void flush() throws IOException {
      System.out.flush();
    }
  }),
  SystemErr("System.err", new OutputStream() {
    // ...省略...
  });

  private final String name;
  private final OutputStream stream;

  private ConsoleTarget(String name, OutputStream stream) {
    this.name = name;
    this.stream = stream;
  }

  // ...省略...
}
```