34.  使用枚举类型替代整型常量

  常量的语义表达不清晰,只能靠前面的名称来区分。枚举具有可读性、更安全、更强大等优势。而且枚举类型对象之间的值比较可以使用==来比较值是否相等的,不是必须使用equals方法。

  要将数据与枚举常量相关联,首先需要声明实例属性并编写一个构造方法,构造方法带有数据并将数据保存在属性中。枚举本质上是不变的,所有的属性都应设为final。

如下:

一个原始的常量类保存int值:

public class Constants {

    public static final int APPLE_FUJT = 1;

    public static final int APPLE_PIPPIN = 2;

    public static final int APPLE_other = 3;

}

改装为枚举类型后的类如下:

public enum Apples {
APPLE_FUJT(1), APPLE_PIPPIN(2), APPLE_other(3); final int number; private Apples(int number) {
this.number = number;
} }

测试代码:

public class Client {

    public static void main(String[] args) {
System.out.println(Constants.APPLE_FUJT); System.out.println(Apples.APPLE_FUJT.number);
} }

JDK中的枚举类型代替整形的例子:RoundingMode类和BigDecimal紧密关联

public enum RoundingMode {

    UP(BigDecimal.ROUND_UP),

    DOWN(BigDecimal.ROUND_DOWN),

    CEILING(BigDecimal.ROUND_CEILING),

    FLOOR(BigDecimal.ROUND_FLOOR),
HALF_UP(BigDecimal.ROUND_HALF_UP), HALF_DOWN(BigDecimal.ROUND_HALF_DOWN), HALF_EVEN(BigDecimal.ROUND_HALF_EVEN), UNNECESSARY(BigDecimal.ROUND_UNNECESSARY);
final int oldMode;
private RoundingMode(int oldMode) {
this.oldMode = oldMode;
} public static RoundingMode valueOf(int rm) {
switch(rm) { case BigDecimal.ROUND_UP:
return UP; case BigDecimal.ROUND_DOWN:
return DOWN; case BigDecimal.ROUND_CEILING:
return CEILING; case BigDecimal.ROUND_FLOOR:
return FLOOR; case BigDecimal.ROUND_HALF_UP:
return HALF_UP; case BigDecimal.ROUND_HALF_DOWN:
return HALF_DOWN; case BigDecimal.ROUND_HALF_EVEN:
return HALF_EVEN; case BigDecimal.ROUND_UNNECESSARY:
return UNNECESSARY; default:
throw new IllegalArgumentException("argument out of range");
}
}
}

BigDecimal中设置scale:

    public BigDecimal setScale(int newScale, RoundingMode roundingMode) {
return setScale(newScale, roundingMode.oldMode);
}

35.  使用实例属性代替序数

  也就是说枚举类的ordinal()方法返回的序号尽量不要使用。ordinal方法返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。

public class Client {

    public static void main(String[] args) {
System.out.println(Operation.PLUS.ordinal());
System.out.println(Operation.MINUS.ordinal());
System.out.println(Operation.TIMES.ordinal());
System.out.println(Operation.DIVIDE.ordinal());
} }

结果:

0
1
2
3

  如果需要用到序号,自己定义一个成员属性。如下:

public enum Operation {

    PLUS(1) {
@Override
double operate(double num1, double num2) {
return num1 + num2;
}
},
MINUS(2) {
@Override
double operate(double num1, double num2) {
return num1 - num2;
}
},
TIMES(3) {
@Override
double operate(double num1, double num2) {
return num1 * num2;
}
},
DIVIDE(4) {
@Override
double operate(double num1, double num2) {
return num1 / num2;
}
}; private int index; private Operation(int index) {
this.index = index;
} abstract double operate(double num1, double num2); public int getIndex() {
return index;
} public void setIndex(int index) {
this.index = index;
} }

测试代码:

public class Client {

    public static void main(String[] args) {
System.out.println(Operation.PLUS.getIndex());
System.out.println(Operation.MINUS.getIndex());
System.out.println(Operation.TIMES.getIndex());
System.out.println(Operation.DIVIDE.getIndex());
} }

结果:

1
2
3
4

36.  使用EnumSet替代位属性

  有时候我们使用位属性来做一些属性,比如按位 &,| 等操作。

