Effective JAVA 读书笔记
看第二遍,仔细做笔记
1. 用静态工厂方法代替构造器
优势:
- 静态工厂方法有具体名称
- 每次调用静态工厂方法时返回的是提前构造好的实例
- 可以返回原返回类型任何子类型的对象
- 创建参数化类型实例,代码较简洁
例如以下,通过静态工厂方法,确保获取KBServiceImpl的单例
public class KBServiceImpl extends KBService {
//(1) 构造函数为空,防止直接调用构造函数创建
private KBServiceImpl {
}
// (2)定义一个hodler 的内部类,专门定义KBServiceImpl 类型的static 的 INSTANCE成员
private static class KBServiceImplHolder {
private static final KBServiceImpl INSTANCE = new KBServiceImpl();
}
//(3) 定义getInstance()函数,通过此函数获取KBServiceImpl的static 对象
//确保外界可以直接调用 KBServiceImpl.getInstance() 而不用先new KBServiceImpl,所以必须声明static
public static KBServiceImpl getInstance() {
return KBServiceImplHolder.INSTANCE;
}
}
以下为泛型HashMap 的一种初始化方式:
public static <K, V> HashMap<K, V> newInstance() {
return new HashMap<K, V>();
}
Map<String, List<String>> m = HashMap.newInstance();
2.构造器参数较多时采用 build 模式
//PYJ: 这种构造方法在项目中还未见过
如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是一种不错的选择
构造参数较多时一般是采用JavaBeans 方式:
Data data = new Data();
data.setA(...);
data.setB(...);
data.setC(...);
builder 方式:
public class Data {
private final int A;
private final int B;
private final int C;
private final int E;
private final int F;
public static class Builder {
private final int A;
private final int B;
private int C = 0;
private int E = 0;
private int F = 0;
public Builder(int a, int b) {
this.A = a;
this.B = b;
}
public Builder setC(int c) {
C = c;
return this;
}
... ...
public Data build() {
return new Data(this);
}
public Data(Builder build) {
A = build.A;
... ...
}
}
}
最后Data data = new Data.build(1, 2).setC(3).setD(4).build();
其实也是直接调用build 这个内部静态类来实现的
3.私有构造器或者枚举类型强化Singleton属性
public class Data {
private static final Data INSTANCE = new Data();
private Data() {
... ...
}
public static getInstance() {return INSTANCE}
}
- 构造函数private 域
- 仅仅getInstance() public 域
- getInstance() 为静态方法,返回的是final 的静态成员变量 INSTANCE
INSTANCE作为静态成员仅仅会初始化一次,所以getInstance() 返回的一直是同一个对象;
4.通过构造器为private 域来确保此类无法被实例化
RT
5.避免创建不必要的对象
同时,还要尽量优先使用基本类型而不是装箱基本类型。
6.消除过期的对象引用
// TODO
7.避免使用终结方法
必须使用就调用 super.finalize
对所有对象通用的方法
8.覆盖equals 时需要注意点:
1.自反性:对象必须equals自己;
2.对称性: A 和 B 必须相互equals
3.传递性:A,B,C传递
4.一致性:equals的比较操作在对象中随用的信息没有被修改,则equals 的值不变;
注: 里氏替换原则----所有调用父类的地方,换成子类都悟异常。所以父类覆盖了equals 方法,子类就不再覆盖 equals 方法
- 使用 == 操作符直接检查“参数是否为这个对象的引用”
- 使用instanceof 操作符直接检查“参数是否为正确的类型”
- 把参数转换为正确的类型
- 覆盖了 equals 方法后一定要覆盖 hashCode 方法
9.覆盖equals时也要覆盖hashCode
注意: equals 的为用户判断相等的,但是 hashCode 是系统运行时判断的,比如HashMap中各成员的key值不能相等,系统就是通过key 的hashCode 来判断的
10.始终要覆盖toString方法
Object提供了toString的实现,但它返回的是**类名+@+散列码,**如果有转化为String的需求,应该覆盖toString方法
数据类型的类都覆盖 toString 方法
11.尽量避免覆盖clone 方法
12.Comparable 接口的问题
## 类 和 接口 ### 13. 类和成员的可访问性最小化
- 尽可能使每个类或者成员不被外界访问;
- 实例域(非 final 或者static)绝不能是公有的;
14.在共有类中使用访问方法而非公有域
非final 或 static 的成员变量都尽量设置为private 域;
将获取/修改 这些成员变量的方法设置为public 域来提供访问/修改
15.
16.复合优于继承
- 同一个包内继承是十分安全的(仅讨论实现继承,不考虑接口继承)
- 不同包,跨包继承是不安全的(仅讨论实现继承,不考虑接口继承)
以上,因为程序都是以包进行优化的,可能继承的包里的类进行了变化
17. 一个类要么为了继承,要么就禁止继承
- 为了继承的类:
-
构造器不能调用可被覆盖的方法;
-
发布前需要先写子类进行测试;
- 对于那些并非安全第进行子类化的类,要禁止子类化:
-
方法1:把此类声明为final
-
方法2:把构造器设置为private 的。(jave类设计的任何方法只要设置为private之后,就无法继承了)
18.接口优于抽象类
当类需要实现混合功能时,通过实现新的接口来实现
接口和抽象类本质区别: 抽象类允许某些方法的实现,但是接口不允许;(所以抽象类的继承有很大限制)
1.接口是定义mixin(混合类型)的理想选择:
一个类仅拥有一个功能,需要添加混合功能(非此类原设定功能)时,可以通过实现新的接口来增加功能;这样功能划分也清楚
2.接口允许我们构造非层次结构的类型框架:
与1相同,混合的接口层次,为了不违背类的单一职责原则
3.现有的类可以通过实现新的接口来进行 更新:
作为1 和2 的补充,不违背开闭原则,当类有新功能需要增加时,以 实现新接口为拓展,不是直接修改;
19.接口只用于定义类型
不应该出现: 实现接口就是为了到处其中定义的常量的情况,防止这个类及其子类被“常量污染”
所以一般接口中不定义常量,仅定义方法
20.类层次优于标签类
21.用函数对象表示策略
//TODO
22.优先考虑静态成员
1.镶嵌类
在一个类中定义另外一个类,就是镶嵌类;
2.内部类
镶嵌类分 静态成员类 ,非静态成员类,匿名类 和 局部类 , 其中 除了静态镶嵌类以外的镶嵌类就是内部类;
- 镶嵌类的成员类 可以访问外围类的所有成员,包括私有成员!
- 镶嵌类也可以访问 成员类的所有成员
泛型
23.不要在新代码中使用原生态类型
“List”的原生态类型就是“List” , 这条原则的意思就是尽量确定类似List 为那种类型的列表:
尽量定义:
public final List list = new ArrayList()
而不是:
public final List list = new ArrayList()
//1.使用原生态类型 , 不安全
static int numEqualsTest(Set s1, Set s2) {
... ...
for(Object o1 : s1) {
if(s2.contains(o1)) {
result ++;
}
}
}
//2.使用 无限制通配符 , 安全
static int numEqualsTest(Set<?> s1, Set<?> s2) {
... ...
for(Object o1 : s1) {
if(s2.contains(o1)) {
result ++;
}
}
}
24.消除非受检警告
25.列表优先于数组
- 数组是“协变”的,泛型是“不可变”的——泛型严厉控制了add 的类型,在编译时就会检查出问题;
- 数组是“具体化”的,在运行时才检查数组元素类型的约束,泛型编译时就检查了;
26. 优先使用泛型
// 非泛型
public class Stack {
private Object[] elements;
private int size = 0;
private static final DEFAULT_INITAL_CAPACITY = 16;
public stack() {
elements = new Object[DEFAULT_INITAL_CAPACITY];
}
public void push(Object e) {
... ...
elements[size++] = e;
}
public void pop() {
... ...
Object result = elements[size--];
element[size] = null;
result element;
}
... ...
}
泛型化:
public void Stack<E> {
private E<> elements;
private size = 0;
... ...
public Stack() {
elements = new E[DEFAULT_INITAL_CAPACITY];
}
public void push(E e) {
elements[size++]= e;
... ...
}
public void pop() {
... ...
E result = elements[size--];
element[size] = null;
result element;
}
}
27.优先考虑泛型方法
泛型方法的一个显著的特性是,无需明确指定类型参数的值;
//
Map<String, List<String>> testMap = new HashMap<String, List<String>>();
//泛型单例模式
public static <K,V> HashMap<K,V> newHashMap() {
return new HashMap<K,V>();
}
Map<String, List<String>> testMap = new HashMap();
28.?利用有限制通配符来提升API 的灵活性
//未使用通配符
public void pushAll(Iterable<E> src) {
... ...
push(e);
}
public void popAll(Collection<E> dst) {
... ...
numberStack.popAll();
}
//使用通配符
public void pushAll(Iterable<? extends E> src) {
... ...
}
public void popAll(Collection<? super E> dst) {
... ...
}
有限制通配符可以限制泛型参数的范围:
pushAll()中,输入参数必须为初始化时E这个类的子类;
popAll() 中,输入参数必须为初始化时E这个类的父类;
//PYJ:以下需要结合具体业务实例来理解 ?????????
- 如果参数化类型为一个T生产者,就使用<? extends T>
- 如果表示一个T消费者,就使用<? super T>
29.?优先考虑类型安全的异构容器
Set 仅仅一个参数,Map 也仅仅2个参数,但是如果需要多个参数,此时需要将key进行参数化而不是将容器进行参数化,然后将参数化的key提交给容器,来插入或者获取值。用泛型系统来确保值的类型与它的键相符
//以Favorites 为例
public class Favorites {
private HashMap<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();
public <T> void putFavorite(Class<T> type, T instance) {
... ...
favorites.put(type, instance);
}
public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
}
//运用PYJ:?????????
Favorites f = new Favorites();
f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 0xcafaebabe);
f.putFavorite(Class.class, Favorites.class);
方法
38.检查参数有效性
- 对应公有方法,要使用Javadoc的@throw标签在文档中说明违反参数值会抛出的异常
- 对与非公有方法,使用“断言”
总的说,每次编写方法时,都需要考虑参数的现在,并且在方法体的开头处通过显示的检查来实施这些限制
**
39.必要时进行保护性拷贝
40.方法签名的设计
- 方法名称:
- 不要过于追求提供便利的方法:
- 避免参数列表太长:
- 对于参数类型,优先使用接口而不是类(“面向接口编程”)
- 对于boolean参数,优先使用2个元素的枚举类型
41.谨慎使用重载
使用时尽量确保每个重载的方法的参数个数不一样: 永远不要导出2个具有相同参数数目的重载方法;
42. 返回零长度的数组或者集合,而不是null(尽量避免返回null)
通用程序设计
45. 将局部变量作用域最小化
- 在第一次使用局部变量的地方声明
- 每个局部变量的声明应该都包含一个初始化表达式;
46. for-each 循环优先于传统的for循环
以下三种情况下for-each循环无法使用,其他情况下尽量优先使用for-each
- 过滤
- 转换
- 平行迭代
尽量优先使用for-each
48.如果需要精确的答案,避免使用float 和double
精确的数据尽量使用 BigDecimal , int 或者 long 进行计算