EFFECTIVE JAVA读书笔记

本贴最后更新于 3068 天前,其中的信息可能已经事过景迁

第一章 引言

语言与模式之类的书在大学时才看过,而且是.NET版本的,现在给JAVA也补上一课。个人笔记,无太大参考意义。

第二章 创建和销毁对象

第一条 考虑使用静态工厂方法替代构造器

优点:

  • 静态工厂方法能提供名称,方便使用者使用
  • 静态工厂方法能够不必每次调用都创建一个新实例
  • 可以返回原返回类型的任意子类型
    • 对外只提供接口,内部实现的类可变,方便以后替换升级
    • 只提供了一个对外接口,屏蔽内部实现类,减轻了使用者的认知负担
    • 编写静态工厂类的时候,实现可以不提供,由外部人员实现并注册到静态工厂类当中。方便 接口使用者 使用统一的接口形式 而无需关注具体实现。如JDBC API的实现
  • 使得代码更加简洁易读

缺点:

  • 静态工厂方法如果不含有共有的或者受保护的构造器,那么它就不能被子类化
  • 静态工厂方法本质与其他静态方法没有语法上的区别,只能人为地识别工厂方法

总结:在创建较为复杂的工具类的时候,要优先考虑使用静态工厂方法提供实例,减少 使用者学习负担,基于父类接口的契约随时可替换子类实现。

第二条 遇到多个构造器参数时要考虑使用Builder模式

Builder形式如下:

public class Example{
    private Integer a;//必填
    private Integer b;//必填
    private Integer c;//可选
    private Integer d;//可选
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">static</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">class</span> Builder{
    <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">private</span> Integer a;<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">//必填</span>
    <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">private</span> Integer b;<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">//必填</span>
    <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">private</span> Integer c;<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">//可选</span>
    <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">private</span> Integer d;<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">//可选</span>
    
    <span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">Builder</span><span class="hljs-params" style="box-sizing: border-box;">(Integer a,Integer b)</span></span>{
        <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>.a = a;
        <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>.b = b;
    }
    
    <span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> Builder <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">c</span><span class="hljs-params" style="box-sizing: border-box;">(Integer c)</span></span>{
        <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>.c = c;
        <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>;
    }
    
    <span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> Builder <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">d</span><span class="hljs-params" style="box-sizing: border-box;">(Integer d)</span></span>{
        <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>.d = d;
        <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>;
    }
    
    <span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">build</span><span class="hljs-params" style="box-sizing: border-box;">()</span></span>{
        
    }
    
}


<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">private</span> <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">Example</span><span class="hljs-params" style="box-sizing: border-box;">(Builder builder)</span></span>{
    <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>.a = builder.a;
    <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>.b = builder.b;
    <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>.c = builder.c;
    <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>.d = builder.d;
}

}

useage:
Example.Builder(1,2).d(3).build();

以上为Builder模式。

优点:

  • 若某个类有很多的参数的话,构造器模式能够提供一个清晰可读的代码。(使用 很多参数的类构造器时,经常出现放错参数的位置的情况,核对参数的位置十分痛苦)
  • 使用这种方法创建出来的对象可以保证对象的完整性*(使用类似JAVABEAN的形式,逐个设置属性值会导致对象还没正确初始化就已经被外部可用了,builder模式还可以在build的时候检查对象属性的完整性)*
  • 设置好的Builder生成了一个很好的抽象工厂,可以生成多个所需的对象。

缺点:

  • 创建对象需要先创建Builder对象,增加了消耗
  • 增加了码代码的工作量,最好必要时才用

总结: 当有很多初始化参数且要求保证对象完整性的时候就用这个吧。

第三条 Singleton的三种实现形式

Singleton指仅仅被实例化一次的类,通常用来代表那些本质上唯一的系统组件,如文件系统。使类变成Singleton会使得客户端很难进行测试,因为singleton难以被MOCK。

第一种实现形式:直接使用静态字段 第二种实现形式:使用静态方法getInstance() 第三种实现形式:使用枚举类定义,如下

public enum Singleton{
    INSTANCE;
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">private</span> <span class="hljs-constant" style="box-sizing: border-box;">Singleton</span>(){
    <span class="hljs-regexp" style="box-sizing: border-box; color: #009926;">//init</span>...
}

}

