Effective Java Third edition--34. overriding integer constants with enum types

Source: Internet
Author: User
Tags instance method


Tips
"Effective Java, third Edition" an English version has been published, the second edition of this book presumably many people have read, known as one of the four major Java books, but the second edition of 2009 published, to now nearly 8 years, but with Java 6, 7, 8, and even 9 of the release, the Java language has undergone profound changes.
In the first time here translated into Chinese version. For everyone to learn to share.





Java supports the special-purpose series of two reference types: A class called an enumeration type and an interface called an annotation type. This chapter discusses best practices for using these types of series.


34. Overriding integer constants with enumeration types


An enumeration is a type whose legal value consists of a fixed set of constants, such as the season of the year, the planets in the solar system, or a set of cards in a deck of poker. Before adding an enumeration type to the language, a common pattern that represents an enumeration type is to declare a set of constants named int, each of which has a constant:


// The int enum pattern - severely deficient!
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;


This technique, known as the INT enumeration pattern, has many drawbacks. It does not provide a type-safe way, nor does it provide any expressive power. If you pass an Apple to a method that requires orange, the compiler does not get a warning, and the==operator compares Apple with Orange, or worse:


// Tasty citrus flavored applesauce!
int i = (APPLE_FUJI - ORANGE_TEMPLE) / APPLE_PIPPIN;


Note that the name prefix for each Apple constant is theAPPLE_name of eachOrangeconstantORANGE_. This is because Java does not provide namespaces for INT enumeration groups. When the two int enumeration group has the same named constant, the prefix can prevent name collisions, such asELEMENT_MERCURYPLANET_MERCURYbetween and.



Programs that use INT enumeration are vulnerable. Because int enumerations are compile-time [jls,4.12.4], their int values are compiled into the client using them [jls,13.1]. If the value associated with the INT enumeration changes, the client must be recompiled. If not, the customer will still run, but their behavior will be incorrect.



There is no easy way to convert an int enumeration constant to a printable string. If you print such a constant or display it from the debugger, you see just a number, which is not very useful. There is no reliable way to iterate over all int enumeration constants in a group, or even to get the size of an int enumeration group.



You may encounter variants of this pattern, where string constants are used in place of int constants. This variant, called the string enumeration pattern, is less desirable. Although it provides a printable string for a constant, it can cause the novice user to hardcode the string constant to the client code instead of using the property name. If this hard-coded string constant contains a write error, it will escape detection at compile time and cause an error at run time. Additionally, it may cause performance problems because it relies on string comparisons.



Fortunately, Java provides a workaround that avoids all the drawbacks of the int and string enumeration patterns and provides many additional benefits. It is an enumeration type [jls,8.9]. Here's the simplest form of it:


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


On the surface, these enumeration types may look similar to other languages, such as C,c + + and C #, but that is not the case. The enumeration type of Java is a complete class, more powerful than other languages in other languages, and its enumeration is essentially an int value.



The basic idea behind Java enumeration types is simple: they are classes that export an instance of each enumeration constant through a public static final property. Because there is no accessible construction method, the enumeration type is actually final. Because the customer cannot create an instance of an enumeration type and cannot inherit it, there cannot be any instances other than the declared enumeration constants. In other words, the enumeration type is instance controlled (page 6th). They are generics of the Singleton (entry 3), which is basically an enumeration of the elements.



Enumeration provides the security of a compile-time type. If you declare a parameter to be an Apple type, you can guarantee that any non-null object reference passed to the parameter is one of three valid Apple values. Attempting to pass a value of the wrong type causes a compile-time error because an expression of an enumeration type is attempted to be assigned to a variable of another type, or an operator is used==to compare values of different enumeration types.



enumerated types with the same name constants can coexist peacefully, because each type has its own namespace. You can add or reorder constants in an enumeration type without recompiling its clients, because the properties of the exported constants provide a layer of isolation between the enumeration type and its clients: constant values are not compiled to the client because they are in the INT enumeration pattern. Finally, you can convert antoStringenumeration to a printable string by calling its method.



In addition to correcting the flaws of the int enumeration, enumeration types allow the addition of arbitrary methods and properties and implement arbitrary interfaces. They provide a high-quality implementation of all the object Methods (chapter 3rd), which implementComparable(entry 14) andSerializable(12th), and design serialization for the arbitrary modification of enum types.



