第1条 用enum代替int常量
-
枚举类型是指由一组固定的常量组成合法值的类型,例如一年中的季节、太阳系中的行星或者一副牌中的花色。
-
枚举类型是实例受控的,它们是单例的泛型化,本质上,是单元素的枚举。
-
为了将数据与枚举常量关联起来,得声明实例域,并编写一个带有数据并将数据保存在域中的构造器。
-
枚举天生就是不可变的,因此,所有的域都应该为final的。
-
枚举构造器不可以访问枚举的静态域:因为构造器运行时,这些静态域还没有被初始化。唯一的特例是:枚举常量无法通过其构造器访问另一个构造器。
-
枚举中的switch语句适合于给外部的枚举类型增加特定于常量的行为。
-
与int常量相比,枚举有个性能缺点:即装载和初始化枚举时会需要空间和时间成本,但在实践中几乎注意不到这个问题。
-
什么时候应该使用枚举:每当需要一组固定常量,并且在编译时就知道其成员的时候,就应该使用枚举。
-
枚举类型中的常量集并不一定要始终保持不变。
-
总而言之,与int常量相比,枚举类型的优势是不言而喻:可读性,更安全,功能更加强大。
第2条 用实例域代替序数
-
枚举天生就与一个单独的int值相关联
-
永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实例域中。
/** * 用实例域代替序数 * @author qiweiwei * @date 2019年7月19日-上午9:34:53 * @describle */public enum Ensemble { SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5), SEXTET(6), SEPTET(7), OCTET(8), DOUBLE_QUARTET(8), NONET(9), DECTET(10), TRIPLE_QUARTET(12); private final int numberOfMusicians; Ensemble(int size) { this.numberOfMusicians = size; } public int numberOfMusicians() { return numberOfMusicians; }}
第3条 用EnumSet代替位域
-
EnumSet类来有效地表示从单个枚举类型中提取的多个值的多个集合。
-
总而言之,正是因为枚举类型要用在集合中,所以没有理由用位域来表示它。EnumSet类集位域的简洁性和性能优势以及枚举的所有优点。
/** * NOT USE THIS * int 枚举模式的text * @author qiweiwei * @date 2019年7月19日-上午11:11:50 * @describle */public class Text { //加粗 public static final int STYLE_BOLD = 1 << 0;//1 //斜体 public static final int STYLE_ITALIC = 1 << 1;//2 //下划线 public static final int STYLE_UNDERLINE = 1 << 2;//4 //删除线 public static final int STYLE_STRIKETHROUGH = 1 << 3;//8 public void applyStyles(int styles) { //TODO } public static void main(String[] args) { Text text = new Text(); text.applyStyles(STYLE_BOLD | STYLE_ITALIC); }}
/** * 使用EnumSet代替位域 * @author qiweiwei * @date 2019年7月19日-上午11:19:24 * @describle */public class TextApplayEnumSet { public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETROUGH } public void applyStyles(Set<Style> styles) { //TODO } public static void main(String[] args) { TextApplayEnumSet text = new TextApplayEnumSet(); text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC)); }}
第4条 用EnumMap代替序数索引
-
EnumMap实现专门用于枚举键
-
最好不要用序数来索引数组,而要使用EnumMap。如果你所表示的这种关系是多维的,就使用EnumMap<..., EnumMap<...>>。
-
一般情况下不使用Enum.ordinal
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; } public static void main(String[] args) { Plant[] garden = new Plant[3]; garden[0] = new Plant("annual", LifeCycle.ANNUAL); garden[1] = new Plant("perennial", LifeCycle.PERENNIAL); garden[2] = new Plant("biennial", LifeCycle.BIENNIAL); //不稳健的方法 Set<Plant>[] plantsByLifeCycle = (Set<Plant>[])new Set[Plant.LifeCycle.values().length]; for (int i = 0; i < plantsByLifeCycle.length; i++) { plantsByLifeCycle[i] = new HashSet<Plant>(); } for (Plant p : garden) { int s = p.lifeCycle.ordinal(); plantsByLifeCycle[s].add(p); } for (int i = 0; i < plantsByLifeCycle.length; i++) { System.out.printf("%s: %s%n", Plant.LifeCycle.values()[i], plantsByLifeCycle[i]); } System.out.println("----------------"); //使用EnumMap的方法 Map<Plant.LifeCycle, Set<Plant>> plantsByLifeCycleMap = new EnumMap<Plant.LifeCycle, Set<Plant>>(Plant.LifeCycle.class); for (Plant.LifeCycle lifeCycle : Plant.LifeCycle.values()) { plantsByLifeCycleMap.put(lifeCycle, new HashSet<Plant>()); } for (Plant p : garden) { plantsByLifeCycleMap.get(p.lifeCycle).add(p); } System.out.println(plantsByLifeCycleMap); System.out.println("----------------"); //选择自己的映射实现,空间与时间不吻合 System.out.println(Arrays.stream(garden).collect(Collectors.groupingBy(p -> p.lifeCycle))); System.out.println("----------------"); //指定实现 System.out.println(Arrays.stream(garden).collect( Collectors.groupingBy(p -> p.lifeCycle, () -> new EnumMap<>(LifeCycle.class), Collectors.toSet()))); }}
第5条 用接口模拟可扩展的枚举
-
虽然无法编写可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型来对它进行模拟。
//接口public interface Operation { double apply(double x, double y);}//基础操作public enum BasicOperation implements Operation { PLUS("+") { @Override public double apply(double x, double y) { return x + y; } }, MINUS("-") { @Override public double apply(double x, double y) { return x - y; } }, TIMES("*") { @Override public double apply(double x, double y) { return x * y; } }, DIVIDE("/") { @Override public double apply(double x, double y) { return x / y; } }; private final String symbol; private BasicOperation(String symbol) { this.symbol = symbol; } @Override public String toString() { return symbol; }}//扩展public enum ExtendedOperation implements Operation { EXP("^") { @Override public double apply(double x, double y) { return Math.pow(x, y); } }, REMAINDER("%") { @Override public double apply(double x, double y) { return x % y; } }; private final String symbol; private ExtendedOperation(String symbol) { this.symbol = symbol; } @Override public String toString() { return symbol; }}//两种用法import java.util.Arrays;import java.util.Collection;public class Test { public static void main(String[] args) { double x = 4d; double y = 2d; test(ExtendedOperation.class, x, y); System.out.println("--------"); test(Arrays.asList(ExtendedOperation.values()), x, y); } private static <T extends Enum<T> & Operation> void test( Class<T> opEnumType, double x, double y) { for (Operation operation : opEnumType.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) { System.out.printf("%f %s %f = %f%n", x, operation, y, operation.apply(x, y)); } }}
第6条 注解优先于命名模式
-
命名模式(要求方法或类或参数名称以什么样的格式命名,例如JUnit4之前的版本,要求方法以test开头)的三个缺点:
-
文字拼写错误会导致失败,且没有任何提示
-
无法确保它们只用于相应的程序元素上。
-
它们没有提供将参数值与程序元素关联起来的好方法。
-
-
注解很好的解决了以上问题
-
注解永远不会改变被注解代码的语义,但是使它可以通过工具进行特殊的处理。
-
既然有了注解,就完全没有理由再使用命名模式了。
-
除了“工具铁匠”(平台框架程序员)之外,大多数程序员都不必定义注解类型。但是所有的程序员都应该使用Java平台所提供的预定义的注解类型(第40条和第27条)。还要考虑使用IDE或者静态分析工具所提供的任何注解。
第7条 坚持使用Override注解
-
在你想要覆盖超类声明的每个方法声明中使用Override注解,编译器就可以替你防止大量的错误。
第8条 用标记接口定义类型
-
标记接口是不包含方法声明的接口,它只指明一个类实现了具有某种属性的接口。例如,考虑Serializable接口,通过实现这个接口,类表明它的实例可以被写到ObjectOutputStream中(或被序列化)。
-
标记接口定义的类型是由被标记类的实例实现的;标记注解则没有定义这样的类型。
-
标记接口胜过标记注解的另一个优点是,它们可以被更加精确地进行锁定。
-
标记注解胜过标记接口的最大优点在于,它们是更大的注解机制的一部分。
-
何时用标记注解?何时用标记接口?
-
如果标记是应用于任何程序元素而不是类或者接口,就必须使用注解,因为只有类和接口可以用来实现或者扩展接口
-