TOC
- Chapter 1. Introduction *(PASS)*
- Chapter 2. Creating and Destroying Objects
- Item 1: Consider static factory methods instead of constructors
- Item 2: Consider a builder when faced with many constructor parameters
- Item 3: Enforce the singleton property with a private constructor or an enum type
- Item 4: Enforce noninstantiability with a private constructor
- Item 5: Prefer dependency injection to hardwiring resources
- Item 6: Avoid creating unnecessary objects
- Item 7: Eliminate obsolete object references
- Item 8: Avoid finalizers and cleaners
- Item 9: Prefer try-with-resources to try-finally
- Chapter 3. Methods Common to All Objects
- Chapter 4. Classes and Interfaces
- Item 15: Minimize the accessibility of classes and members
- Item 16: In public classes, use accessor methods, not public fields
- Item 17: Minimize mutability
- Item 18: Favor composition over inheritance
- Item 19: Design and document for inheritance or else prohibit it
- Item 20: Prefer interfaces to abstract classes
- Item 21: Design interfaces for posterity
- Item 22: Use interfaces only to define types
- Item 23: Prefer class hierarchies to tagged classes
- Item 24: Favor static member classes over nonstatic
- Item 25: Limit source files to a single top-level class
- Chapter 5. Generics
- Item 26: Don’t use raw types
- Item 27: Eliminate unchecked warnings
- Item 28: Prefer lists to arrays
- Item 29: Favor generic types
- Item 30: Favor generic methods
- Item 31: Use bounded wildcards to increase API flexibility
- Item 32: Combine generics and varargs judiciously
- Item 33: Consider typesafe heterogeneous containers
- Chapter 6. Enums and Annotations
- Item 34: Use enums instead of int constants
- Item 35: Use instance fields instead of ordinals
- Item 36: Use EnumSet instead of bit fields
- Item 37: Use EnumMap instead of ordinal indexing
- Item 38: Emulate extensible enums with interfaces
- Item 39: Prefer annotations to naming patterns
- Item 40: Consistently use the Override annotation
- Item 41: Use marker interfaces to define types
- Item 42: Prefer lambdas to anonymous classes
- Item 43: Prefer method references to lambdas
- Item 44: Favor the use of standard functional interfaces
- Item 45: Use streams judiciously
- Item 46: Prefer side-effect-free functions in streams
- Item 47: Prefer Collection to Stream as a return type
- Item 48: Use caution when making streams parallel
- Chapter 8. Methods
- Item 49: Check parameters for validity
- Item 50: Make defensive copies when needed
- Item 51: Design method signatures carefully
- Item 52: Use overloading judiciously
- Item 53: Use varargs judiciously
- Item 54: Return empty collections or arrays, not nulls
- Item 55: Return optionals judiciously
- Item 56: Write doc comments for all exposed API elements
- Chapter 9. General Programming
- Item 57: Minimize the scope of local variables
- Item 58: Prefer for-each loops to traditional for loops
- Item 59: Know and use the libraries
- Item 60: Avoid float and double if exact answers are required
- Item 61: Prefer primitive types to boxed primitives
- Item 62: Avoid strings where other types are more appropriate
- Item 63: Beware the performance of string concatenation
- Item 64: Refer to objects by their interfaces
- Item 65: Prefer interfaces to reflection
- Item 66: Use native methods judiciously
- Item 67: Optimize judiciously
- Item 68: Adhere to generally accepted naming conventions
- Chapter 10. Exceptions
- Item 69: Use exceptions only for exceptional conditions
- Item 70: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors
- Item 71: Avoid unnecessary use of checked exceptions
- Item 72: Favor the use of standard exceptions
- Item 73: Throw exceptions appropriate to the abstraction
- Item 74: Document all exceptions thrown by each method
- Item 75: Include failure-capture information in detail messages
- Item 76: Strive for failure atomicity
- Item 77: Don’t ignore exceptions
- Chapter 11. Concurrency
Chapter 1. Introduction (PASS)
Chapter 2. Creating and Destroying Objects
Item 1: Consider static factory methods instead of constructors
主要讨论了需要创建对象时,静态工厂方法的优越性。
与直接用构造器构造相比,其优点如下:
- 方法名的存在能够便于区分不同的构造方法
- 避免创建重复的实例
- 可以获得子类型的对象,而且可以根据不同参数得到不同类型
- 可以通过修改工厂方法轻松返回在此之前没编写的类
同时依赖静态工厂方法也有一些缺点,如只有私有构造方法的话就不能子类化、在API文档中不好发现。常见的静态工厂方法的命名包括:from、of、valueOf、instance、getInstance、create、newInstance、getXXX、newXXX、XXX(如Collections.list();
)。
Item 2: Consider a builder when faced with many constructor parameters
主要讨论了建造者模式,在面对多个构造器变量、尤其是变量可能更改或者会有不同子类的情况下的优势。
其主要方法是,在类内构建一个内部静态类Builder,把变量成员再在其中定义一遍,再在其中增加这样的方法:
public Builder calories(int val) { calories = val; return this; }
这样就可以以简洁明了的方式初始化需要多个变量构造的类:
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();
Item 3: Enforce the singleton property with a private constructor or an enum type
主要讨论的单例模式的创建,要么需要把构造器设为私有(然后再代码块中定义静态实例),要么使用枚举类型。
更多资料可参考:Java中实现单例(Singleton)模式的八种方式
Item 4: Enforce noninstantiability with a private constructor
使用私有构造函数避免实例化。
对于一些工具类是有用的,如果不写构造函数,默认生成的构造函数就会是公有的。不过如果在IDEA中强行实例化一个只有静态方法的类,会提示“Instantiation of utility class XXX”。
Item 5: Prefer dependency injection to hardwiring resources
主要讲了依赖注入的概念。对于由底层资源参数化的类,相比于直接用静态工具类/单例的方式创造资源并获取,将资源直接作为构造器参数传入是更好的方法,而这就是依赖注入的一种。
Item 6: Avoid creating unnecessary objects
避免创建不必要的类,能够提升程序的性能。示例如下:
String s = new String("bikini"); // No!
String s = "bikini"; // Yes.
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); // No!
private static final Pattern ROMAN = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
return ROMAN.matcher(s).matches(); // Yes
private static long sum() {
Long sum = 0L; // should replace Long to long to avoid automatic unboxing
for (long i = 0; i <= Integer.MAX_VALUE; i++) sum += i;
return sum;
}
Item 7: Eliminate obsolete object references
主要讨论了通过排除不需要的引用来避免内存泄漏。
尽管Java拥有内存回收机制,但是如果要实现拥有存储管理功能的类,就要注意不要持有不需要的引用。本节举了三个常见的可能会发生内存泄漏的类的例子:一个用对象数组实现的栈、缓存和监听器。具体地,利用WeakHashMap中的弱引用特性可以帮助实现由外部是否引用键决定是否要回收条目的缓存。
Item 8: Avoid finalizers and cleaners
避免使用Java对象回收相关的两个方法:finalizer()和作为替代的cleaners相关方法。这些方法在实现时是不稳定的,除非是作为兜底方法或者在操作原生对象时使用。
Item 9: Prefer try-with-resources to try-finally
对于实现AutoCloseable接口的类,使用try-with-resources的方式获取资源并捕获异常,而不是try-finally。这样会使得程序更简洁,同时更好排查异常的产生。
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
AutoCloseable接口用来表示使用后需要关闭的资源,包含一个close()方法,会在try块(大括号内容)结束后自动运行。
Chapter 3. Methods Common to All Objects
Item 10: Obey the general contract when overriding equals
不需要重写equals的情况
- 每个类的实例都是唯一的,如Thread
- 类不需要提供逻辑相等的测试,如java.util.regex.Pattern
- 父类已经重写了equals,且子类的行为也差不多
- 私有类(equals不会被调用)
equals必须满足的等价关系
自反性、对称性、传递性、一致性、非空性
equals通用步骤
- 使用
==
判断参数是否为对象的引用 - 使用
instanceof
判断参数类型是否正确 - 把参数转化为正确的类型
- 检查类的必要字段是否互相对应相等。特别地,对于浮点数使用
Float/Double.compare()
;对于对象使用Objects.equals()
。
以AbstactSet的equals()为例:
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Set))
return false;
Collection<?> c = (Collection<?>) o;
if (c.size() != size())
return false;
try {
return containsAll(c);
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
}
其他注意事项
- 重写equals之后要重写hashCode方法
- 不要让相等的逻辑太过复杂
- 不要让重写equals变成重载(参数类型必须是Object)
- 为了提升性能,可以优先考虑能区分开对象的成员,即使是衍生成员
Item 11: Always override hashCode when you override equals
如果重写了equals却没有重写hashCode,两个相等的对象可能会同时存在一个HashSet中。
hashCode通用步骤
- 对于一个成员
f
计算哈希值的方法:如果是基本类型直接调用XX.hashCode(f)
,而如果是对象引用就递归调用其hashCode方法 - 对于类中的每一个成员,逐个按照上面的方法计算哈希值,并且利用
result = 31 * result + current;
计算得到结果
其他注意事项
- 不要只选择部分必要字段计算哈希值,不然会增加碰撞几率
- 不要对返回的哈希值的来源有过多的解释,日后改动时不会影响到调用者
Item 12: Always override toString
主要讲解了重写toString的重要性,并且要写好返回值注释。
Item 13: Override clone judiciously
讲了重写clone的一些问题。
- 对于实现Cloneable接口的类,其clone方法要么返回一个逐字段复制的类,要么抛出
CloneNotSupportedException
- clone的一般步骤:首先调用
super.clone
(一般还需要强制转换一下),然后对于类的对可变对象的引用的字段实现合理的复制。方法一般写为public(Object中的原本修饰符是protected) - 数组很适合clone方法,并且不需要强制转换返回结果
- 更好的复制对象方法是使用拷贝构造器或者拷贝工厂
Item 14: Consider implementing Comparable
实现Comparable接口的一些技巧。
public interface Comparable<T> {
int compareTo(T t);
}
- compareTo结果的正负值代表指定对象大于/小于当前对象。如果结果为0代表二者相等,但并不一定要求和equals的结果一致(如BigDecimal)
- 比较基本类型大小时使用包装类的静态compare方法,而不是大于小于符号
- Comparator接口配合比较器构造方法可以用简洁的方式定义复杂的比较逻辑:
private static final Comparator<PhoneNumber> COMPARATOR = comparingInt((PhoneNumber pn) -> pn.areaCode) .thenComparingInt(pn -> pn.prefix) .thenComparingInt(pn -> pn.lineNum); public int compareTo(PhoneNumber pn) { return COMPARATOR.compare(this, pn); }
Chapter 4. Classes and Interfaces
Item 15: Minimize the accessibility of classes and members
主要讲的观点是尽量缩小类和成员的可见范围。顶级类(即非内部类)的两个可见范围:package-private和public;类成员的可见范围:private、package-private、protected和public。其中package-private为不加修饰符的默认可见范围。
非零数组是可变的,所以public static final数组的存在是不应当的。
Item 16: In public classes, use accessor methods, not public fields
公共类用get/set方法(Accessors和Mutators)而不是直接将字段设为public,私有类则不需要。
Item 17: Minimize mutability
主要讲了尽可能使类不可变,至少也要尽可能减少可变的部分。
创建不可变类遵循的规则: 方法不能改变类的状态,确保类不可以被子类化,让所有字段final & private,以及确保对所有可变组件的独占访问权。
不可变类的优点主要有简单易用、天生线程安全无需同步、全部及部分可共享、可以作为其他类很好的构建组件,以及提供了故障原子性。主要的缺陷是性能问题,如修改某一部分只能重新创建一个完整的,如有必要可以创建可变的伴生类解决这个问题。
Item 18: Favor composition over inheritance
优先选择构建的方式而不是继承具体的类(这里说的继承不包含接口相关的内容)。继承违反了封装,父类的变动会影响子类,从而影响子类的行为,而且对于跨包的继承,由于对父类实现细节不清楚,还可能在方法实现上出现逻辑错误(如父类调用内部方法)。可以通过构建包装类并转发调用的方式来替代。
Item 19: Design and document for inheritance or else prohibit it
讲了设计可继承类的要点。主要有下:
- 必须说明(document)内部调用可重写的方法(非final的public/protected方法)的情况。可以用tag @implSpec
- 通过设计的比较好的protect方法/字段为其他内部方法提供调用。protect方法不能被对象调用,但是可以被子类继承。如AbstractList的clear方法调用了removeRange方法,后者对范围内的元素一个个实行remove方法。而由于removeRange设置为了protect,Arraylist继承了它之后将其重写为批量的内存更改,从而防止clear退化为 O ( n 2 ) O(n^2) O(n2)的时间复杂度
- 通过写(一般三个)子类来测试一个可继承类
- 构造器不能调用可重写的方法,继承Cloneable或Serializable的类在clone和readObject也同样不能。原因是可重写的方法会先执行,如果重写之后的方法调用了未初始化的内容就会出现问题
- 禁止在设计和文档都不允许安全子类化的类中进行子类化,可以通过final字段或者私有构造器+静态工厂来实现
Item 20: Prefer interfaces to abstract classes
本节讨论接口相比抽象类的优点,并讲了多重继承的相关内容。
接口相比抽象类的区别在这:Java 抽象类和接口的区别,其中比较重要的是接口可以多继承,并且一般不实现方法(但Java8之后可以实现默认方法)。本节还提出了接口的其他优点,包括接口可以简单地装饰到现有类上、允许构建非层次类型的框架、通过包装类的方式实现功能增强。
另外,将接口和抽象类相结合是实现模拟多重继承的另一种方式,由接口定义主要的行为、并可以提供一些主要的方法,而由抽象类实现其余的非主要方法。诸如AbstractCollection的类就是这种模板方法模式的例子。需要注意的是Object类的方法(如equals、hashCode)不能在接口中定义默认方法,而是要在抽象类中重写。
Item 21: Design interfaces for posterity
主要讲了接口的默认实现方法,默认方法不能保证适应所有的实现情况,为已有接口添加默认方法可能会带来问题,要谨慎使用。
Item 22: Use interfaces only to define types
不能把接口用来做静态常量的存储库,而是应该将常量定义在类中或者是工具类中。
Item 23: Prefer class hierarchies to tagged classes
不要在类中加flag区分两种类,而是抽象出共同点作为基类,然后分别继承实现。
Item 24: Favor static member classes over nonstatic
主要介绍了四种嵌套类的实现:方法外部用成员类,依据是否要引用其外层实例又分为静态类和分静态类;方法内部使用匿名类或本地类,根据是否一次性使用区分。静态内部类相比非静态内部类节省了更多的空间,也不会妨碍外层类的回收,因此优先考虑,如HashMap的Entry、Node的时间就是静态内部类。
Item 25: Limit source files to a single top-level class
不要将多个顶级类或接口放入单个源文件中,尽管编译器只关注文件中有一个公共的文件名同名的类/接口,而不在乎是否有其他的私有类/接口。
Chapter 5. Generics
Item 26: Don’t use raw types
主要讲了泛型的原始类型(没有任何类型参数的泛型类或接口),并引出了无界通配符类型。
原始类型(如List
)是不安全的,因为如果写入了不合适的类型,会直到运行时才会出错,而不是编译时。如果需要用到不关心具体类型的泛型,应当使用无界通配符类型(如List<?>
),注意不能向其中写入任何非空元素(主要用来读取)。注意List<Object>
这种类型不能满足要求,因为它只能接受Object类型(实际上想要的是List<? extends Object>
)。
不过,在获取类字面常量时,必须使用原始类型,如List.class
;另外instanceof
也推荐使用原始类型:
if (o instanceof List) { // Raw type
List<?> s = (List<?>) o; // Wildcard type
...
}
Item 27: Eliminate unchecked warnings
在泛型的使用中,常常在类型转换时触发unchecked warning,应当尽量排除,在确定代码安全并且排除不了的情况下,可以在变量声明行或方法/类之前添加@SuppressWarnings("unchecked")
,但要注意注释并尽可能缩小作用范围。
Item 28: Prefer lists to arrays
数组和泛型类型相比一个区别是数组是协变(covariant)的,而泛型类型是不变(invariant)的,即Integer[]
是Number[]
的子类,但List<Integer>
并不是List<Number>
的子类。这会导致出现类型不兼容问题时,泛型类型会在编译时就能找到错误,而数组会在运行时才会出现。
另一个区别是泛型存在类型擦除,其运行时并不知道元素的具体类型,而数组不同。泛型类型数组(new List<E>[], new List<String>[], new E[]
)是不被允许创建的,如果需要混合二者请把数组换成泛型类型。
Item 29: Favor generic types
主要讲了一下泛型类型的使用,如何用它代替Object类型,使得程序更加安全。
Item 30: Favor generic methods
主要讲了一下泛型方法的使用,方式如下:
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {...}
还介绍了递归类型边界:
public static <E extends Comparable<E>> E max(Collection<E> c);
这样参数类型可以限定接受实现了Comparable接口的对象。
Item 31: Use bounded wildcards to increase API flexibility
首先讨论了有界通配符类型的使用。PECS(producer-extends, consumer-super)原则即如果参数是生产者则用extends,消费者则用super,从而实现兼容。例如:
public void pushAll(Iterable<? extends E> src) {...}
public void popAll(Collection<? super E> dst) {...}
之前的max方法可以写成更为复杂的方式:
public static <T extends Comparable<? super T>> T max(List<? extends T> list)
这样的话,即使元素类型本身没有实现Comparable接口,只要其父类实现了也可使用;同时,我们可以传入T的子类构成的列表,同时返回类型T的元素。注意,不能返回通配符类型,因为返回的类型是不确定的,是不合理的实现。
最后,无界通配符(List<?>)和泛型类型(List<E>)在许多方法声明中都可以使用,前者更为简单,不过如果要在方法里使用或者返回类型,就需要用泛型类型了。
Item 32: Combine generics and varargs judiciously
本节主要讲了泛型与可变参数列表不能很好地配合使用,如果要用,在确保不出现堆污染的情况下对方法注释@SafeVarargs
。
Item 33: Consider typesafe heterogeneous containers
本节讲了通过将容器的key参数化,实现可以盛放多种类型的容器:Map<Class<?>, Object>
,其put方法为
public <T> void putFavorite(Class<T> type, T instance){
favorites.put(Objects.requireNonNull(type), instance);
}
这里的type为类字面量(如String.class
)。
get方法为
public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
另外还提到了一些改进,比如put时通过type.cast(instance)
防止类型不匹配等。
Chapter 6. Enums and Annotations
Item 34: Use enums instead of int constants
讲了枚举类型的用法和优势。枚举类型的复杂用法举例:
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);
}
Item 35: Use instance fields instead of ordinals
要将枚举元素与数字联系起来的时候,避免直接使用自带的ordinal
方法,而是手动用实例字段将其联系,这样有更好的可维护性:
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; }
}
Item 36: Use EnumSet instead of bit fields
介绍了EnumSet的使用,它适合作为枚举类型的集合,使用位向量存储集合中的元素:
// EnumSet - a modern replacement for bit fields
public class Text {
public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }
// Any Set could be passed in, but EnumSet is clearly best
public void applyStyles(Set<Style> styles) { ... }
}
Item 37: Use EnumMap instead of ordinal indexing
介绍了EnumMap的使用,它使用枚举类型作为Key,其性能优于常规的Map。
// Using an EnumMap to associate data with an enum
Map<Plant.LifeCycle, Set<Plant>> plantsByLifeCycle = new EnumMap<>(Plant.LifeCycle.class);
for (Plant.LifeCycle lc : Plant.LifeCycle.values())
plantsByLifeCycle.put(lc, new HashSet<>());
for (Plant p : garden)
plantsByLifeCycle.get(p.lifeCycle).add(p);
System.out.println(plantsByLifeCycle);
// Using a stream and an EnumMap to associate data with an enum
System.out.println(Arrays.stream(garden).collect(groupingBy(p -> p.lifeCycle,() -> new EnumMap<>(LifeCycle.class), toSet())));
Item 38: Emulate extensible enums with interfaces
枚举本身不能互相继承,但是可以通过继承接口实现可扩展的枚举。
Item 39: Prefer annotations to naming patterns
介绍了Java注解的使用。定义一个带参数的注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) // anotate a method
public @interface ExceptionTest {
Class<? extends Throwable> value();
}
注意参数后面要带括号(实际上是方法的形式)。
判断方法是否带这个注释:
m.isAnnotationPresent(ExceptionTest.class)
获取方法带的该注释的参数:
Class<? extends Throwable> excType = m.getAnnotation(ExceptionTest.class).value();
如果需要添加多个同类型的参数,定义时换成数组即可;利用注释@Repeatable
可以实现更简洁的使用。
Item 40: Consistently use the Override annotation
当出现方法重写时记得加override注释,除非是重写抽象方法。
Item 41: Use marker interfaces to define types
标记接口(如Serializable)可以标记类和接口的类型,与注释相比,它在编译时就可以进行检查,如果类中有方法必须要求类属于某个类型才能运行,那么推荐使用标记接口。
Item 42: Prefer lambdas to anonymous classes
lambda表达式用来创建小的函数对象,一般不需要指定参数和返回值类型(除非编译不通过),不宜太长(不超过三行),不要序列化。必须使用匿名类而不是lambda的情况:创建抽象类/多个抽象方法的接口的实例,或者从内部访问函数对象。
Item 43: Prefer method references to lambdas
方法引用多数情况下比lambda还要简洁。方法引用的类型有:
Method Ref Type | Example | Lambda Equivalent |
---|---|---|
Static | Integer::parseInt | str -> Integer.parseInt(str) |
Bound | Instant.now()::isAfter | Instant then = Instant.now(); t -> then.isAfter(t) |
Unbound | String::toLowerCase | str -> str.toLowerCase() |
Class Constructor | TreeMap<K,V>::new | () -> new TreeMap<K,V> |
Array Constructor | int[]::new | len -> new int[len] |
Item 44: Favor the use of standard functional interfaces
函数式接口是指只包含一个抽象方法的接口,需要用@FunctionalInterface
注释。使用时可以将他简单理解为函数对象的类型,从而可以将函数对象(如利用lambda表达式/方法引用创建)作为参数传递。java.util.Function
提供的函数式接口应当被优先考虑使用,有下面几类:
Interface | Function Signature | Example |
---|---|---|
UnaryOperator | T apply(T t) | String::toLowerCase |
BinaryOperator | T apply(T t1, T t2) | BigInteger::add |
Predicate | boolean test(T t) | Collection::isEmpty |
Function<T,R> | R apply(T t) | Arrays::asList |
Supplier | T get() | Instant::now |
Consumer | void accept(T t) | System.out::println |
Item 45: Use streams judiciously
Stream API的注意事项:为了提高可读性,不要过度使用stream,lambda参数需要仔细命名,适当使用helper方法;避免使用Stream处理char值;和普通迭代处理各有千秋,在合适的时候选择即可。
Item 46: Prefer side-effect-free functions in streams
forEach是一个终端操作,并不用于流计算;正确使用collectors,常见的有toList, toSet, toMap,
groupingBy, joining.
Item 47: Prefer Collection to Stream as a return type
当编写一个返回序列的方法时,如果内存足够就返回Collection;否则根据情况返回Stream或iterator.
Item 48: Use caution when making streams parallel
在Stream中添加parallel()方法有时可以提高性能,并主要用于这些:ArrayList, HashMap, HashSet, ConcurrentHashMap instances; arrays; int ranges; and long ranges. 而像Stream.iterate
就不适合使用,有时还可能导致错误的结果。
Chapter 8. Methods
Item 49: Check parameters for validity
方法中需要检查参数的有效性。对于public/protected方法,通过抛出异常的方式实现(并使用@throws标注),如Objects.requireNonNull
用来判空;而对于private方法,使用assert
实现,不过需要在启动时加上参数-ea
。
有时提前检查代价高并且在计算时也会执行检查,就没必要进行提前检查了,如在执行Collections.sort(List)
之前检查每一个元素非空。
最后,在设计方法时,还是应当避免对参数进行过多的限制。
Item 50: Make defensive copies when needed
当类中有可变的组件时,传入传出时最好防御性地传一个副本。
Item 51: Design method signatures carefully
API设计注意事项:
- 选择清晰、一致的方法名
- 不要过于追求提供便利化方法
- 避免长参数列表,如果有的话解决方法有:拆解为多个方法、创建辅助类、利用创造器模式创建对象
- 参数类型优先使用接口类型而不是类
- 优先选用枚举类型而不是布尔类型的参数
Item 52: Use overloading judiciously
- 重载发生在编译时,重写发生在运行时
- 避免重载同样参数数量的函数,否则将会引起混淆,比如list.remove(i)根据i是Integer还是int就会有不同的行为
- 不要在同一个参数位置上重载方法,使其接受不同的函数式接口
Item 53: Use varargs judiciously
恰当使用可变参数能够处理不同数量的参数,在前面要罗列好必须的参数,最后要注意其带来的性能影响。
Item 54: Return empty collections or arrays, not nulls
在需要返回空数组/空集合的时候不要返回null。这里还给出一个高性能的返回数组的方式:
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
public Cheese[] getCheeses() {
return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY); // cheesesInStock不为空时因为EMPTY_CHEESE_ARRAY空间不足总会创建新数组,反之直接返回静态变量
}
Item 55: Return optionals judiciously
在不确定是否要返回一个值,并且需要调用方处理的情况下,考虑使用optional:Optional.empty()
/Optional.of(result)
. 调用方处理Optional值可以选择给他一个默认值:optionalResult.orElse("Default!")
. Optional本身是有性能损失的,对于基本类型有性能更优的包装:OptionalInt, OptionalLong, OptionalDouble
. 不要在集合元素、键、值等场景使用optional。
Item 56: Write doc comments for all exposed API elements
主要讲了一些javadoc的书写规范。
Chapter 9. General Programming
Item 57: Minimize the scope of local variables
为了缩小局部变量的作用域,首次使用时再声明,并且尽量初始化;优先选择for循环而不是while;保持方法小而精。
Item 58: Prefer for-each loops to traditional for loops
遍历集合和数组时优先选择for-each形式的方式,除非要对其进行改动或者需要用到下标。
Item 59: Know and use the libraries
不要重复造轮子,尽量使用库方法。
Item 60: Avoid float and double if exact answers are required
在需要进行高精度数值计算的场合,使用int/long/BigDecimal而不是float/double.
Item 61: Prefer primitive types to boxed primitives
优先使用基本类型而不是装箱类型。装箱类型的陷阱包括:使用==比较时即使是同样大小的数也会导致不相等的结果(除非范围在-128到127且使用自动装箱的方式定义);与基本类型比较时可能抛出NullPointerException;与基本类型运算时多次装箱开箱造成大量性能消耗。使用装箱类型的情景有:集合元素,参数化类型和方法的类型参数,调用反射方法。
Item 62: Avoid strings where other types are more appropriate
避免过度使用String。本节列出了String替代其他值类型、枚举类型、聚合类型、权限功能的不足之处。
Item 63: Beware the performance of string concatenation
避免字符串直接用“+”连接,而是转而使用StringBuilder类。
Item 64: Refer to objects by their interfaces
如果存在合适的接口类型,则参数、返回值、变量和字段都应当使用接口类型声明;否则使用保证功能前提下最低级的类类型声明。这样会使程序改动更加灵活。
Item 65: Prefer interfaces to reflection
反射虽然功能强大,但是也有很多缺陷,如丧失了编译时类型检查的优势、代码更为笨拙冗长、性能损失。推荐只使用反射来实例化对象,然后通过在编译时已知的接口或超类来访问对象。
Item 66: Use native methods judiciously
谨慎地使用本地方法。本地方法的性能优势并不大,并存在难以debug、安全性和可移植性差、内存占用等缺点。
Item 67: Optimize judiciously
首先优先考虑写好程序,性能就自然不会差。也要避免限制性能的设计(尤其是API、协议、数据格式等),同时考虑API设计的性能后果。如果进行了性能优化,那么测试一下实际的性能变化。
Item 68: Adhere to generally accepted naming conventions
Java标识符要遵守常见的命名规范:
Identifier Type | Examples |
---|---|
Package or module | org.junit.jupiter.api , com.google.common.collect |
Class or Interface | Stream , FutureTask , LinkedHashMap , HttpClient |
Method or Field | remove , groupingBy , getCrc |
Constant Field | MIN_VALUE , NEGATIVE_INFINITY |
Local Variable | i , denom , houseNum |
Type Parameter | T , E , K , V , X , R , U , V , T1 , T2 |
Chapter 10. Exceptions
Item 69: Use exceptions only for exceptional conditions
只在出现意料之外的地方使用异常,不能把它用来做常规的流程控制。API设计时应当使用状态获取方法或特殊返回值来帮助调用者进行流程控制。
Item 70: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors
Java中有三种可抛出的类型:
- checked exceptions: 用于程序可以恢复至正常时
- runtime exceptions: 用于程序需要停止运行时,多用于提示编程错误
- errors: 一般被JVM使用,不要手动实现
Item 71: Avoid unnecessary use of checked exceptions
不必要的checked exceptions使用会让API用起来很难受。可选择的替代有使用Optional、使用分支以及适时抛出runtime exceptions.
Item 72: Favor the use of standard exceptions
尽量使用标准异常,而不是直接使用或者实现Exception/RuntimeException/Throwable/Error. 常见的异常有:
Exception | Occasion for Use |
---|---|
IllegalArgumentException | Non-null parameter value is inappropriate |
IllegalStateException | Object state is inappropriate for method invocation |
NullPointerException | Parameter value is null where prohibited |
IndexOutOfBoundsException | Index parameter value is out of range |
ConcurrentModificationException | Concurrent modification of an object has been detected where it is prohibited |
UnsupportedOperationException | Object does not support method |
Item 73: Throw exceptions appropriate to the abstraction
对于底层抛出的异常,上层应该尽可能地处理,如果处理不了应该抛出适合该层级的异常(异常翻译)。
Item 74: Document all exceptions thrown by each method
在注释文档中使用@throws
标注所有可能抛出的异常;对于checked exceptions还要在throw子句处标注。
Item 75: Include failure-capture information in detail messages
异常的细节说明应包含导致异常的所有参数和字段的值,但也不需要带有密码或者其他的冗余信息。为了确保异常对象始终包含足够的上下文信息,建议在异常类的构造函数中显式要求传入必要的失败捕获信息。
Item 76: Strive for failure atomicity
一次失败的方法调用应当不影响到对象状态,为了实现这点,可能的方法有:设计不可变对象;在操作之前检查参数有效性;对计算排序从而让失败在修改对象之前发生;操作对象副本;编写故障恢复代码。
Item 77: Don’t ignore exceptions
不要创建空catch块,至少要将异常变量命名为ignored并添加注释。
Chapter 11. Concurrency
Item 78: Synchronize access to shared mutable data
同步不仅用来阻止当前进程的变量被其他进程修改,也用于线程之间的交流。对于同一个变量的读和写必须都采用同步的方式,即使变量本身是原子的,这可以通过synchronized块或者volatile修饰的方式完成。volatile使用时还需要和原子操作结合,比如int的自增(++)就不是原子操作,可以将其换为AtomicLong然后调用getAndIncrement()方法。避免发生错误的一个方法是把可变数据限制在一个单独线程内。
Item 79: Avoid excessive synchronization
不要在同步代码块或方法中暴露控制权给调用方(如可重写的方法、函数对象等);尽量减少同步代码区域内的工作量;谨慎使用同步,一种设计选择是把同步任务交给调用方,另一种是自己在内部实现(如ArrayList和CopyOnWriteArrayList的区别)。
Item 80: Prefer executors, tasks, and streams to threads
介绍了Java中的线程池。
Item 81: Prefer concurrency utilities to wait and notify
现在的Java版本已经很少需要使用wait and notify或者synchronizedMap这种同步集合,更推荐直接使用java.util.concurrent中的集合等类,能够提高性能。顺便给了一个小tip:测量时间间隔的时候,推荐使用System.nanoTime,而不是System.currentTimeMillis.
Item 82: Document thread safety
类的线程安全声明需要标注级别:
Name | Description | Example Classes |
---|---|---|
Immutable | Instances are immutable and do not require any external synchronization. | String, Long, BigInteger |
Unconditionally thread-safe | Instances are mutable but have enough internal synchronization to be safely used concurrently without external synchronization. | AtomicLong, ConcurrentHashMap |
Conditionally thread-safe | Some methods of the class require external synchronization to ensure safe concurrent use. | Collections.synchronized wrappers |
Not thread-safe | Instances are mutable, and external synchronization is required to ensure thread safety. | ArrayList, HashMap |
Thread-hostile | Even if every method call is externally synchronized, it cannot be safely used concurrently, often due to unsynchronized static data. | generateSerialNumber (if unpatched) |