常量类和枚举改造的常量类同上面34,如下:

    public static void main(String[] args) {
eatFruits(Constants.APPLE_FUJT | Constants.APPLE_PIPPIN);
} static void eatFruits(int fruit) {
System.out.println(fruit);
}

  我们在eatFruits知道参数是3,所以知道是1和2.

    public static void main(String[] args) {
EnumSet<Apples> enumSets = EnumSet.of(Apples.APPLE_FUJT, Apples.APPLE_PIPPIN);
eatFruits(enumSets);
} static void eatFruits(Set fruit) {
System.out.println(fruit);
}

结果:

[APPLE_FUJT, APPLE_PIPPIN]

37.  使用EnumMap替代序数索引

  使用序数来索引数组很不合适,改用EnumMap。如果代表的关系是多维的,用EnumMap<...,EnumMap<...>>。程序员应该很少用Enum.ordinal。

如下植物类:

/**
* 植物类
*
* @author Administrator
*
*/
public class Plant {
enum LifeCycle {
// 1年生, 永久, 两年生
ANNUAL, PERENNIAL, BIENNIAL;
} final String name;
final LifeCycle lifeCycle; public Plant(String name, LifeCycle lifeCycle) {
super();
this.name = name;
this.lifeCycle = lifeCycle;
} public String getName() {
return name;
} public LifeCycle getLifeCycle() {
return lifeCycle;
} @Override
public String toString() {
return name;
}
}

  现在假设有一组植物代表一个花园,想要列出这些由声明周期组织的植物(一年生、多年生、两年生)。

如下:普通程序的做法是构建三个集合,每个声明周期作为一个花园,并且从植物数组中筛选植物组成一个花园,筛选的时候根据ordinal()确定对应的花园。

import java.util.HashSet;
import java.util.Set; public class Client { public static void main(String[] args) {
// 3个数组
Set<Plant>[] plantsByLifeCycle = new Set[Plant.LifeCycle.values().length];
for (int i = 0; i < Plant.LifeCycle.values().length; i++) {
plantsByLifeCycle[i] = new HashSet<>();
} Plant[] garden = { new Plant("西兰花", Plant.LifeCycle.PERENNIAL), new Plant("菊花", Plant.LifeCycle.ANNUAL),
new Plant("木兰", Plant.LifeCycle.BIENNIAL), new Plant("菜兰", Plant.LifeCycle.PERENNIAL) }; for (Plant p : garden) {
plantsByLifeCycle[p.lifeCycle.ordinal()].add(p);
} for (int i = 0; i < plantsByLifeCycle.length; i++) {
System.out.printf("%s: %s%n", Plant.LifeCycle.values()[i], plantsByLifeCycle[i]);
}
} }

结果:

ANNUAL: [菊花]
PERENNIAL: [西兰花, 菜兰]
BIENNIAL: [木兰]

  这种做法可以实现,但是充满了问题,而且代码阅读起来比较费劲。

更好的办法是使用EnumMap:(如果按我的一开始的想法是HashMap<LifeStyle,Set<Plant>>)

import java.util.EnumMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set; public class Client { public static void main(String[] args) {
Plant[] garden = { new Plant("西兰花", Plant.LifeCycle.PERENNIAL), new Plant("菊花", Plant.LifeCycle.ANNUAL),
new Plant("木兰", Plant.LifeCycle.BIENNIAL), new Plant("菜兰", Plant.LifeCycle.PERENNIAL) }; Map<Plant.LifeCycle, Set<Plant>> maps = new EnumMap<>(Plant.LifeCycle.class);
for (Plant.LifeCycle lifeCycle : Plant.LifeCycle.values()) {
maps.put(lifeCycle, new HashSet<>());
} for (Plant p : garden) {
maps.get(p.lifeCycle).add(p);
} System.out.println(maps);
} }

结果:

{ANNUAL=[菊花], PERENNIAL=[菜兰, 西兰花], BIENNIAL=[木兰]}

  

38.  使用接口模拟可扩展的枚举

  虽然不能编写可扩展的枚举类型,但是可以编写一个接口配合实现接口的基本的枚举类型来进行模拟。