第二种形式的优势是,提供了灵活性,当我们想改变Singleton的唯一性的时候(如变成线程唯一什么的)可以简单的进行修改。

第三种的形式与共有域方法相似,但是更简洁,且无偿的提供了序列化机制,绝对防止多次实例化。(第一种,第二种形式可以通过反射等手段新建一个实例。第一二中形式Singleton需要序列化时,还要做额外处理,反序列化后才能保证唯一性)

总结:单元素的枚举值是实现Singleton的最佳形式。

第四条 为类增加 私有构造器 强调类不可实例化

某些工具类并不需要实例化,实例化也没有意义,那么可以给它家一个私有的构造器。如java.util.Collections

第五条 避免创建不必要的对象

没什么好说的,在对象的职责单一明确的情况下,没必要重复创建相同的对象来进行相同的工作。

创建小对象的代价是很小的,JVM很快就会把小对象的内存给回收掉,若创建小对象能提高程序可读性,那么久创建一些小对象。

但若创建对象的代价十分高昂,如数据库连接,那么就有必要把这些对象缓存起来,重复使用了

第六条 避免内存泄漏

容易产生内存泄漏的情况:

  • 自己管理内存。如自己维护一个存储池,存储池的内容有没有效,是通过类内部定义的一些数据来判断识别的。
  • 使用自定义的缓存,没有定义清理方法。
    • WeakHashMap,当WeakHashMap外再无对缓存对象的引用,那么对应对象就会被移除
    • LinkedHashMap有一个removeEldestEntry方法,可以移除最老的一个对象
  • 监听器和回调。客户端注册了回调,但没有显式取消。

第七条 避免使用终结方法

终结方法 执行时间不可预测,而且有很多坑,一般情况下不必使用终结方法。

若存在外部资源需要释放,那么可以提供一个显式关闭的接口,供使用者调用。

但调用者有可能忘记调用这个资源释放的接口,那么对应的对象编写者可以在终结方法中写上释放资源的代码。毕竟最终放了好过一直没放。

但引入终结方法的对象JVM回收的执行效率会大大降低。需要认真考虑这个问题。

并且如果子类复写了父类的终结方法,那么除非 子类显式调用父类终结器,否则 父类终结器方法并不会执行。

当普通对象需要传给本地方法执行时,普通对象会在本地方法中变成一个本地对等体。这个本地对等体不会被JVM回收机制回收掉,因此需要给定终结方法。(本段话不太确定,可能有误)

第三章 对于所有对象都通用的方法

第八条 Object.equals

默认的Object.equals适用的常见场景:

  • 每个实例本质都是唯一的(不同JAVA实例不相等)
  • 不关心是否逻辑相等(不关心不同实例是否指代同一个事物)

需要复写equals的大多是值类,值才是代表他们是否相等的依据。复写equals需要遵循以下约定:

  • 自反性(若没有,则Set.contains会判断失败)
  • 对称性(若没有,数组/SET contains判断可能失败)
  • 传递性
  • 一致性(多次调用equals都返回相同的结果)
  • x非null,那么x.equals(null)必须返回false

结论:尽量保持equals语义简单,尽量不要引入继承比较等复杂语义场景。不要将equals的入参从 Object替换成其他的类型,会大大提高复杂度。

第九条 覆盖equals时总要覆盖hashCode

不覆盖的话,会影响哈希算法的使用。哈希算法当遇到碰撞时,需要用到equals

复写hashCode的约定:

  • 应用程序执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么这个对象的hashCode方法必须如一的返回同一个整数。
  • 两个对象相等,那么hashCode必须相等。
  • 两个对象equals不等,不一定要返回不等的hashCode,但返回不同的hashCode能减少碰撞,提高效率

第十条 始终要覆盖toString

规范建议把对象的所有内容打印出来,方便阅读编程调试

第十一条 谨慎的覆盖clone

没看太懂,不常用,多坑。结论:不复写。

第十二条 考虑实现Comparable接口

实现Comparable接口可以简单的利用很多集合工具类,如 Arrays.sort,TreeSet

