Effective Java-enumerations and annotations

Source: Internet
Author: User

Enumeration

Enum type added to Java 1.5 ...
Enum type is a type of fixed set of constants, such as Four Seasons, poker suits.
Before the enum type occurs, an enumeration type is typically represented by a set of int constants.
such as this:

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 doesn't feel right.
But if the apples and oranges above are compared to each other, or write ....

int i = (APPLE_FUJI - ORANGE_TEMPLE) / APPLE_PIPPIN;

Although legal but surprised, this is to make juice?


Moreover, this constant is a Compile-time constant, and everything is finished after compilation, and the place where the constant is used is replaced with the value of the constant.
If the constant value needs to be changed, all code that uses the constant must be recompiled.
In a worse case, no recompilation can work, but it has unpredictable results.
(PS: I think the worse is that someone directly writes the constant value into the code ... )


In addition, like the above Apple_fuji, I want to print its name, not its value.
Not only that, I also want to print all the apples, I want to print how many kinds of apples.
Of course, if you want to print, you can only use enum directly, no matter how you do it is cumbersome.


If you use an enum, such as:

public enum Apple  { FUJI, PIPPIN, GRANNY_SMITH } public enum Orange { NAVEL, TEMPLE, BLOOD }


It looks like a bunch of constants, but the enum has no instances, there is no accessible constructor, and it cannot be extended.
The enum itself is final, so many times it is also directly implemented with an enum singleton.
Enum is type-safe at compile time, for example, if there is a place to declare an Apple type parameter in the code above, then the reference to that parameter must be one of three apples.
And the enum itself is a type that can have its own methods and field, and can implement interfaces.


Attached to the book of the solar system enum, it is difficult to imagine if there are similar requirements with ordinary constants to achieve.
Maybe I can declare a planet class, add field methods to it, and declare it as final in a constant class, but there is no guarantee that the planet class is used only as a constant, so use enum instead:

  public enum Planet {MERCURY (3.302e+23, 2.439e6), VENUS (4.869e+24, 6.052e6), EARTH (5.975e+24, 6.3 78E6), 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}}  


And then we can use Planet Enum, whether it's a value or a name, it's 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, a way like planet is sufficient for most scenarios that use enumerations.
This means that each planet constant expresses different data, but there are exceptions.
For example, we want to assign different behaviors to each of the constants in the enum.
Here is an example of an enum expression in a 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)); }}


Switch to a different enumeration constant: Case: Can actually express the effect we want.
If you add a new constant later, you need to add a case, of course, there will be no hint, and then the worst is the run-time problem.
As the above code is the correct use of constant behavior, that is, constant-specific method implementation.
Provides an abstraction for the behavior and provides an implementation for each constant, that is, an enumeration constant is also the Constant-specific class body.
In this way, if you add a constant, you must provide a method implementation, or the compiler will give a hint, which is a layer of protection.


Unfortunately, this approach also has flaws.
For example, we have a need to calculate a day's salary, this one day can be a day of the week, it may be a holiday, such as Monday to Friday using the same operation, the weekend count, another holiday count.
That is, I need to declare a constant in the enumeration that represents the Monday through Sunday, and if I continue to declare an abstract method in the same way as before, if the exact same calculation is used from Monday to Friday, then five identical pieces of code will appear.
But even then we can't use back switch. Case: mode, it is necessary to force the selection of a constant when it chooses a behavior implementation.
So we have a way of calling strategy enum, which is the field that declares another enumeration in the enumeration, which represents the policy and provides policy-related behavior.
Here is the code in the book:

Enum Payrollday {MONDAY (Paytype.weekday), Tuesday (Paytype.weekday), Wednesday (Paytype.weekday), Thursday (P    Aytype.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 * p            AYRATE/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 is often the case before Java 1.5, which provides special functionality by naming the program elements, such as the test method in JUnit, which has to start with testing.
Of course, this approach is feasible to some extent, but not elegant enough.
Like what:

    • The incorrect spelling of the text does not prompt you until the runtime discovers a problem.
    • Second, it is not specific to a program element, such as a user to the beginning of a class name to make a special name, want to act on all methods in the class, the result may be no hint, no effect, no meaning.
    • And, this way is too monotonous, for example, I want to interact with a parameter of a method or an exception that is thrown by a declaration. Of course, reflection is fine, but the question is how I can provide a reflection method without knowing the user's behavior.


The usual work is rarely annotated, and most of the use of annotations provided by others.
Never thought about what it would look like without annotations, but the comparison with naming pattern is really 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 retention and target in the code, we have a special term called "meta-Annotations (meta-annotation)".
For this no parameter, just the annotation of the program element, we call it "markup annotation (marker annotation)".


If you need to declare a parameter to the annotation is not complex, it is just the equivalent of adding an instance field to a class.
The following code, which indicates that an exception in the exception array occurred while testing, was passed:

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 effect on the program element, and it cannot alter the semantics of the code itself.
We need to rely on a particular annotation processing class.
Of course, not one annotation corresponds to a processing class, and a processing class can handle many kinds of annotations.
For example, the following code provides the processing for test and exceptiontest:

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 faile                D:%s%n ", M, exc);    }}} System.out.printf ("Passed:%d, Failed:%d%n", Passed, tests-passed); }}


The code does not explain much, mainly by reflection to judge annotations and get exceptions.


Tagging annotations are very common, but when it comes to tagging annotations, it's important to say that tag interfaces, such as Serializable, are just as noted.
Annotations can be used for more program elements than interfaces that add implements only after the class name. So it came to the conclusion that the labeling interface could be eliminated?
But this is too one-sided.


First, the class that is tagged by the interface provides the implementation of that interface, which is not possible with annotations, and cannot be a constraint even if there is a processing class to subsidize.
As far as serializable is concerned, it is meaningless if the tagged class does not provide the implementation ObjectOutputStream.write(Object) .
In addition, this interface is a bit special, it is really a constraint, but in the compilation period did not give a warning.
I had previously thought that the write method was not defined in serializable, but the author's exact words were:

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 do not follow this approach.


The 2nd is a more precise interface tag.
At first glance, there seems to be some contradiction, compared to the interface can only be used for class elements, annotations may be used in many elements is not the advantage of annotations?
In fact, the author does not express this view, in terms of an interface and target for elementtype.type annotations, the latter can act on any class and interface.


The author uses the set interface to illustrate that the set is somewhat special and the set inherits the collection interface.
At first glance, set does not seem to be a markup interface, it declares too many methods.
Reference:

The Set interface Places additional stipulations, beyond those inherited from the Collection interface, On the contracts of any constructors and on the contracts of the add, equals and hashcode metho Ds. Declarations for other inherited methods is also included here for convenience. (The specifications accompanying these declarations has been tailored to the Set interface, but they does not cont Ain any additional stipulations.)

However, the author describes it as "a restricted marker interface", which declares the same method as the collection interface.
Set does not improve collection's contract, but provides an abstract description of the implementation class.


But even so, annotations cannot be designed to have at least one parameter in the form.
The first thing to admit is that it's an advantage to be able to mark more types than interfaces.
In addition, in a class, the same markup annotations can appear multiple times, which is also its advantage.
And most importantly, compared to the interface of this Convention (that is, after the declaration by some classes provide implementation, in later versions it is difficult to modify the interface), annotations can become richer later.

Effective Java-enumerations and annotations

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.