So why do you want to add a method or property to an enumeration type? For starters, you might want to associate data with its constants. For example, our apple and orange types may benefit from the method of returning fruit color or the method of returning fruit images. You can also use any method that looks appropriate to enhance the enumeration type. Enumeration types can be a simple collection of enumeration constants and evolve into full-featured abstractions over time.



For a good example of a rich enumeration type, consider our solar system's eight planets. Each planet has mass and radius, from which two properties can be computed for its surface gravity. Thus, the weight of an object on the surface of a planet is calculated under the mass of a given object. The following is the enumeration type. The number in parentheses after each enumeration constant is the argument passed to its construction method. In this case, they are the mass and radius of the Earth:


// 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),
    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
    }
}


Writing a rich enumeration type is easy, for examplePlanet. to associate data with enumeration constants, declare the instance properties and write a constructor that constructs the method with data and saves the data in the property . Enumerations are inherently immutable, so all properties should be final (entry 17). Properties can be public, but it is best to set them to private and provide public access methods (entry 16). InPlanetthe case, the construction method also calculates and stores the surface gravity, but this is only an optimization. Whenever gravity isSurfaceWeightused by a method, it can be recalculated from mass and radius, which returns its weight on the planet represented by the constant.



AlthoughPlanetthe enumeration is simple, it has a very powerful function. This is a short program that takes the weight of an object on Earth (any unit), prints a nice table that shows the weight of the object on all eight planets (in the same unit):


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));
      }
}


Note that,Planetlike all enumerations, there is a staticvaluesmethod that returns an array of its values in the order in which they are declared. Also note that thetoStringmethod returns the declared name of each enumeration value, making itprintlnprintfeasy to print. If you are not satisfied with this string representation, you can change it by overriding thetoStringmethod. This is theWeightTableresult of running the program (without overriding ToString) using the command line parameter 185:


Weight on MERCURY is 69.912739
Weight on VENUS is 167.434436
Weight on EARTH is 185.000000
Weight on MARS is 70.226739
Weight on JUPITER is 467.990696
Weight on SATURN is 197.120111
Weight on URANUS is 167.398264
Weight on NEPTUNE is 210.208751


Until 2006, after two years of enumeration in Java, Pluto was no longer a planet. This raises the question: "What happens when you remove an element from an enumeration type?" "The answer is that any client program that does not reference the removed element will continue to work properly. So, for example, ourWeightTableprogram only needs to print a small row of tables. What is a client program that references the deleted element (in this casePlanet.Pluto)? If the client program is recompiled, the compilation will fail and provide useful error messages at the row of the previous planet; If the client cannot be recompiled, it will throw a useful exception from this line at run time. This is the best behavior you can expect, much better than the result you get with INT enumeration mode.



Some of the behaviors associated with enumeration constants need to be used only in classes or packages that define enumerations. These behaviors are best implemented in a private or package-level private way. Each constant then carries a collection of hidden behaviors that allow the class or package containing the enumeration to react appropriately when rendered with constants. As with other classes, unless you have a compelling reason to expose the enumeration method to its clients, declare it private and declare it as a package-level private if necessary (entry 15).



If an enumeration is widely used, it should be a top class; If it is used with a particular top-level class binding, it should be a member class of that top-level class (entry 24). For example,java.math.RoundingModean enumeration represents a rounding pattern for fractional parts.BigDecimalclasses Use these rounding patterns, but they provide a useful abstraction that does notBigDecimalhave a fundamental connection. ByRoundingModesetting it to the top-level enumeration, the Class Library designer encourages any programmer who needs a rounding pattern to reuse this enumeration, thereby increasing cross-API consistency.


// Enum type that switches on its own value - questionable
public enum Operation {
    PLUS, MINUS, TIMES, DIVIDE;

    // Do the arithmetic operation represented by this constant
    public double apply(double x, double y) {
        switch(this) {
            case PLUS:   return x + y;
            case MINUS:  return x - y;
            case TIMES:  return x * y;
            case DIVIDE: return x / y;
        }
        throw new AssertionError("Unknown op: " + this);
    }
}


This code is valid, but not very beautiful. If there is nothrowstatement, it cannot be compiled, because the end of the method is technically attainable, although it will never be reached [jls,14.21]. To make things worse, the code is fragile. If you add a new enumeration constant, but forget to add the appropriate condition to the switch statement, the enumeration will still compile, but it will fail at run time when you try to apply the new operation.



