{Effective Java} Chap 6 Enums and Annotations



In release 1.5, enum and annotation type are added.


Item 30: Use enums instead of int constants

Int Enum pattern:

not type safety.

they're complie-time constants, so if changed, cilents must recompile the program.

Can't print or display them.


String Enum pattern:

performance problem when need to compare strings.

hard-code string constants may contain typographical error.


Enum type: 

Instance controlled, they're a generalization ofsingletons, all fields should befinal.

full-fledged classes, more powerful, final class so we can't construct or extend them.

The basic idea behind: 

classes that export one instance for each enumeration constant via a public static final field.

Provide type safety: find compile-time errors.

Enums can have namespaces, we can add or reorder constants in an enum type without recompiling because the fields that export the constants provide a layer of insulation(隔离,绝缘) between enum type and its clients: the constant values are not compiled into the clients. We can also print them.

We can add arbitrary methods and fields and interfaces because enum hashigh-quality implementations of Object methods, e.g theirSerialized form is designed to withstand most changes to the enum type.

A enum type can start life as a simple collection of constants and evolve over time into a full-featured abstraction.

E.g. planet.

package effective.java;

//Enum type with data and behavior
public enum Planet {
	MERCURY(3.302E+23, 2.439E6), VENUS(4.869E24, 6.052E6);

	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
	private 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
	}
}

public static void main(String[] args) {
		double venusWeight = 100.0;// It's not real value
		double mass = venusWeight / Planet.VENUS.surfaceGravity();
		for (Planet planet : Planet.values()) {
			System.out.printf("Weight on %s is %f%n", planet,
					planet.surfaceWeight(mass));
		}

	}


To associate data with enum constants, declare instance fields and write a constructor that takes the data and stores it in the fields.

We should limit the accessibility of those enums. 

E.g java.math.RoundingMode enum type provide useful abstractions for many other classes, so we can put it in top-level.


Associate different behavior with each enum constant: 

Constant specific method implementations

package effective.java;

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 symbolString;

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

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

	abstract double apply(double x, double y);

	public static void mian(String[] args) {
		double x = 2.0;
		double y = 4.0;

		for (Operation operation : Operation.values()) {
			System.out.printf("%f %s %f = %f %n", x, operation, y,
					operation.apply(x, y));
		}
	}
}

How to translate a String to Enum:

private static final Map<String, Operation> STRINGTOENUM_MAP = new HashMap<String, Operation>();
	static {
		for (Operation operation : values()) {
			STRINGTOENUM_MAP.put(operation.toString(), operation);
		}
	}

	public static Operation fromString(String symbol) {
		return STRINGTOENUM_MAP.get(symbol);
	}

Notice: try to initialize map in constructor will cause compilation error. Constructors can't access staic fields because they have not yet been initialized when the constructors run.


Disadvantage of Constant specific method: code duplication


Notice: why do we not use switch case? It's hard to maintain, you may forget to add specifi case for a new item. But swtich on enums are good for augmenting external enum types with constant-specific behavior.

e.g we want to have a instance method to control Operation.

//switch on an enum to simulate a missing method
	public static Operation inverse(Operation operation) {
		switch (operation) {
		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: " + operation);
		}
	}



Use private strategy enum, it's safer and more flexible

 e.g Payroll for everyday

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

	//the strategy enum type
	private enum PayType {
		WEEKDAY {

			@Override
			double overtimePay(double hrs, double payRate) {
				return hrs <= HOURS_PER_SHIFT ? 0 : (hrs - HOURS_PER_SHIFT)
						* payRate / 2;
			}
		},

		WEEKEND {

			@Override
			double overtimePay(double hrs, double payRate) {
				return hrs * HOURS_PER_SHIFT / 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);
		}
	}
}


Enum need more space and time cost than int constants.

When to use enum: anytime when we want to fix a set of constants, such as operation codes and flags.


In summary, enum types are safer and more readable than int constants. Many enums can associate data with each constant and provide methods whose behaviors is affected by this data. We can also use constant specific methods, if multiply constants share common behaviors we can use strategy enum pattern.


Item 31: Use instance fields instead of ordinals(序数,有顺序的,依次的)

All enums have an ordinal method which returns the numerical position of each enum constant.

Never derive a value associated with an enum from its ordinal, store it in an instance field instead.

enum Ensemble {
	SOLO(1), DUE(2), TRIO(3);

	private final int numberOfMusicians;

	Ensemble(int number) {
		this.numberOfMusicians = number;
	}

	public int numberOfMusicians() {
		return numberOfMusicians;
	}
}


