Effective Java 3rd Edition Chapter 6 enums and annotations

[Effective Java 3rd Edition](https://www.amazon.co.jp/Effective-Java-%E7%AC%AC3%E7%89%88-%E3%], which is a must-have book for intermediate Java users and above. 82% B8% E3% 83% A7% E3% 82% B7% E3% 83% A5% E3% 82% A2% E3% 83% BB% E3% 83% 96% E3% 83% AD% E3% 83% 83% E3% 82% AF-ebook / dp / B07RHX1K53) has a Kindle version, so I will summarize it.

Previous: Effective Java 3rd Edition Chapter 5 Generics Next: Effective Java 3rd Edition Chapter 7 Lambda and Stream

Item 34 Use enum instead of int

--enum is a class that exposes one instance for each enum constant through the public static final field. --The enum doesn't have an accessible constructor, so it's effectively final. --The enum type is a generalized version of the singleton, which is basically a single element enum. --Any method or field can be added to the enum type.

Enum type with data and behavior


public enum Planet {
    MERCURY(3.302e+23, 2.439e6),
    VENUS(4.869e+24, 6.052e6),
    EARTH(5.975e+24, 6.378e6),
    MARS(6.419e+23, 3.393e6);
    //abridgement

    private final double mass; //mass
    private final double radius; //radius
    private final double surfaceGravity; //Surface gravity

    private static final double G = 6.67300E-11; //Gravitational constant
    //Constructor that associates data with an enum constant
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
        surfaceGravity = G * mass / (radius * radius);
    }
    public double mass() {
        return mass;
    }
    public double radius() {
        return radius;
    }
    public double surfaceGravity() {
        return surfaceGravity;
    }
    // 
    public double getSurfaceWeight(double mass) {
        return mass * surfaceGravity;   // F = ma
    }
    //Call example
    public static void main(String[] args) {
     //How much does 60kg on Earth weigh on other planets?
        double earthWeight = Double.parseDouble("60.0");
        double mass = earthWeight / Planet.EARTH.surfaceGravity();
        for (Planet p: Planet.values()) {
            System.out.printf("Weight on %s is %f%n", p, p.getSurfaceWeight(mass));
        }
    }
}

//result
Weight on MERCURY is 22.674402
Weight on VENUS is 54.303060
Weight on EARTH is 60.000000
Weight on MARS is 22.776240

--Enum type with constant-specific class body and constant-specific data

Constant-specific class body and enum type with constant-specific data


public enum Operation {
    PLUS("+") {
        //Override apply
        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;

    @Override
    public String toString() {
        return symbol;
    }
    //constructor
    Operation(String symbol) {
        this.symbol = symbol;
    }
    //If you define a method in abstract, each euum constant is required to override the method
    public abstract double apply(double x, double y);

    //Call example
    public static void main(String[] args) {
        double x = 2.0;
        double y = 4.0;
        for (Operation op : Operation.values()) {
            System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
        }
    }
}

Item 35 Use instance fields instead of ordinal numbers

--The int value is automatically assigned to the enum type, but it should not be used because it cannot handle missing numbers or duplicates. When using an int value, assign it explicitly.

Save the value in the instance fold


public enum Ensemble {
    SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5),
    SEXTET(6), SEPTET(7), OCTET(8), DOUBLE_QUARTET(8),
    NONET(0), DECTET(10), TRIPLE_QUARTET(12);

    private final int numberOfMusicians;

    //If you want to have a numerical value, define it in the constructor
    Ensemble(int size) {
        this.numberOfMusicians = size;
    }

    public int getNumberOfMusicians() {
        return numberOfMusicians;
    }

    //Call example
    public static void main(String[] args) {
        for (Ensemble e : Ensemble.values()) {
            System.out.printf("%s %s %n",e.name(), e.numberOfMusicians);
        }
    }
}
//Execution result
SOLO 1 
DUET 2 
TRIO 3 
QUARTET 4 
QUINTET 5 
SEXTET 6 
SEPTET 7 
OCTET 8 
DOUBLE_QUARTET 8 
NONET 0 
DECTET 10 
TRIPLE_QUARTET 12 

