第二章 创建和销毁对象
何时用静态工厂方法代替构造器?
1、获取有特殊含义的对象时,用有名称的静态工厂方法优于使用参数。
2、不可变类希望复用实例时。
3、不想暴露具体子类型(例如非公有内部类),或客户不需要关心具体子类型时。隐藏子类型便于系统扩展(可以随时删除或增加新的子类型),还可以增加系统灵活性(在运行时决定具体实现)。
4、当实例化参数列表较长时。
参数很多时,使用builder模式。
单例singleton如何防止AccessibleObject.setAccessible攻击?
让私有构造器检测实例是否存在,如存在则抛出异常。
用枚举实现单例 mark
只有单个元素的枚举,简洁,安全,天然可序列化。
防止util类被误实例化 mark
包含一个私有构造器,并无条件抛出异常。
重用对象和创建新对象的权衡
1、循环语句中反复创建新对象,可能影响性能。
2、只有当创建新对象代价非常大时(比如数据库连接对象)才建议维护对象池。
3、需要“保护性拷贝”的对象,重用有风险。
消除过期对象的引用 mark
书中的解说容易误解。其实原理很简单,一个数组逻辑上容量缩减,但虚拟机并不理解这个逻辑,因此不会回收边界之外的对象。只要数组对象不被回收,边界之外的引用就一直存在,并且这些对象所引用的对象也不会被回收。
将过期的引用置为null即可。
只要类是自己管理内存,就应该警惕内存泄漏问题。
内存泄漏另一个常见来源是缓存。简单来说,就是缓存的部分数据可能不再有用,但不会自动过期。可以由一个后台线程实现定时失效。
内存泄漏第三个来源是监听器和其他回调。这段没看懂。
第三章 对于所有对象都通用的方法
通用方法的通用约定(general contract)
equals:
自反性reflexive,对称性symmetric,传递性transitive,一致性consistent,非空性(不等于null)。
覆盖equals时总要覆盖hashCode
hashCode约定:equals为true,则hashCode必须相等;equals为false,则hashCode最好不等(提高查询性能)
hashCode最佳实践:
hc = 17;
for field in fields
do
hc = 31*hc+hashCode(field); //或 (hc << 5)-hc+hashCode(field)
end for
return hc;
对于不可变类,如果hashCode()开销比较大,考虑缓存。
一个简单的hashCode()方法,其实有那么多可优化的细节。
始终覆盖toString
实际工作中,VO类应该覆盖toString,server类可以不覆盖。
谨慎覆盖clone
慎重使用cloneable接口。。
compareTo约定:
这里用数学符号>,<,=来表示顺序
1、x>y则y<x, x=y则y=x;
2、x>y>z则x>z
3、x=y则x.compareTo(z)和y.compareTo(z)符号相等
第四章 类和接口
如果一个类适合设计成不可变的,就设计成不可变的。
不可变类遵循五条规则:
1.不要提供任何会修改对象状态的方法
2.保证类不会被扩展。“final”修饰,或使用静态工厂方法。
3.所有域都要是final,且为私有
4.确保对于任何可变组件的互斥访问。不使用客户端提供的对象引用初始化这样的域;不要在任何访问方法(accessor)中返回这样的域,而是使用保护性拷贝(defensive copy)
不可变对象本质是线程安全的,不要求同步
组合优于继承
除非是专门为了继承而设计的类,否则不要继承。
要么为继承而设计,否则禁止继承。
如何设计一个可以被继承的类?
1、提供文档,说明某个方法调用了哪些可覆盖方法,覆盖这些方法会造成什么影响。(写给扩展类的程序员,而非用户)
2、提供protected hook
3、构造器决不能调用可被覆盖的方法,否则会造成父类构造器super()调用被子类覆盖的方法,造成不可预知的后果。
4、慎用Cloneable 和 Serializable
如何实现一个不可继承的类?
1、final 2、静态工厂代替构造器
继承要慎用,其使用场合仅限于你确信使用该技术有效的情况。一个判断方法是,问一问自己是否需要从新类向基类进行向上转型。如果是必须的,则继承是必要的。反之则应该好好考虑是否需要继承。《Java编程思想》
只有当子类真正是超类的子类型时,才适合用继承。换句话说,对于两个类A和B,只有当两者之间确实存在is-a关系的时候,类B才应该继续类A。《Effective Java》
接口优于抽象类
可以混合实现多个接口,更灵活;可以运用decorator模式方便的扩展功能;
想要同时具有抽象类的方便性,可以为接口提供一个抽象的骨架实现(例如AbstractList)。接口的实现可以选择扩展骨架类,也可以选择完全手动实现;完全手动实现时,可以使用“模拟多重继承(simulated muliple inheritance)”简化实现。
接口的缺点在于不能随便增加新功能。
用函数对象表示策略
即策略模式。策略类应该是无状态的。
嵌套类的使用
嵌套类最好都是私有的。如果需要被外围引用,就应该声明为顶层类。
静态成员类:最接近普通类,只能访问外围静态成员。
非静态成员类:与外围类实例绑定。可访问外围类非静态成员。非静态成员类的实例必然归属于某个外围类的实例。例如集合类的Iterator。
匿名类:常用语创建函数对象。
第五章 泛型(generic)
泛型的通配符类型
通配符通常用于函数参数,使函数可接受不同类型的泛型对象。
无限制的通配符类型(unbounded wildcard type)
List<?> 和 List<Object>的区别:
1、List<?>可接受其他类型List赋值,例如List<?> l = new ArrayList<String>();List<Object>不可以。因此,如果一个函数可以接受不同类型的列表做参数,可以写成 void func(List<?> arg)
2、List<?>不能添加任何元素(null除外),因此更加安全。
固定上边界的通配符(Upper Bounded Wildcards):
<? extends E>,不能使用add(除了null),get返回E类型。
固定下边界的通配符(Lower Bounded Wildcards):
<? super E>,可以使用add(E类型或null),不能使用get。
PECS:producer-extends, consumer-super
如果泛型是一个生产者(使用get),使用<? extends E>;如果是一个消费者(使用add),使用<? super E>。
如果函数的参数即是生产者,又是消费者,就不该用通配符,需要严格的类型匹配。
尽可能消除每一个非受检警告!!!
泛型的类型擦除(erasure)
泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。通俗地讲,泛型类和普通类在 java 虚拟机内是没有什么特别的地方。
相对的,数组是具体化的(reified。个人理解即运行时,不同类型在虚拟机中的表示是有区别的,运行时表示包含了具体的类型信息)。
优先考虑类型安全的异构容器
有点累,以后再看
第六章 枚举和注解
java枚举的本质:通过公有的静态final域为每个枚举常量导出实例的类。是没有构造器的类,不能实例化也不能扩展。
枚举代替int常量
枚举的优势:
1、实例受控
2、类型安全
3、可添加方法和域(例如一段描述性文本),实现了常见的通用接口(Object方法,Serializable,Comparable)
用实例域代替序数
即,如果要对枚举常量关联一个int值,不要使用ordinal方法,而是保存在一个int类型的域中,更好维护。
用EnumSet代替位域,EnumMap代替序数索引
累了,改天看.
用接口模拟可伸缩的枚举
当需要对枚举进行扩展时。客户端扩展接口定义自己的枚举,服务端用接口接受枚举实例。
public interface EnumItf{
void action();
}
public enum BasicEnum implements EnumItf{
...
}
public enum extendedEnum implements EnumItf{
…
}
public void serverAPI(EnumItf concrEnum){
concrEnum.action();
}
第七章 方法
方法参数优先使用接口而不是类;优先使用枚举类型,而不是枚举的value
最好避免定义两个有相同参数数目的重载方法,不要重载可变参数的方法。
返回空数组或集合而不是null
为所有API编写文档
第八章 通用程序设计
局部变量作用域最小化
目的是增强代码的可读性。在第一次使用变量的地方声明。
精确计算时,避免使用float和double
建议使用BigDecimal。迟些看细节。
优先使用接口引用对象,而不是类型
第九章 异常
只针对异常情况使用异常
不要用异常代替业务校验。
try-catch会阻止JVM的某些性能优化。
自定义异常可以添加新的域和方法,以提供更多相关信息;
toString方法应包含足够多的定位错误信息,可以写在message中,也可以重写toString方法
失败原子性
方法调用失败,对象仍保持在被调用之前的状态。
同时,调用者应考虑捕获异常后,对象的状态是否被破坏。