Fortunately, there is a better way to associate different behaviors with each enumeration constant: Declare an abstract method in an enumeration typeapply, and rewrite it with a specific method for each constant in a constant-specific class body. This method is called a constant (constant-specific)-specific method implementation:


// Enum type with constant-specific method implementations
public enum 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;}};

  public abstract double apply(double x, double y);
}


If you add a new constant to the operation of the second version, you are less likely to forget to provide theapplymethod, because the method immediately follows each constant declaration. In case you forget, the compiler will remind you that the abstract method in the enumeration type must be overridden by a specific method in all constants.



Constant-specific method implementations can be used in conjunction with constant-specific data. For example, here isOperationa version that overrides thetoStringmethod to return the symbols that are typically associated with the operation:


// Enum type with constant-specific class bodies and data
public enum 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;


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


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


    public abstract double apply(double x, double y);
}


The displayedtoStringimplementation can easily print an arithmetic expression, as shown in this small program:


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));
}


Running this program with 2 and 4 as command-line arguments produces the following output:


2.000000 + 4.000000 = 6.000000
2.000000 - 4.000000 = -2.000000
2.000000 * 4.000000 = 8.000000
2.000000 / 4.000000 = 0.500000


An enumeration type has an automatically generatedvalueOf(String)method that converts the constant name to the constant itself. If you override a method in an enumeration typetoString, consider writing afromStringmethod to convert the custom string representation back to the appropriate enumeration type. The following code (the type name is changed appropriately) will be valid for any enumeration, as long as each constant has a unique string representation:


// Implementing a fromString method on an enum type
private static final Map<String, Operation> stringToEnum =
        Stream.of(values()).collect(
            toMap(Object::toString, e -> e));

// Returns Operation for string, if any
public static Optional<Operation> fromString(String symbol) {
    return Optional.ofNullable(stringToEnum.get(symbol));
}


Note that theOperationenumeration constants are placed instringToEnumthe map, which is derived from the static property initialization that runs after the enumeration constant is created. The preceding codevalues()uses the stream on the array returned by the method (chapter 7th); Before Java 8, we created an emptyhashMapand iterated array of values, inserting string-to-enumeration mappings into the map, and still do so if you want. Note, however, that trying to have each constant put itself in a map from its construction method does not work. This causes a compilation error, which is good because if it is legitimate, it will be caused at run timeNullPointerException. In addition to the compile-time constants attribute (entry 34), the enumeration construction method does not allow access to the static properties of the enumeration. This restriction is required because static properties are not initialized when the enumeration construction method runs. A special case of this limitation is that enumeration constants cannot be accessed from one another in a constructed method.



Also note that thefromStringmethod returns oneOptional<String>. This allows the method to indicate that the passed-in string does not represent a valid operation, and forces the client to face this possibility (entry 55).



One drawback to constant-specific method implementations is that they make it difficult to share code between enumerated constants. For example, consider an enumeration that represents the number of working days in a payroll package. The enumeration has a method of calculating the worker's wages for the day based on the worker's base salary (hourly) and the number of minutes of work that day. Overtime will be paid for any work exceeding normal working hours within five business days; During the two weekends, overtime is paid for all work. Using the switch statement,caseyou can easily complete this calculation by applying multiple tags to each of the two code snippets:


// Enum that switches on its value to share code - questionable
enum PayrollDay {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,
    SATURDAY, SUNDAY;

    private static final int MINS_PER_SHIFT = 8 * 60;

    int pay(int minutesWorked, int payRate) {
        int basePay = minutesWorked * payRate;

        int overtimePay;
        switch(this) {
          case SATURDAY: case SUNDAY: // Weekend
            overtimePay = basePay / 2;
            break;
          default: // Weekday
            overtimePay = minutesWorked <= MINS_PER_SHIFT ?
              0 : (minutesWorked - MINS_PER_SHIFT) * payRate / 2;
        }

        return basePay + overtimePay;
    }
}


This code is undeniably concise, but it is dangerous from a maintenance standpoint. Suppose you add an element to an enumeration, which may be a special value to represent a vacation, but forget to add a corresponding case condition to the switch statement. The program will still compile, but the payment method will silently pay the same number of vacation days as the normal working day.