Item 36 Use EnumSet instead of bitfield

--A bit field is a way to express abc as 1 + 2 + 4 = 7 when a = 1, b = 2, c = 4, and d = 8. --EnumSet is a Set that can have multiple enum types.

EnumSet example


public class Text {
  public enum Style { BOLD, ITALIC, UNDERLINE STRIKETHROUGH }
  //Receive EnumSet with Set
  public void applyStyles(Set<Style> styles) { ... }
}
//Example of using EnumSet
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC);

Item 37 Use EnumMap instead of ordinal index

--EnumSet is a fast Map implementation designed to use enums as keys.

Example of using EnumSet


public class Plant {

    enum LifeCycle {ANNUAL, PERENNIAL, BIENNIAL;}

    final String name;
    final LifeCycle lifeCycle;

    Plant(String name, LifeCycle lifeCycle) {
        this.name = name;
        this.lifeCycle = lifeCycle;
    }

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

    //Call example
    public static void main(String[] args) {
        var garden = List.of(new Plant("annual", LifeCycle.ANNUAL),
                new Plant("biennial1", LifeCycle.BIENNIAL),
                new Plant("biennial2", LifeCycle.BIENNIAL));
        //Create an instance of EnumMap
        Map<LifeCycle, Set<Plant>> plantsByLifeCycle =
                new EnumMap<>(Plant.LifeCycle.class);
        //Key: Enum type LifeCycle, add Map with HashSet with empty value
        for (Plant.LifeCycle lc : Plant.LifeCycle.values()) {
            plantsByLifeCycle.put(lc, new HashSet<>());
        }
        //Add a value to the HashSet of the Map.
        for (Plant p : garden) {
            plantsByLifeCycle.get(p.lifeCycle).add(p);
        }
        System.out.println(plantsByLifeCycle);
    }
}
//Output result
{ANNUAL=[annual], PERENNIAL=[], BIENNIAL=[biennial1, biennial2]}

Item 38 Imitate an extensible enum with an interface

--The enum type cannot be extended, but it is possible to implement the interface with the enum type.

Implement interface with enum


//Interface definition
public interface Operation {
    double apply(double x, double y);
}
//Implement the interface
enum BasicOperation implements Operation {
    PLUS("+") {
        //Implement apply
        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;

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

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

//Extended enum type
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;

    //constructor
    ExtendedOperation(String symbol) {
        this.symbol = symbol;
    }

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

//Call example
class Main {
    public static void main(String[] args) {
        double x = 2.0;
        double y = 4.0;
        text1(BasicOperation.class, x, y);
        text1(ExtendedOperation.class, x, y);
        text2(Arrays.asList(BasicOperation.values()), x, y);
        text2(Arrays.asList(ExtendedOperation.values()), x, y);
    }

    //Pass as enum and Operation subtype
    private static <T extends Enum<T> & Operation> void text1(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));
        }
    }

    //How to pass using bounding wildcard type
    private static void text2(Collection<? extends Operation> opSet, double x, double y) {
        for (Operation op : opSet) {
            System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
        }
    }
}
//Execution result
2.000000 + 4.000000 = 6.000000
2.000000 - 4.000000 = -2.000000
2.000000 * 4.000000 = 8.000000
2.000000 / 4.000000 = 0.500000
2.000000 ^ 4.000000 = 16.000000
2.000000 % 4.000000 = 2.000000
2.000000 + 4.000000 = 6.000000
2.000000 - 4.000000 = -2.000000
2.000000 * 4.000000 = 8.000000
2.000000 / 4.000000 = 0.500000
2.000000 ^ 4.000000 = 16.000000
2.000000 % 4.000000 = 2.000000

Item 39 Select annotations over naming patterns

--Annotations should be used because it is a drawback to give method names etc. special properties due to naming patterns. (Example: testSafetyOverride starts with test, so test med, is a bad example) --Definition of Test annotation


