Effective Java 第三版读书笔记

157 阅读7分钟

要做的事情

用静态工厂方法代替构造器,如果要保证单例有效性也可以用静态工厂方法,这时候可以考虑用泛型的静态工厂。

构造器可选参数比较多的时候,最好用Builder模式(标准Builder代码),参考Lombok。

要优先使用基本类型而不是自动装箱基本类型,因为基本类型性能更高。

try-with-resource优先于try-finally,因为它带有释放资源的特性。

用Comparator接口代替覆盖compareTo方法,优雅

用data类的setter和getter,还是Lombok,你值得拥有。

可以使用通配符来提升泛型的灵活性。

泛型的容器更具安全性。

用实例域来代替枚举自带的序数,比如说使用如 APPLE(1)这样的枚举

用注解来代替命名模式(如@Test代替testXXX),这样更灵活。

覆盖方法时,一定要提前用@Override注解来发现错误。

标记时,优先使用标记注解接口。

lambda参数类型可以不存在,除非为了更清晰。只有推导不出来的时候才加上。

java.util.functon里面函数接口只要记住其中6个基础接口,其他可以推断(idea这样的IDE也能帮助你找到需要的)。 在这里插入图片描述

方法签名:名称要写好,开始不要为方法设计快捷方式,避免过长的参数列表(尽量4个以下),尤其是相同类型的参数并列。参数类型优先使用接口,如果可以的的话。优先使用枚举代替boolean参数。

方法有多个参数可以采用:

  1. 拆分方法,
  2. 用辅助类(参数类)
  3. Builder模式

返回零长度的数组或者集合,代替返回null。谨慎返回optinal,尤其当想要返回null的时候

返回Optional而不是T,应该是如果无法返回结果并且当没有返回结果时客户端必须执行特殊操作的时候,才返回Optional。用OptionalInt代替Optional有更低开销,还有long double等。

导出的API元素最好都写文档注释。对抛出的异常也应该有对应文档。线程安全性要文档化

用运行时异常来表名编程错误。努力使得失败保持原子性,也就是可恢复性。

executor,task,stream优先于线程,并发工具(如ConcurrentHashMap)优先于wait notify(其实现在基本不会用)

不做的事情

最好不要覆盖equals方法,clone方法。

有空就覆盖toString方法,使得程序输出更好看,不过现在编程时,log已经可以输出各种内容了。

在类中用数组增加和减少元素的时候注意(不做最好),如果一个栈先增长后收缩,从栈中弹出来的对象将不会被当做垃圾回收,因为栈会维持着对这些对象的过期应用。这叫“无意识的对象保持”。做法是将过期的对象应用设置成null。只要类是自己管理内存,程序员就应该警惕内存泄漏问题。(如类里面的数组增加和缩小等)

如果是类里面缓存引起的内存泄漏,可以通过类似WeakHashMap的方式来作为缓存。

公有静态final的常量,应该是不可变的对象或者基本类型值。

在现有的接口上面,最好别添加缺省方法,以免原先客户端已经写有的同名方法被失效了,其实可以看情况。

用接口来放全部的final常量不好,应该是用类。

静态内部类比非静态内部类好,而一个文件最好只有一个类

泛型类不要直接用原生类,如 ABC 直接用 ABC,IDEA也会提示加黄,因为这样泛型就起不到作用了。

@SupppressWarnings(“unchecked”)是最后才用,用的时候要写注释为什么用,泛型要好好写泛型。

泛型数组不好,用泛型列表List,因为数组是运行时安全,但没有了泛型的编译器安全。

泛型和可变参数相互不能良好作用,所以最好不要并用它们。一定要用,都要用@SafeVarrags注解。

慎用重载,因为会导致意图不清晰,尤其是具有相同参数数目的方法。慎用可变参数。

float和double不适合用于货币计算,和精确计算,要用BigDecimal

不要用异常终结循环,异常只用在真正需要的地方。异常不应该用于正常的控制流里面。

期待使用者能适当恢复的异常,使用受检异常,因为受检异常通常指明了恢复条件。而运行时异常和错误应该是无法恢复的。

优先使用标准的异常。

不要用空catch来忽略异常(除非加入注释),任何异常不该被忽略。

不用Thread.stop,Thread.yield。读写要一起同步,不然无法保护。将可变数据限制在单个线程里面。同步区域内做尽可能少的工作。

其他的序列化方法优于Java序列化,或者不做序列化,如json或protobuf。永远不要反序列化不被信任的数据。谨慎实现Serializable接口。

一些编程认知

依赖注入模式:就是构造器(资源类型)这样的类,在使用中可以注入不同的资源类型从而给出不同的资源实例。不过用的多会让项目凌乱,所以需要用依赖注入框架,如Guice和Spring

lambda用于代替匿名类,谨慎使用stream并行,只有局部性数据结构才能用并行提升效率,如ArrayList,HashMap,int[]等,而iterate或者limit就会让pipeline不并行。

假设类的客户端会尽其所能来破坏这个类的约束条件,因此你必须保护性地设计程序。比如说误解api。保护方法可以是重新复制可变参数。

可访问性最小化,可变性最小化

复合优于继承 —— 就是少用继承。

接口优于抽象类,因为接口可以从小到多,也可以多继承。如果有新的需要直接实现于一个新的接口。并且带有强类型安全。

函数接口@FunctionalInterface(写自己的函数接口必须用),单个抽象的方法的接口,可以让lambda来实现。因为lambda没有名称和文档,所以最好是自描述的,最好一行,最多三行,不然就考虑不要放lambda

java8新增java.util.function里面有很有现成的函数接口可用。通常使用标准的能满足就最好不自己创建新的。

lambda还可以缩写函数,Idea会提示。lambda名称最好精心设计,增加可读性

有时候用引用::的静态方法比lambda要清晰简洁,不过如果不短也不清晰,那还是用lambda。

stream可以做很多事情,但不是应该,用得好清晰,用的差混乱。用与不用,用多少,可以视乎stream改善了多少可读性,如循环内用单个stream而不是全部用stream代替循环。

stream和循环不同的地方在于stream过程中会改变原有值,所以要注意。

编写stream pipeline的本质是无副作用的函数对象。forEach只用于报告计算结果,而不是作为循环执行。而主要了解收集器,如toList,toSet,toMap,groupingBy和join。

将局部变量的作用域最小化,如第一次使用它的地方才声明,除非它是类似全局的条件。

for-each优于for,除非for-each用不了。

优先使用类库,而不是自己写,如数学函数等。经常学习新版本特性类库是值得的。

应该熟悉 java.lang java.util java.io 及其子包的内容,和Collections Stream,java.util.concurrent,还有类似Guava

基本类型由于装箱基本类型。除非放集合类型里面,或者可能null情况下。

如果有其他类型更适合,避免使用字符串来代表。如果大量字符串连接可以用StringBuilder(如循环里面),不过一般简单+起来就直接+吧。

尽量用接口类型来作为引用对象,如 Set a = new LinkedHashSet<>();

可以的话,用多写一个接口的方式来代替反射。

谨慎用本地方法,谨慎优化。写好的程序而不是快的程序。优化前后必须对性能做测量。