实现的约定:

  • x.compareTo(y) 与 y.compareTo(x)的 正负符号相同
  • compareTo具有传递性
  • 建议 x.compareTo(y) == 0,那么x.equals(y)

第四章 类和接口

第十三条 使类和成员的可访问性最小化

没什么好说的,就这样

第十四条 在共有类中 使用访问方法 而非公有域

同上,公有域无法修改实现,不够灵活,但在包内部或类内部使用直接访问字段也没啥坏处

第十五条 不可变对象

好处:

  • 线程安全,可以方便地共享
  • 更方便的用到MAP,SET中
  • 容易使用,坑少

缺点:

  • 性能消耗大

一个类应该优先考虑实现成不可变类

实现不可变对象的五条原则:

  • 不要提供任何会修改对象状态的方法
  • 保证类不会被扩展
  • 使所有的域都是final的
  • 使所有的域都是私有的
  • 确保外部不能拿到可变组件的引用

若某个计算过程需要对不可变对象进行大量变更,可提供一个不可变对象的协助类,如 String 对应的 StringBuilder

第十六条 复合优先于继承

若父类子类不是同一个人实现,不在同一个包时,尽量避免继承的理由:

  • 如果我们不知道父类实现的细节,继承类复写的方法有可能无法达到预期的效果
  • 父类实现的细节有可能随着版本的不同而不同,子类扩展有可能因此失效

结论:父类实现细节怎么变,子类都无法预测与干预,因此继承不可控的父类,并OVERRIDE或者扩展方法,干预父类的细节是很危险的,继承这做法违反了封装原则。

复合是解决上述问题的一个好办法,因为复合模式依赖的是接口,而非实现细节。

不用扩展现有的类,而是在新的类中增加一个私有域,它引用现有类的一个实例,这种设计叫复合,因为原有的类成为新类的一个组件。 新类的每个实例方法都可以调用现有类实例中的方法,并返回他的结果。 新类可以在调用现有方法的前后加入自己的一些额外实现代码。

第十七条 要么为继承而设计,并提供文档说明,要么就禁止继承

若一个类是为继承而设计的,那么在父类中的每个Public或者Protect方法必须包含调用了 哪些可覆盖方法 的自用性说明(即说明类内部的实现中是怎样使用这个被覆盖的方法的)。

一旦上述自用性说明发布出去之后,那么这个类以后的修改都必须遵守自用性说明中的契约

一个好的API文档应该描述的是 WHAT TO DO 而不是 HOW TO DO,但这种为继承而生的类的文档与这个说法相违背,但 也没啥好办法。

为继承而设计的类还需要遵守一个约束,那就是 构造方法 不要调用可被覆盖的类,否则可能会出现意想不到的情况,因为 子类复写的方法 将会在子类的初始化任务还没有完成的时候就被调用了。

第十八条 接口优先于抽象类

