Valid Java, inclutivejava
Enumeration
The enum type added in Java 1.5...
Enum type is a set of fixed constants, such as four seasons and poker colors.
Before enum type occurs, an int constant is usually used to represent the enumeration type.
For example:
public static final int APPLE_FUJI = 0;public static final int APPLE_PIPPIN = 1;public static final int APPLE_GRANNY_SMITH = 2;public static final int ORANGE_NAVEL = 0;public static final int ORANGE_TEMPLE = 1;public static final int ORANGE_BLOOD = 2;
If you just want to use it as an enumeration, it feels nothing like this.
However, if you compare the above apples with oranges, or write them ....
int i = (APPLE_FUJI - ORANGE_TEMPLE) / APPLE_PIPPIN;
Although legal but surprised, is this juice?
In addition, this constant is a compile-time constant. After compilation, everything ends and the place where this constant is used is replaced with the value of this constant.
If the constant value needs to be changed, all code that uses the constant must be re-compiled.
Even worse, it can run normally without re-compiling, but with unpredictable results.
(Ps: I think what's worse is that someone directly writes the constant value to the code ...)
In addition, for example, the APPLE_FUJI above, I want to print its name, not its value.
Besides, I want to print all the apple types.
Of course, if you want to print it, you can simply use enum, which is very troublesome.
If you use enum, for example:
public enum Apple { FUJI, PIPPIN, GRANNY_SMITH } public enum Orange { NAVEL, TEMPLE, BLOOD }
It looks like a bunch of constants, but enum does not have an instance or an accessible constructor and cannot be expanded.
Enum itself is final, so it is often used to implement singleton directly with enum.
Enum is type-safe during compilation. For example, if an Apple-type parameter in the above Code is declared, the reference to this parameter must be one of the three Apple types.
In addition, enum itself is a type, which can have its own methods and fields, and can implement interfaces.
Attached to the book, the solar system enum, it is hard to imagine that if there is a similar demand to achieve with a normal constant.
Maybe I can declare a Planet class, add the field method to it, and declare final in a constant class, but this does not ensure that the Planet class is only used as a constant, so I should use enum:
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), JUPITER(1.899e+27, 7.149e7), SATURN( 5.685e+26, 6.027e7), URANUS(8.683e+25, 2.556e7), NEPTUNE(1.024e+26, 2.477e7); private final double mass; // In kilograms private final double radius; // In meters private final double surfaceGravity; // In m / s^2 // Universal gravitational constant in m^3 / kg s^2 private static final double G = 6.67300E-11; // Constructor 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 surfaceWeight(double mass) { return mass * surfaceGravity; // F = ma }}
Then we can use Planet enum in this way. Whether it is a value or a name, it is natural to use it:
public class WeightTable { public static void main(String[] args) { double earthWeight = Double.parseDouble(args[0]); double mass = earthWeight / Planet.EARTH.surfaceGravity(); for (Planet p : Planet.values()) System.out.printf("Weight on %s is %f%n",p, p.surfaceWeight(mass)); }}
In fact, methods like Planet are sufficient for most enumeration scenarios.
That is to say, each Planet constant expresses different data, but there are exceptions.
For example, we need to assign different behaviors to every constant in enum.
The following is an example of enum expression calculation in the book:
import java.util.HashMap;import java.util.Map;public enum Operation { PLUS("+") { double apply(double x, double y) { return x + y; } }, MINUS("-") { double apply(double x, double y) { return x - y; } }, TIMES("*") { double apply(double x, double y) { return x * y; } }, DIVIDE("/") { double apply(double x, double y) { return x / y; } }; private final String symbol; Operation(String symbol) { this.symbol = symbol; } @Override public String toString() { return symbol; } abstract double apply(double x, double y); private static final Map<String, Operation> stringToEnum = new HashMap<String, Operation>(); static { for (Operation op : values()) stringToEnum.put(op.toString(), op); } public static Operation fromString(String symbol) { return stringToEnum.get(symbol); } public static void main(String[] args) { double x = Double.parseDouble(args[0]); double y = Double.parseDouble(args[1]); for (Operation op : Operation.values()) System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y)); }}
Changing different enumerated constants... case... can also express what we want.
If a new constant is added in the future, you need to add another case. Of course, there will be no prompt if you do not add it. The worst case is that a problem occurs during the running.
The above code is the correct way to use constant behavior, that is, constant-specific method implementation.
Provides an abstraction for the behavior and an implementation for each constant, that is, an enumerated constant is also the constant-specific class body.
When this method is used, if a constant is added, a method must be provided for implementation. Otherwise, the compiler will give a prompt, which provides a level of protection.
Unfortunately, this method is also flawed.
For example, if we have such a requirement to calculate the salary of a day, this day can be a day of the week or a holiday, for example, from Monday to Friday using the same calculation method, on weekends, a holiday is counted separately.
That is to say, I need to declare a constant from Monday to Sunday in enumeration. if I continue to use the previous method to declare an abstract method, if the calculation is identical from Monday to Friday, five identical codes will appear.
However, even in this case, we cannot use the switch... case... method. When adding a constant, it is necessary to select an action to implement it.
Therefore, we have a method called strategy enum, that is, declaring another enumerated field in enumeration. This field represents a policy and provides behavior related to the policy.
The following is the code in the book:
enum PayrollDay { MONDAY(PayType.WEEKDAY), TUESDAY(PayType.WEEKDAY), WEDNESDAY( PayType.WEEKDAY), THURSDAY(PayType.WEEKDAY), FRIDAY(PayType.WEEKDAY), SATURDAY( PayType.WEEKEND), SUNDAY(PayType.WEEKEND); private final PayType payType; PayrollDay(PayType payType) { this.payType = payType; } double pay(double hoursWorked, double payRate) { return payType.pay(hoursWorked, payRate); } private enum PayType { WEEKDAY { double overtimePay(double hours, double payRate) { return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT) * payRate / 2; } }, WEEKEND { double overtimePay(double hours, double payRate) { return hours * payRate / 2; } }; private static final int HOURS_PER_SHIFT = 8; abstract double overtimePay(double hrs, double payRate); double pay(double hoursWorked, double payRate) { double basePay = hoursWorked * payRate; return basePay + overtimePay(hoursWorked, payRate); } }}
Annotation
This often happens before Java 1.5. Special names are given to program elements to provide special functions. For example, the test method in JUnit must start with test.
Of course, this method is indeed feasible to some extent, but not elegant enough.
For example:
- There is no prompt for incorrect text spelling, and it will not be found until it is run.
- Secondly, this method cannot specify a program element. For example, you have made a special name for the beginning of a Class Name and want to apply it to all methods in the class, the results may not prompt, have no effect, and have no significance.
- Moreover, this method is too monotonous. For example, I want to interact with parameters of a method or exceptions thrown by the Declaration. Of course, reflection is fine, but the problem is how to provide reflection methods without knowing user behavior.
Annotations are rarely provided at work. In most cases, annotations provided by others are used.
I never thought about what the annotation would look like, but compared with naming pattern, I found that it is really too important.
For example, in the following example, declare an annotation to represent the Test method:
import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface Test { //..}
For the retention and target in the code, we have a special term called "meta-annotation )".
For this kind of non-parameter annotation, only annotation of program elements is called "marker annotation )".
It is not complex to declare parameters for annotations, but it is equivalent to adding an instance field to a class.
The following code passes the test when an exception occurs in the array:
import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface ExceptionTest { Class<? extends Exception>[] value();}
Of course, the annotation itself has no direct impact on program elements, and it cannot change the semantics of the Code itself.
We need to rely on specific annotation processing classes.
Of course, not an annotation corresponds to a processing class, And a processing class can also process many kinds of annotations.
For example, the following code provides processing for Test and predictiontest:
import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class RunTests { public static void main(String[] args) throws Exception { int tests = 0; int passed = 0; Class testClass = Class.forName(args[0]); for (Method m : testClass.getDeclaredMethods()) { if (m.isAnnotationPresent(Test.class)) { tests++; try { m.invoke(null); passed++; } catch (InvocationTargetException wrappedExc) { Throwable exc = wrappedExc.getCause(); System.out.println(m + " failed: " + exc); } catch (Exception exc) { System.out.println("INVALID @Test: " + m); } } if (m.isAnnotationPresent(ExceptionTest.class)) { tests++; try { m.invoke(null); System.out.printf("Test %s failed: no exception%n", m); } catch (Throwable wrappedExc) { Throwable exc = wrappedExc.getCause(); Class<? extends Exception>[] excTypes = m.getAnnotation( ExceptionTest.class).value(); int oldPassed = passed; for (Class<? extends Exception> excType : excTypes) { if (excType.isInstance(exc)) { passed++; break; } } if (passed == oldPassed) System.out.printf("Test %s failed: %s %n", m, exc); } } } System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed); }}
The code will not be explained much, mainly through reflection to identify annotations and obtain exceptions.
In fact, tag annotation is very common, but when it comes to tag annotation, you have to say that the tag interface, such as Serializable, is only used as the annotation.
Compared to interfaces, implements can only be added after the class name. Annotations can act on more program elements. So I came to the conclusion that the mark interface can be eliminated?
However, this is too one-sided.
First, the class marked by the interface provides the implementation of this interface, which cannot be implemented by annotations. Even if the processing class is subsidized, it cannot become a constraint.
For Serializable, if the marked class does not provide an implementation,ObjectOutputStream.write(Object)It is meaningless.
In addition, this interface is a bit special. It is indeed a constraint, but no warning is given during compilation.
I previously thought that the write method does not define any special significance in Serializable, but the author's original statement is:
Inexplicably, the authors of the ObjectOutputStream API did not take advantage of the Serializable interface in declaring the write method.
It can be seen that he does not know the meaning of it. In this case, we will not follow this practice.
The second point is more accurate interface marking.
At first glance, there seems to be some contradictions. Compared with interfaces, annotations can only act on class elements. Can annotations act on multiple elements, not the advantages of annotations?
In fact, the author does not express this point of view. In terms of an interface and the annotation of Target as ElementType. Type, the latter can act on any class and interface.
The authors use the Set interface to explain the special situation of Set. Set inherits the Collection interface.
At first glance, Set does not seem to be a tag interface, and it declares too many methods.
Refer:
The Set interface places additional stipulations, beyond those inherited from the Collection interface, on the contracts of all constructors and on the contracts of the add, equals and hashCode methods. Declarations for other inherited methods are also included here for convenience. (The specifications accompanying these declarations have been tailored to the Set interface, but they do not contain any additional stipulations.)
But the author described it as "a restricted marker interface", which declares the same method as the Collection interface.
Set does not improve the Collection contract, but only provides an abstract description for implementation classes.
However, even so, annotations cannot be designed in the form of at least one parameter.
First of all, we have to admit that there are more types to mark than interfaces, which is indeed an advantage.
In addition, in a class, the same tag annotation can appear multiple times, which is also its advantage.
Most importantly, compared with the interface conventions (that is, some classes provide implementation after declaration, and it is difficult to modify this interface in later versions ), annotations can be enriched later.