// @Definition of Test annotation
@Retention(RetentionPolicy.RUNTIME) //Test annotation should be retained at run time
@Target(ElementType.METHOD) //Only allowed for method declarations
@interface Test {
}

//Tested class
class Sample {
    @Test
    public static void m1() {
    }

    public static void m2() {
    }

    @Test
    public static void m3() {
        throw new RuntimeException("Boom");
    }

    public static void m4() {
    }

    @Test
    public static void m5() {
    }

    public static void m6() {
    }

    @Test
    public static void m7() {
        throw new RuntimeException("Crash");
    }

    public static void m8() {
    }
}

///Test annotation processing class
class RunTests {
    public static void main(String[] args) throws Exception {
        int tests = 0;
        int passed = 0;
        Class<?> testClass = Class.forName("sec39.Sample");
        //Loop through all methods in the test
        for (Method m : testClass.getDeclaredMethods()) {
            // @Is it with the Test annotation?
            if (m.isAnnotationPresent(Test.class)) {
                tests++;
                try {
                    //Tested method call
                    m.invoke(null);
                    passed++;
                } catch (InvocationTargetException wrappedExc) {
                    Throwable exc = wrappedExc.getCause();
                    System.out.println(m + " failed: " + exc);
                } catch (Exception e) {
                    System.out.println("Invalid @Test: " + m);
                }
            }
        }
        System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);
    }
}

//Execution result
public static void sec39.Sample.m7() failed: java.lang.RuntimeException: Crash
public static void sec39.Sample.m3() failed: java.lang.RuntimeException: Boom
Passed: 2, Failed: 2

Item 40 Always use Override annotation

--When overriding, annotating @Override will detect that it was not overridden. --If you try to override without the @Override annotation, the IDE will warn you so you can avoid unintended overrides.

Item 41 Use the marker interface to define the type

--A marker interface is an interface that does not contain a method declaration (empty), and specifies (marks) that the class that implements that interface has some characteristics. --If you want to define a type that doesn't have a new method associated with it, it should be a marker interface. --If you want to mark program elements other than classes and interfaces, or if you want to fit markers within a framework that already makes heavy use of annotation types, you should choose marker annotations.

Recommended Posts

Effective Java 3rd Edition Chapter 6 enums and annotations
Effective Java 3rd Edition Chapter 4 Classes and Interfaces
Effective Java 3rd Edition Chapter 7 Lambda and Streams
Effective Java 3rd Edition Chapter 5 Generics
Effective Java 3rd Edition Chapter 8 Methods
Effective Java 3rd Edition Chapter 2 Object Creation and Disappearance
Effective Java 3rd Edition Chapter 9 Program General
Effective Java 3rd Edition Chapter 3 Methods Common to All Objects
What has changed between Effective Java 2nd Edition and 3rd Edition
Effective Java Chapter 2
Effective Java Chapter 6 34-35
Effective Java Chapter 4 15-22
Effective Java Chapter 3
effective java 3rd summary
Effective Java 3rd Edition Section 85 Choose Alternatives Over Java Serialization
[Read Effective Java] Chapter 2 Item 7 "Avoid Finalizers"
Discrimination of Enums in Java 7 and above
Parallel and parallel processing in various languages (Java edition)
Effective Java 2nd Edition Related Matters Survey Summary
[Read Effective Java] Chapter 3 Item 10 "Always Override toString"
I tried to explain Effective Java 3rd edition "almost all chapters" in "easy-to-read Japanese".
Java and JavaScript
XXE and Java
Convert Java enum enums and JSON to and from Jackson
[Read Effective Java] Chapter 3 Item 12 "Considering Implementation of Comparable"
Summary of ToString behavior with Java and Groovy annotations
The comparison of enums is ==, and equals is good [Java]
[Read Effective Java] Chapter 2 Item 6 "Remove obsolete object references"
Updates from Effective Java Third Edition 2nd Edition Personal Notes
I will write an excerpt of what I found interesting while reading Effective Java 3rd Edition