接口优点

  • 有共同祖先X的的一些类,若只需少部分扩展某些新功能,那么可以通过新增实现的接口来扩展某个功能,而无需担心影响其他的类
  • 接口是实现MIXIN(指代为为类加上一些其他额外属性)的理想选择(貌似跟上一条没啥区别)
  • 接口允许我们构造非层次结构的类型框架。如 某个人 既是 音乐家 ,又是 作家,那么这个人MIXIN 音乐家 及 作家 的接口即可。
  • 有了接口,可以更为优雅的实现Wrapper class模式(所以Spring代理最优是接口?如果不是的话,代理的对象会侵入实现细节么?待确认

抽象类有一个好处,就是可以提供一些基础骨架的实现,方便使用者快速开发。对于接口,接口也可以提供一些实现了骨架的类。这样,开发者就可以通过 继承骨架类 快速实现对应的接口。如果不能使用extends,那么,可以使用wrapper class模式,结合骨架类 给某个现有类快速MIXIN对应的接口。

骨架类按照惯例命名方式为 "Abstract" + InterfaceName ,如 AbstractSet,AbstractCollection等。

接口缺点

  • 一旦接口被发布且广泛使用后无法新增方法

什么时候选择继承? 演变的容易性(新增功能之类的)比灵活性和功能更为重要的时候。

第十九条 接口只用于表示可进行某种操作,其他任何类型的使用都是不合理的

一个接口里只包含一些常量,目的是为了实现该接口的类可以使用这些常量,这种做法是不正确的。 若需要导出常量,可以另外定义一个类,并 Static Import

第二十条 类层次优先于标签类

没说啥东西,略过

第二十一条 使用 函数对象 实现策略模式

可以使用 函数对象 来代替 函数指针的作用,实现策略模式。如用到 Comparator这接口的排序等等

第二十二条 优先考虑使用静态成员类

讲了 静态内部类,非静态内部类,匿名类,局部类运用的一些场景,运用场景都比较明确,不作记录

第五章 泛型

第二十三条 不要在新代码中使用原生态类型(要使用泛型)

第二十四条 消除非受检警告

当编译器有 非受检警告 时,需要通过泛型消除。如果无法消除,确认代码安全后,可用SuppressWSarning("unchecked")来禁止告警,并说明为什么。

第二十五 列表优于数组(可避免ClassCastException)

数组是协变的(convariant),指代 如果 Sub是 Super的子类,那么Sub[] 也是 Super[]的子类,这意味着下述的赋值在编译时是合法的

String[] sub = new String[]{""};
Object[] super = new Object[0];
super = sub;//能成功赋值
//以下赋值,编译时无错误,因为Integer确实是Object的子类,能放进去,
//但是运行时会报错,因为 数组会对放进来的类型进行检查
subper[0] = 1;

泛型列表的设计就是为了避免这种 运行时类型转换错误的。List<Sub> List<Super>之间并没有任何继承关系,是完全不同的两个类型。

JAVA中还不允许使用 泛型数组 如 List<Sub>[] 是被不能通过编译的。如果能通过编译,那么以下场景还是会出现类型转换异常的错误

List<String>[] strLists = new List<String>[1];//编译报错,但我们假设能通过
List<Integer> intList = Arrays.asArray(1);
Object[] objList = strLists;// Object是List<String>的父类,因此,这个赋值可以成功
Object[0] = intList;//能赋值成功,因为运行时是抹除了泛型信息的,运行时List<String>和List<Integer>是同一个类型,因此数组赋值的类型检查不会报错
String s = strLists[0].get(0);//会有运行时错误,Integer无法强转为String

由于上述原因,因此,不允许泛型数组的存在。

第二十六 实现优先考虑泛型类

第二十七 实现优先考虑泛型方法

第二十八 利用有限制通配符来提升API的灵活性

PECS:producer extends ,customer super. 如下例子:

public class Collections { 
    public static <T> void copy(List<? super T> dest,     List<? extends T> src) 
    {
        for (int i=0; i<src.size(); i++) 
        dest.set(i,src.get(i)); 
    } 
}

extends有如下特性

List<? extends Number> f = new                 
ArrayList<Integer>();//不报错
f.add(1);//编译报错
Number n = f.get(0);//不报错

super有如下特性

List<? super Number> f = new ArrayList<Object>();//不报错
f.add(1);//不报错
Object n = f.get(0);//只能拿到Object

extends能推断出方法自身产生的对象的最精确的值(extends 的那个类),但不能接受对应的泛型入参,因为无法保证类型安全

super不能推断出返回对象的类型,但是能接受任何类型的入参

第二十九 若要使用异构容器,优先使用类型安全的异构容器

public class Favorites{
    private Map<Class<?>,Object> map = new HashMap<Class<?>,Object>();
    public <T> void put(Class<T> key,T value){
        map.put(key,key.cast(value));//cast用于加上类型检测
    }
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> &lt;T&gt; T <span class="hljs-built_in" style="box-sizing: border-box; color: #0086b3;">get</span>(Class&lt;T&gt; <span class="hljs-variable" style="box-sizing: border-box; color: teal;">key</span>){
    <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> <span class="hljs-variable" style="box-sizing: border-box; color: teal;">key</span>.cast(<span class="hljs-built_in" style="box-sizing: border-box; color: #0086b3;">map</span>.<span class="hljs-built_in" style="box-sizing: border-box; color: #0086b3;">get</span>(<span class="hljs-variable" style="box-sizing: border-box; color: teal;">key</span>));
}

}

第六章 枚举和注解

第30条 用enum代替int常量

  • 类型安全
  • 更高可读性
  • 枚举功能更强大,可附加功能
  • 可用枚举实现策略模式(每个枚举值都是一个类)

例子 public enum Operator{ PLUS("+"){ double apply(double x,double y){ return x + y; } }, MINUS("-"){double apply(double x,double y){ return x - y; } }

private String symbol;
    Operator(String symbol){
        this.symbol = symbol;
    }
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">abstract</span> apply(<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">double</span> x,<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">double</span> y);

<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> toString(){
    <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> symbol;
}

<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">private</span> Map&lt;<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">String</span>,Operator&gt; <span class="hljs-built_in" style="box-sizing: border-box; color: #0086b3;">map</span> = <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">HashMap</span>&lt;&gt;();
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">static</span>{
    <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">for</span>(Opeator op : values){
        <span class="hljs-built_in" style="box-sizing: border-box; color: #0086b3;">map</span>.put(op.toString(),op);
    }
}