In summary, avoid ordinal method.


Item 32: Use EnumSet instead of bit fields

java.util.EnumSet represent bit vector, it is shorter, clearer and safer.

Just because an enumerated type will be used in sets, there is no reason to represent it with bit fields. It combines the conciseness and performance of bit fields with all the many advantages of enum types.

public class Text{

	//EnumSet - a modern replacement for bit fields
	public enum Style{
		BOLD, ITALIC, UNDERLINE
	}
	
	//Any set could be passed in, but EnumSet is clearly best
	public void applyStyles(Set<Style> styles){...}
}

//How to use 
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));



Item 33: Use EnumMap instead of ordinal indexing

public class Herb{
	public enum Type{
		ANNUAL, PERENNIAL, BIENNIAL
	}
	
	private final String nameString;
	private final Type type;
	
	Herb(String name, Type type){
		this.nameString = name;
		this.type = type;
	}
	
	@Override public String toString(){
		return nameString;
	}
}

//Using an EnumMap to associate data with an enum
	Map<Herb.Type, Set<Herb>> herbsByTypeMap = new EnumMap<Herb.Type, Set<Herb>>(Herb.Type.class);
	
	for(Herb.Type t: Herb.Type.values()){
		herbsByTypeMap.put(t, new HashSet<Herb>());
	}
	for(Herb h: garden){
		herbsByTypeMap.get(h.type).add(h);
	}
//Using a nested EnumMap to associate data with enum pairs
public enum Phase {
	SOLID, LIQUID, GAS;

	public enum Transition {
		MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID) ; //and so on

		private final Phase srcPhase;
		private final Phase dstPhase;

		Transition(Phase src, Phase dst) {
			this.srcPhase = src;
			this.dstPhase = dst;
		}
	}

	// Initialize the phase transition map
	private static final Map<Phase, Map<Phase, Transition>> m = new EnumMap<Phase, Map<Phase, Transition>>(
			Phase.class);

	static {
		for (Phase phase : Phase.values()) {
			m.put(phase, new EnumMap<Phase, Phase.Transition>(Phase.class));
		}
		for (Transition transition : Transition.values()) {
			m.get(transition.srcPhase).put(transition.dstPhase, transition);
		}
	}

	public static Transition from(Phase src, Phase dst) {
		return m.get(src).get(dst);
	}
}

In summary, it's inappropriate to use ordinals to index arrays : use EnumMap instead.


Item 34: Emulate extensible enums with interfaces

Enum types can implements interfaces.


public interface Operation {
	double apply(double x, double y);
}

public enum BasicOperation implements 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 enum ExtendedOperation implements Operation{
	EXP("^") {
		public double apply(double x, double y) {
			return Math.pow(x, y);
		}
	};
}

public static void mian(String[] args) {
		double x = 2.0;
		double y = 4.0;
		test(ExtendedOperation.class, x,y);
		// test(Arrays.asList(ExtendedOperation.values()), x, y);
	}
	
	private static <T extends Enum<T> & Operation> void test(Class<T> opSet, double x, double y){

		for (Operation operation : opSet.getEnumConstants()) {
			System.out.printf("%f %s %f = %f %n", x, operation, y,
					operation.apply(x, y));
		}
	}
	
	private static void test(<Collection<?> extends Operation> opSet, double x, double y){

		for (Operation operation : opSet.getEnumConstants()) {
			System.out.printf("%f %s %f = %f %n", x, operation, y,
					operation.apply(x, y));
		}
	}



In summary, while you cannot write an extensible enum type, you can emulate it by writing an interface to go with a basic enum type that implements the interface. These enums can be used wherever the basic enum type can be used, assuming APIs are written in terms of the interface.


Item 35: Prefer annotations to naming patterns


There is simply no reason to use naming patterns now that we have annotations.

In summary, we should use the predefined annotation types provided by Java. Also consider using any annotations provided by IDE or static analysis tools. They can improve the quality of the diagnostic information provided by these tools.


Item 35: Consistently use the Override annotation

use the Override annotation on every method declaration that you believe to override a superclass declaration.

In summary, the compiler can protect you from a great many errors if you use Override annotation on the method you think override a supertype declaration. In concrete classes, you need not annotate methods that you believe to override abstract method declarations.


Item 36: Use marker interfaces to define types

Marker interfaces define a type that is implemented by instances of the marked class, marker annotations do not.

If you find yourself writing a marker annotation type whose target is ElementType.TYPE, take the time to figure out whether it really should be an annotation type, or whether a marker interface would be more appropriate.



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Anyanyamy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值