[JAVA] Item 38: Emulate extensible enums with interfaces

38. The interface should imitate the extensible enum

Most of the time, the idea of extending enums is bad, but it's urgently needed only when writing operation code. There is a good way to do this with an enum. Since enum can implement an interface, it can be done as follows, for example.

// Emulated extensible enum using an interface
public interface Operation {
    double apply(double x, double y);
}

public enum BasicOperation implements Operation {
    PLUS("+") {
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        public double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES("*") {
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE("/") {
        public double apply(double x, double y) {
            return x / y;
        }
    };
    private final String symbol;

    BasicOperation(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return symbol;
    }
}

The enum type is not extensible, but the interface is extensible, and it is the interface that is used as the API for operations. You can also use this interface to define a new operation code as follows:

// Emulated extension enum
public enum ExtendedOperation implements Operation {
    EXP("^") {
        public double apply(double x, double y) {
            return Math.pow(x, y);
        }
    },
    REMAINDER("%") {
        public double apply(double x, double y) {
            return x % y;
        }
    };
    private final String symbol;

    ExtendedOperation(String symbol) {

        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return symbol;
    }
}

An instance of an extended enum can be used wherever an instance of the underlying enum is expected. Not only that, but even in situations where you want to pass all the elements of a base enum, you can pass all the elements of an extended enum instead. The following source code displays the result of all operations by ExtendedOperation of the two values taken as arguments.

public static void main(String[] args) {
    double x = Double.parseDouble(args[0]);
    double y = Double.parseDouble(args[1]);
    test(ExtendedOperation.class, x, y);
}

private static <T extends Enum<T> & Operation> void test(Class<T> opEnumType, double x, double y) {
    for (Operation op : opEnumType.getEnumConstants())
        System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));

}

In this code, ExtendedOperation.class is taken as the first argument of the test method. This is a bounded type token (Item33). <t extends enum<t> & operation>However, the class given to the first argument of test by this is enum andoperationIs guaranteed to be a subclass of. You can also use the bounded wildcard type (Item31) to write:

public static void main(String[] args) {
    double x = Double.parseDouble(args[0]);
    double y = Double.parseDouble(args[1]);
    test(Arrays.asList(ExtendedOperation.values()), x, y);
}
private static void test(Collection<? extends Operation> opSet, double x, double y) {
    for (Operation op : opSet) {
        System.out.printf("%f%s%f=%f%s", x, op, y, op.apply(x, y));
    }
}

This code is a bit more concise and flexible, but you should give up using EnumSet (Item36) and EnumMap (Item37). The above two codes output as follows when `` `4 2``` is given as an argument.

4.000000 ^ 2.000000 = 16.000000
4.000000 % 2.000000 = 0.000000

The minor drawback of this extended enum is that the code that stores the symbols associated with the operation and the code that searches must be retained in both the `BasicOperation``` and the ```ExtendedOperation```. .. If too many common functions appear, it is necessary to prepare a helper class etc. to eliminate code duplication. The patterns mentioned in this chapter are also used in `java.nio.file.LinkOption```.

Recommended Posts

Item 38: Emulate extensible enums with interfaces
Mock Enums with PowerMock
Item 65: Prefer interfaces to reflection