比如一个运算例子:

public interface OperationInter {

    double operate(double num1, double num2);
}
public enum BasicOperation implements OperationInter {

    PLUS(1) {
@Override
public double operate(double num1, double num2) {
return num1 + num2;
}
},
MINUS(2) {
@Override
public double operate(double num1, double num2) {
return num1 - num2;
}
},
TIMES(3) {
@Override
public double operate(double num1, double num2) {
return num1 * num2;
}
},
DIVIDE(4) {
@Override
public double operate(double num1, double num2) {
return num1 / num2;
}
}; private int index; private BasicOperation(int index) {
this.index = index;
} public int getIndex() {
return index;
} public void setIndex(int index) {
this.index = index;
} }

扩展接口:

public enum ExtendsOperation implements OperationInter {

    MOLE(5) {
@Override
public double operate(double num1, double num2) {
return num1 % num2;
}
}; private int index; private ExtendsOperation(int index) {
this.index = index;
} public int getIndex() {
return index;
} public void setIndex(int index) {
this.index = index;
} }

39.  注解优于命名模式

40.  始终使用Override注解

  这个注解只能使用在方法上面,它表明带此注解的方法声明重写了父类的声明。

  如果在一个方法声明中使用该注解并且认为要重写父类声明,那么可以编译器可以保护免受很多错误的影响。但有一个例外,在具体类中,不需要注解标记你确信可以重写抽象方法声明的方法。

阿里规约也有一条:所有的覆写方法,必须加@Override 注解。

  说明: getObject()与 get0bject()的问题。一个是字母的 O,一个是数字的 0,加@Override可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。

41.  使用标记接口定义类型

  标记接口不包含方法声明,只是指定(或标记)一个类实现了具有某些属性的接口。例如:Clonable、Serializable接口等。

  Java 的序列化机制使用 Serializable 标记接口来指示某个类型是可序列化的。 对传递给它的对象进行序列化的 ObjectOutputStream.writeObject 方法要求其参数可序列化。 如果此方法的参数是Serializable 类型,在编译时会检测到序列化不适当对象的尝试(通过类型检查)。 编译时错误检测是标记接口的意图,但不幸的是, ObjectOutputStream.write API 没有利用 Serializable 接口:它的参数被声明为Object 类型,所以尝试序列化一个不可序列化的对象直到运行时才会失败

42.  lambda表达式优于匿名类

  lambda 没有名称和文档; 如果计算不是自解释的,或者超过几行,则不要将其放入 lambda表达式中。 一行代码对于 lambda 说是理想的,三行代码是合理的最大值。 如果违反这一规定,可能会严重损害程序的可读性。

对于逆序排序,匿名类如下:

    public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(3);
list.add(2); Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o2, o1);
}
});
}

使用lambda表达式如下:

    public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(3);
list.add(2); Collections.sort(list, (s1, s2) -> Integer.compare(s2, s1));
}

  其参数s1和s2及其返回值类型int型,编译器会使用称为类型推断的过程从上下文推断出这些类型。

43.  方法引用优于lambda表达式

  如果方法引用看起来更简短更清晰,就使用方法引用;否则使用lambda表达式。

  通过方法引用来简写lambda表达式中已经存在的方法,这种特性就叫做方法引用(Method Reference)。比如我们用上面的:

    public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(3);
list.add(2); Collections.sort(list, Integer::compare);
System.out.println(list);
}

下面是五种方法引用:

44.  优先使用标准的函数式接口