<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">static</span> fromString(<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">String</span> name){
    <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> <span class="hljs-built_in" style="box-sizing: border-box; color: #0086b3;">map</span>.<span class="hljs-built_in" style="box-sizing: border-box; color: #0086b3;">get</span>(name);
}

}

第31条 用实力域代替序数

不使用ordinal,其会改变,实例域不会。

第32条 使用EnumSet代替位域

EnumSet.of(Sytle.BOLD,Style.ITALIC);

第33条 使用EnumMap代替序数索引

不使用ordinal,因其会改变,使用EnumMap代替。 反正不要使用ordinal就对了

第34条 用接口模拟可伸缩的枚举

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

public enum BasicOpeation implements Operation{
...
}

缺点,骨架代码要写两遍

第35条 注解优于命名模式

  • 注解是显式的,比较容易发现错误,命名模式里命名错了难以发现
  • 注解可以给某个元素附加一些特殊的信息,而命名模式难以做到,做到也不雅

第36条 坚持使用Override注解

第37条 用标记接口定义类型

要定义一个任何新方法都不会与之关联的类型,标记接口才是最好的选择,而非 标志注解

  • 标志接口定义的类型是由被标记类的实例实现的,有语言编译的检查,而标记注解则无,只能运行时检查

标志注解的优点

  • 可以一直给类加注解,增加信息,扩展性好,而接口则无法变更

第7章 方法

第38条 检查参数的有效性

尽早检查入参,能及时判断错误的来源

第39条 进行保护性复制

当调用者不可信或者防止意外时,有时需要对传入的参数进行复制并保存到复制的对象。传出对象时,也可以视情况返回一个复制的对象,以免外部通过传出的对象修改影响对象内部状态。 当然保护性复制是有消耗的,有必要才使用

第40条谨慎涉及方法签名

  • 方法名称:与包风格一致,使用大众认可的名称
  • 不要过于追求提供便利的方法,除非提供的方法特常用
  • 避免过长的参数列表,目标是4个,或者更少
    • 分解大方法,提供小方法
    • 创建辅助类保存参数分组
    • Builder模式
  • 参数优先使用接口而非类
  • boolean参数尽量使用枚举类型代替

第41条 慎用重载

重载决定使用哪个方法是编译时决定的,如下

public static ex(Integer i){
    ...
}

public static ex(Object i){
...
}

public static void main(String[] args){
Integer i = 1;
ex((Object)i);//调用的是第二个方法,而非第一个
}

这个表现跟Override不一样,Override决定调用哪个方法是在运行时,根据运行类型决定的。

由于上述原因,我们使用重载的时候要特别的小心,尤其是碰上 自动拆包装包,泛型 等情况的时候。

要提供类似的方法的时候,优先使用不同名称的方法,而不是重载。

若在构造器等场景,只能使用一个方法名,我们可以尝试使用静态工厂方法来替换掉重载的构造器

若一定要使用重载,也尽可能的避免使用相同参数个数的重载方法。

第42条 慎用可变参数

没看懂为什么,不管

第43条 返回令长度的数组或集合 而不是NULL

常识

第44条 为所有到处API元素编写文档注释

第8章 通用程序设计