To implement a payroll calculation safely using a constant-specific method, you must repeat the overtime pay calculation for each constant, or move the calculation to two helper methods, one for weekdays, another for weekends, and a call to the appropriate helper methods from each constant. Both of these methods produce a considerable amount of boilerplate code, which greatly reduces readability and increases the chance of error.



PayrollDayyou can reduce the boilerplate by replacing the abstract Y method with the specific method that performs overtime calculationsovertimePa. Then only the weekend days must override this method. However, this has the same disadvantage as a switch statement: If youovertimePayadd another day without overriding the method, the Sunday calculation is silently inherited.



What you really want is to be forced to choose an overtime policy every time you add an enumeration constant. Fortunately, there is a good way to achieve this. The idea is to move the overtime calculation into a private nested enumeration and pass the instance of this policy enumeration toPayrollDaythe constructor of the enumeration. ThePayrollDayenumeration then delegates the overtime pay calculation to the policy enumeration, eliminating the need toPayrollDayimplement a switch statement or a constant-specific method implementation in. Although this model is less concise than the switch statement, it is more secure and more flexible:


// The strategy enum pattern
enum PayrollDay {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,
    SATURDAY(PayType.WEEKEND), SUNDAY(PayType.WEEKEND);


    private final PayType payType;


    PayrollDay(PayType payType) { this.payType = payType; }
    PayrollDay() { this(PayType.WEEKDAY); }  // Default


    int pay(int minutesWorked, int payRate) {
        return payType.pay(minutesWorked, payRate);
    }


    // The strategy enum type
    private enum PayType {
        WEEKDAY {
            int overtimePay(int minsWorked, int payRate) {
                return minsWorked <= MINS_PER_SHIFT ? 0 :
                  (minsWorked - MINS_PER_SHIFT) * payRate / 2;
            }
        },
        WEEKEND {
            int overtimePay(int minsWorked, int payRate) {
                return minsWorked * payRate / 2;
            }
        };


        abstract int overtimePay(int mins, int payRate);
        private static final int MINS_PER_SHIFT = 8 * 60;


        int pay(int minsWorked, int payRate) {
            int basePay = minsWorked * payRate;
            return basePay + overtimePay(minsWorked, payRate);
        }
    }
}


If the switch statement for the enumeration is not a good choice for implementing constant-specific behavior, what are their benefits? switch of enum type facilitates the addition of enumeration types with constant-specific behavior. For example, suppose theOperationenumeration is not under your control, and you want it to have an instance method that returns each of the opposite actions. You can use the static method to simulate the effect:


// Switch on an enum to simulate a missing method
public static Operation inverse(Operation op) {
    switch(op) {
        case PLUS:   return Operation.MINUS;
        case MINUS:  return Operation.PLUS;
        case TIMES:  return Operation.DIVIDE;
        case DIVIDE: return Operation.TIMES;


        default:  throw new AssertionError("Unknown op: " + op);
    }
}


If a method is not part of an enumeration type, you should also use this technique on enumerated types that you control. The method may need to be used for some purposes, but it is usually not sufficient to include enumeration types.



Generally, enumerations are typically equivalent to the int constant in performance. A small performance disadvantage of enumerations is the spatial and temporal costs of loading and initializing enumeration types, but are unlikely to be noticeable in practice.



So when should you use enumerations? Any time you use an enumeration, you need a set of constants that are known to the members at compile time. This includes, of course, "natural enumeration types" such as planets, days of the week, and checkers. But it also contains other collections that you already know about all possible values at compile time, such as options on menus, action codes, and command-line flags. * * The set of constants in an enumeration type does not have to remain constant * *. The enumeration functionality is specifically designed to allow for the evolution of binary-compatible enumeration types.



In summary, the advantages of enum types over int constants are convincing. Enumerations are more readable, more secure, and more powerful. Many enumerations do not need to explicitly construct methods or members, but others can benefit by associating data with each constant and providing methods that behave as affected by this data. Using a single method to correlate multiple behaviors can reduce the enumeration. In this relatively rare case, you prefer to use constant-specific methods to enumerate your own values. If some, but not all, enumeration constants share common behavior, consider the policy enumeration pattern.



Effective Java Third edition--34. overriding integer constants with enum types


Related Article

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.