Effective.Java第34-44条(枚举)的更多相关文章

  1. <<Effective Java>> 第四十三条

    <<Effective Java>> 第四十三条:返回零长度的数组或者集合,而不是null 如果一个方法的返回值类型是集合或者数组 ,如果在方法内部需要返回的集合或者数组是零长 ...

  2. 关于Java代码优化的44条建议!

    关于Java代码优化的N条建议! 本文是作者:五月的仓颉 结合自己的工作和平时学习的体验重新谈一下为什么要进行代码优化.在修改之前,作者的说法是这样的: 就像鲸鱼吃虾米一样,也许吃一个两个虾米对于鲸鱼 ...

  3. Effective Java —— 用私有构造器或枚举类型强化单例属性

    本文参考 本篇文章参考自<Effective Java>第三版第三条"Enforce the singleton property with a private construc ...

  4. 《Effective Java》第6章 枚举和注解

    第30条:用enum代替int常量 将加班工资计算移到一个私有的嵌套枚举中,将这个策略枚举(strategy enum)的实例传到PayrollDay枚举的构造器中.之后PayrollDay枚举将加班 ...

  5. 《Effective Java》读书笔记 - 6.枚举和注解

    Chapter 6 Enums and Annotations Item 30: Use enums instead of int constants Enum类型无非也是个普通的class,所以你可 ...

  6. [Effective JavaScript 笔记]第44条:使用null原型以防止原型污染

    第43条中讲到的就算是用了Object的直接实例,也无法完全避免,Object.prototype对象修改,造成的原型污染.防止原型污染最简单的方式之一就是不使用原型.在ES5之前,并没有标准的方式创 ...

  7. Effective java读书札记第一条之 考虑用静态工厂方法取代构造器

    对于类而言,为了让client获取它自身的一个实例,最经常使用的方法就是提供一个共同拥有的构造器. 另一种放你发,也应该子每一个程序猿的工具箱中占有一席之地.类能够提供一个共同拥有的静态 工厂方法.它 ...

  8. <<Effective Java>>之善用组合而不是继承

    使用JAVA这门OO语言,第一要义就是,如果类不是专门设计来用于被继承的就尽量不要使用继承而应该使用组合 从上图2看,我们的类B复写了类A的add喝addALL方法,目的是每次调用的时候,我们就能统计 ...

  9. Effective Java 第三版——34. 使用枚举类型替代整型常量

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  10. 《Effective Java》学习笔记 —— 枚举、注解与方法

    Java的枚举.注解与方法... 第30条 用枚举代替int常量 第31条 用实例域代替序数 可以考虑定义一个final int 代替枚举中的 ordinal() 方法. 第32条 用EnumSet代 ...

随机推荐

  1. springboot启动报错,Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.

    报错: Error starting ApplicationContext. To display the conditions report re-run your application with ...

  2. mpvue 小程序开发之 数据埋点统计

    mpvue 小程序开发之 数据埋点统计 在开发过程中,有数据统计的需求,需要获取小程序当前页面和来源页面的数据,以及页面的停留时间 在对小程序api进行了一番研究之后,发现获取这些数据其实并不难 当前 ...

  3. git的clone

    在使用git来进行版本控制时,为了得一个项目的拷贝(copy),我们需要知道这个项目仓库的地址(Git URL). Git能在许多协议下使用,所以Git URL可能以ssh://, http(s):/ ...

  4. Django的视图系统:View

    一.CBV和FBV FBV:functoin based view,基于函数的view 我们之前写过的都是基于函数的view CBV:class based view,基于类的view 定义CBV: ...

  5. .net web api 权限验证

    做一个登录权限验证. 开始吧. using System; using System.Collections.Generic; using System.Drawing; using System.D ...

  6. Excel分列,Excel 列拆分,Excel根据分隔符号拆分某列

    解决方案: https://zhidao.baidu.com/question/572807483.html 步骤:数据--分列--下一步--其它---下一步-- 注意的此操作会覆盖当前列和后n列(根 ...

  7. C 语言实现回调函数

    优点 不需要改变调用的主函数,只需添加命令和相应函数. #include "stdio.h" #include "stdlib.h" #include &quo ...

  8. HTTP协议那些事儿

    HTTP协议简介 超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式.协作式和超媒体信息系统的应用层协议.HTTP是万维网的数据通信的基础. ...

  9. Python程序 #!/usr/bin/python 的解释

    关于脚本第一行的 #!/usr/bin/python 的解释,相信很多不熟悉 Linux 系统的同学需要普及这个知识,脚本语言的第一行,只对 Linux/Unix 用户适用,用来指定本脚本用什么解释器 ...

  10. python基本数据类型的时间复杂度

    1.list 内部实现是数组 2.dict 内部实现是hash函数+哈希桶.一个好的hash函数使到哈希桶中的值只有一个,若多个key hash到了同一个哈希桶中,称之为哈希冲突. 3.set 内部实 ...