第45条 将局部变量的作用于最小化

常识,最基本的方法就是在第一次使用的地方进行声明

第46条 for-each循环优先于传统的FOR循环

略,已习惯使用for-each循环

第47条 了解和使用类库

优先使用类库,不重复制作轮子OVER 至少要知道java.lang,java.util包里有什么内容,java.io也要了解下

第48条 如果需要精确的答案,请避免使用float和double

使用 long,int或者BigDecimal代替 虽然double的精度很高,但使用double进行计算,舍入并不总能得到正确的结果。 如:

public static void main(String[] args){
    double funds = 1.0;
    int itemBought = 0;
    for(double price = 0.1;funds >= price; price+= 0.1){
        funds -= price;
        itemBought++;
    }
}

运行的最后,会剩下 0.39999999999元,itemBought为3,与预期用完所有钱,买到4颗糖果的预期不符

第49条 基本类型优先于装箱基本类型

  • 性能
  • 装箱类型==操作有坑
  • 装箱类型自动拆箱可能出现nullPointerExceptiron

结论,平时用基本类型,不能用基本类型才用 装箱类型。

第50条 如果其他类型更合适,避免使用字符串

感觉差不多,略

第51条 当心字符串连接的性能

第52条 通过接口引用对象

已形成习惯:可替换,减少暴露,更易于修改

第53条 接口优先于反射机制

反射一般会比原生方法慢 2-10倍。因此尽量避免使用反射。除非要管理一些在编译时不存在的类型。 而且也尽量只使用反射来创建实例,获取元信息。操作方法的时候使用接口。

第54条 谨慎地使用本地方法

以下三种情形要使用本地代码

  • 访问特定于运行平台的机制的能力
  • 访问遗留代码库的能力
  • 提升性能

提升性能对现在的JVM来说意义不大。

不使用的原因, 类型不安全,影响移植,胶合代码难看

第55条 谨慎地进行优化

不要因为性能牺牲合理的结构。要努力编写好的程序而不是快的程序。 努力避免那些限制性能的设计决策,好的API设计会带来好的性能,如果开发完速度不够,再进行优化

可以用性能剖析器测试性能瓶颈

第56条 遵守普遍接收的命名惯例

命名惯例参考 The Java Language Specification

第9章 异常

第57条 只针对异常的情况才使用异常

  • 基于异常的写法难以让人理解
  • 基于异常捕获的形式的代码会比正常的写法慢

总之,正常的流程控制不应该包含异常

第58条 对可恢复的情况使用受检异常,对编程错误使用运行时异常

  • 如果期望调用者能够适当的恢复,那么使用受检的异常
  • 运行时异常用来表明编程错误,大多数运行时异常都表示 前提违例
  • ERROR基本都是保留给JVM使用的,因此自己不应该抛出ERROR

第59条 避免不必要的使用受检的异常

我对受检的异常非常痛恨...会大大加大工作量,使用受检异常应该只有在以下两个条件都存在时,才使用

  • 正确使用API依然可能出现异常
  • 出现了异常后,调用者能恢复处理这个异常

第60条 优先使用标准的异常

  • 与大家习惯用法一致
  • 便于学习
  • 可以继承某些异常,并增加一些异常信息

第61条 抛出与抽象相对应的异常

更高层的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解析的异常。 这个叫异常转译。异常转译通常会包括异常链,方便排查问题

第62条 每个方法抛出的异常都要有文档

....

第63条 在细节消息中包含能捕获失败的信息

一直这么做

第64条 努力使失败保持原子性

但是基于数据库的一致性倒是容易的。

第65 不要忽略异常

第10章 并发

第66条 同步访问共享的可变数据

常识,没有正确同步的数据,JVM并不保证何时可见,可能由于某种优化,某数据就一直看不见了。 最好的办法是 不共享的办法,或者 共享不可变变量的办法

第67条 避免过度同步

  • 同步块内不要调用外部方法
  • 同步块内尽量做少的工作
  • 同步会令CPU上市并行的机会,JVM丧失编译优化的机会
  • 一个可变的类要并发使用,应该使得这个类是线程安全的,同步内部同步会比外部锁定整个对象同步性能更好。
  • 但同步并不是必须的时候,如经常就一个线程使用,偶尔才多线程,那么,就应该由外部进行同步
  • 在多核时代,设计一个可变类的时候要思考这个类是否应该自己实现同步,这比避免过度同步更重要

