Skip to content

Latest commit

 

History

History
187 lines (164 loc) · 5.79 KB

chapter17-enum.md

File metadata and controls

187 lines (164 loc) · 5.79 KB

Enum

An enum is a kind of class where all instances are known and can be enumerated

By example, for a program they may be 3 ways to list files of a directory, either all files (ALL), either only the normal file (NORMAL) or only the directory (DIRECTORY)

enum FileListMode { ALL, NORMAL, DIRECTORY }

Enum instances

The enumerated instances are considered as constants thus can be accessed like any constant

System.out.println(FileListMode.ALL);

All enums inherits from the class java.lang.Enum that defines two components

  • name which is the name of the instance
  • ordinal which is the index (starting at 0)
System.out.println(FileListMode.ALL.name());
System.out.println(FileListMode.ALL.ordinal());

equals()/hashCode() and toString() are inherited from java.lang.Enum

  • equals() delegates to ==
  • hashCode() returns ordinal
  • toString() returns name
System.out.println(FileListMode.ALL.equals(FileListMode.NORMAL));
System.out.println(FileListMode.NORMAL);
System.out.println(FileListMode.DIRECTORY.hashCode());

Enum instances are comparable (their ordinal value is used) so ALL < NORMAL < DIRECTORY

System.out.println(FileListMode.ALL.compareTo(FileListMode.NORMAL) < 0);

Two supplementary static methods are generated by the compiler

  • values() return an array of all instances
  • valueOf(name) return the instance corresponding to the name or an exception
System.out.println(Arrays.toString(FileListMode.values()));
System.out.println(FileListMode.valueOf("ALL"));
System.out.println(FileListMode.valueOf("invalid"));

values() returns a new cloned array at each invocation so don't call it inside a loop :)

Enum are classes

Unlike in C where enums are integers, enum in Java are full objects so they can have fields, constructors and methods defined after a semicolon at the end of the list of the instances

enum FileListMode {
  ALL,
  NORMAL,
  DIRECTORY,  // trailing comma is allowed
  ;           // ends of the instances
  public String shortName() {
    return name().toLowerCase().substring(0, 3);
  }  
}
System.out.println(FileListMode.NORMAL.shortName());
System.out.println(FileListMode.DIRECTORY.shortName());

Enum constructors

You can add fields if you want to associate specific values to the enum instances By example to convert from bits of an int to a set of modifier.

enum Modifier {
  PUBLIC(1), FINAL(2), STATIC(4)
  ;
  private final int value;
  private Modifier(int value) {
    this.value = value;
  }
  // avoid to calls values() several times
  private static final List<Modifier> MODIFIERS = List.of(values());
  static int modifiersAsInt(Modifier... modifiers) {
    return Arrays.stream(modifiers).map(m -> m.value).reduce(0, (a, b) -> a | b);
  }
  static Set<Modifier> intAsModifierSet(int modifiers) {
    return MODIFIERS.stream().filter(m -> (modifiers & m.value) != 0).collect(Collectors.toSet());
  }
}
var modifiers = Modifier.modifiersAsInt(Modifier.PUBLIC, Modifier.STATIC);
System.out.println("int: " + modifiers);
var modifierSet = Modifier.intAsModifierSet(modifiers);
System.out.println("set: " + modifierSet);

The implementation of intAsModifierSet can be a little more efficient, see below

Enum with abstract methods

An enum can have abstract methods, in that case, all instances have to implement the missing method bodies using the same syntax as the anonymous class one In that case, the compiler generates one anonymous class per enum instance.

interface FilePredicate {
  boolean test(Path path) throws IOException;
}
enum FileListMode implements FilePredicate {
  ALL {
    public boolean test(Path path) throws IOException {
      return true;
    }
  },
  NORMAL {
    public boolean test(Path path) throws IOException {
      return !Files.isHidden(path);
    }
  },
  DIRECTORY {
    public boolean test(Path path) throws IOException {
      return NORMAL.test(path) && Files.isDirectory(path);
    }
  }
}

It can be used to list the files of a directory in a way that depend on the mode. If you don't understand the cast in the for loop see chapter 'iteration'

void printAllPath(Path directory, FileListMode mode) throws IOException {
  try(var stream = Files.list(directory)) {
    for(var path: (Iterable<Path>)stream::iterator) {
      if (mode.test(path)) {
        System.out.println(path);
      }
    }
  }
}
printAllPath(Path.of("."), FileListMode.DIRECTORY);

Use delegation, not inheritance

The implementation above uses inheritance where it should use delegation Here is a better implementation delegating each implementation to a lambda.

enum FileListMode {
  ALL(path -> true),
  NORMAL(path -> !Files.isHidden(path)),
  DIRECTORY(path -> NORMAL.test(path) && Files.isDirectory(path))
  ;
  private final FilePredicate predicate;
  FileListMode(FilePredicate predicate) {
    this.predicate = predicate;
  }
  public boolean test(Path path) throws IOException {
    return predicate.test(path);
  }
}
printAllPath(Path.of("."), FileListMode.DIRECTORY);

EnumSet and EnumMap

There are one implementations of set (respectively map) specific if all values comes from the same enum because in that case ordinal() is a perfect hash function

so a EnumSet is implemented

  • using only one long if there is less than 64 enum instances
  • using an array of longs if there are more instances because there are two implementations, you have to use factory methods that takes the enum class to get an instance of the set
var emptySet = EnumSet.noneOf(Modifier.class);
var enumSet = EnumSet.of(Modifier.PUBLIC, Modifier.FINAL);
System.out.println(enumSet);

and EnumMap is implemented as an array of values, the index being the value of ordinal()

var enumMap = new EnumMap<>(Map.of(Modifier.PUBLIC, "private", Modifier.FINAL, "final"));
System.out.println(enumMap);