[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
--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));
}
}
}
--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
--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);
--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]}
--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
--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
--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.
--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