第68条 executor和task优先于Thread

略,常识

第69条 并发工具优先于wait和notify

  • ConcurrentHashMap(并发集合相关)
  • CoundownLatch(同步类相关)
  • 若要使用wait那么要加上循环条件判断
  • 如果要用notify最好用notifyALL,牺牲一点性能避免错误发生

第70条 线程安全性的文档化

线程安全的可能的几个级别

  • 不可变的(字符串之类)
  • 无条件线程安全(内部已实现同步)
  • 有条件的线程安全(部分方法需要外部进行同步)
  • 非线程安全(需要外部同步)
  • 线程对立(不能多线程执行,即使外部执行了同步。通常是由于异步修改了静态数据导致)

线程安全的描述需要写在文档注释中,否则使用者将无法正确使用

第71条 慎用延时初始化

因为有坑,且在实例域延迟初始化时,会降低效率

若一定要做这个,那么有以下方法

静态域的延迟初始化使用匿名内部类模式

private static class LazyInit{
    static final lazyField = new LazyField();
}

public static LazyField getLazyField(){
return LazyInit.lazyField;
}

实力域的延迟初始化方式——双重检查

private volatile LazyField lazyField;//使用volatile的原因见代码后的解析

public LazyField getLazyField(){
LazyField lazyField = this.lazyField;//避免每次使用都访问 volatile 域,提高性能
if(lazyField == null){//第一次检查,不带锁,避免每次检查都加锁,提升效率
synchronized(this){
if(this.lazyField == null){//第二重检查,带锁,保证不重复初始化的关键
this.lazyField = lazyField =new LazyField();
}
}
}
return lazyField;
}

lazyField声明为volatile是为了禁止lazyField = new LazyField()方法的重排序。若不声明为volatile,这个赋值操作可能就变成了以下伪代码描述的场景

memory = allocat();
lazyField = memory;
initInstance(memory);

这种场景的话,lazyField可能还没被初始化,但lazyField已经不为NULL了

当实例域可以重复初始化时,可以编程单重检查

private volatile LazyField lazyField;

public LazyField getLazyField(){
LazyField lazyField = this.lazyField;//避免每次使用都访问 volatile 域,提高性能
if(lazyField == null){
synchronized(this){
this.lazyField = lazyField =new LazyField();
}
}
return lazyField;
}

volatile总是不能去掉的

否则会出现难以预测的场景。

第72条 不要依赖于线程调度器

因为这个是与平台相关的,依赖于这个东西,将无法重现对应的结果

第73条 避免使用线程组

没用过。说过时了。那就不用了

第11章 序列化

第74条 谨慎地实现Serializable接口

  • 一旦一个类被发布,就大大降低了“改变这个类的实现”的灵活性,因为类中的内部实例域都会被公布出去
  • 增加了出现BUG和安全漏洞的可能性。反序列化机制是一个隐藏的构造器,使用这个构造器的时候很容易就会忘记保证原有对象中应有的约束
  • 增加了测试的负担,需要测试二进制的兼容性

表示实体活动的类,一般不应该实现Serializable 为了继承而设计的类,应尽可能少实现Serializable接口

第75条 考虑使用自定义的序列化形式

当对象的逻辑含义与物理表示接近的时候,可以考虑使用默认的序列化形式。

但逻辑含义与物理表示差别很大的时候,就不适合了。

如一个用链表表示的字符串对象。默认序列化会把链表的引用指向关系如实的保存起来,消耗比较大。导出的对象实现形式会被束缚在这具体的实现形式上无法变更,除非放弃之前序列化的对象的二进制数据。

第76条 保护性的编写readObject方法

避免有人恶意修改被范序列化的对象,破坏约束 常用做法是,readObject的时候检查约束,对于一些可变对象进行保护性复制

第77条 对于实例控制,美剧类型优先于readResolve

第78条 考虑用序列化代理替代序列化实例

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3187 引用 • 8213 回帖
  • 模式
    8 引用 • 45 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...