EFFECTIVE JAVA 学习笔记
JAVA SE范例:原生基本类型的包装类
优点:
1. 工厂方法带有名字
2. 不必每次调用都实例化对象(更为灵活、尤其适用于不可变类)
3. 能够返回任意子类对象(适用于基于接口的框架,如集合框架、以及更为灵活的服务提供者框架, 如JCE)
缺点:
1. 如不提供公共或受保护的构造函数则不能被子类化(继承)
2. 难以同其他的静态方法区分(采用命名约定有一定效果)
实现单件有两种方式:
1. 采用公共静态成员变量+类静态初始化
2. 采用私有静态成员+静态工厂方法(更为灵活)。
JAVA SE范例:数学、集合等实用工具类
优点:
1. 保证类不可实例化,达到仅提供工具函数的目的,同时也保证不被继承(导致子类实例化)
缺点:
1. 容易滥用,导致过程式编程
JAVA SE范例:基本对象的包装类
优点:
1. 减少冗余、提高了复用,同时性能也大为提升(尤其适用于不可变对象)
JAVA SE范例:堆栈的数组实现方式
优点:
1. 减少内存泄露,提高性能
缺点:
1. 不能保证及时执行
2. 不能依赖其保证持久状态
JAVA SE范例:线程、随机、集合等类。
不进行重写的情况:
1. 类的实例独一无二
2. 并不需要类进行逻辑相等测试
3. 超类已经提供可用equals()方法
4. 类为私有类(重写为抛出异常)
重写时遵守的协议:
1. 反身性
2. 对等性
3. 传递性
4. 一致性
5. 非空性
高质量equals()方法的特征:
1. 使用==操作符先行测试
2. 使用instanceof操作符测试
3. 使用cast进行类型转换
4. 相应字段进行相等性测试
5. 进行对等性、传递性、一致性
重写equals()方法时的注意事项:
1. 重写equals()方法时重写hashCode()方法
2. 不要过于聪明
3. 不要依赖不可靠资源
4. 不要子类化参数类型
JAVA SE范例:HashMap、HashTable、HashSet
重写时遵守的协议:
1. 相等测试字段不变时,hashCode()方法返回相同整数
2. 相等的两个对象返回相同的hashCode整数
3. 不相等的两个对象也可能产生相同的hashCode整数
4. 可以通过一个私有字段缓存hashCode值
构建Hash函数的基本步骤:
1. 存储非零整数值如17到整形变量result
2. 计算相关字段对应的hash值c
3. result = 37*result + c
4. 返回result
5. 测试是否相等对象拥有相同hash值
JAVA SE范例:Object对象的toString()方法
优点:
1. 更为方便使用、更为清晰
重写toString()时的注意事项:
1. 应该包含对象中的所有有意义的信息
2. 注释文档的意图(不管采用特定格式与否)
3. 方法返回的值最好能够编程访问
JAVA SE范例:Clonable接口
优点:
1. 提供更为灵活的clon()方法实现
2. 能够充当构造函数
JAVA SE范例:List、Array等有序集合
实现compareTo()方法时遵守的规范:
1. 对称性
2. 传递性
3. 等值性
4. 相等性
5. 方法在比较不同类对象时抛出异常
信息隐藏或封装是这一原则的基本概念,通过封装解藕模块关系。Java中内置有访问控制机制,提供private、protected、包访问等。
最小化原则:
1. 尽可能使得类或成员不可访问
2. 顶层类尽可能提供包私有访问
3. 成员尽可能提供私有访问
4. 不要提供public静态数组字段
JAVA SE范例:String、包装类等
类不可变的五大原则:
1. 不提供修改对象的方法
2. 方法都不能被复写
3. 字段均为final型
4. 字段私有
5. 排除对可变组件的访问
优点:
1. 简单
2. 线程安全
3. 自由共享
4. 为其他对象提供大型构建快
缺点:
1. 每个值都得提供一个对象
总结:
1. 尽量使得类不可变
2. 限制类的可变性
3. 构造函数提供对像创建的所有信息
继承的不足:
1. 破坏了封装
2. 容易导致过深的继承层次
如何为了类的后续继承而设计、编档?
1. 必修对重写任何方法的效果进行精确的注释
2. 类可能必修通过受保护的方法提供到内部工作的钩子
3. 构造函数禁止调用(直接、间接)可重写的方法
4. 将readResolve方法或writeReplace方法设为受保护的
5. 设计一个用于继承的类添加了子类化限制
6. 适当的时候应当禁止子类化
1. 已经存在的类能够容易的实现一个新的接口
2. 接口是定义“微类型”的理想选择
3. 接口允许非继承的类型层次结构
4. 接口保证了安全、强大的功能增强
5. 抽象类比接口更容易演化(例外)
一个典型范例——常量接口,常量接口是接口的一个比较差的应用。总的来说接口不应当仅仅用于定义类型
JAVA中包含四种内联类:
1. 静态成员类
2. 非静态成员类
3. 匿名类
4. 本地类
类相比于结构体现出更好的封装性,但有时基于性能的考虑也有例外。
C中的枚举仅定义了命名整数常量,JAVA中提供了新的类型安全枚举模式作为替代物。
通常方法对传入的参数的有效性有一定约束,如索引越界、非法参数、空指针等。具体检测策略:
1. 对公用方法用throws注释
2. 内部方法通过assert结构强化处理
总之,写方法是应该认真考虑参数的限制、予以注释并进行相应的检查。
有必要进行防卫式编程,以保证不可变性,通常这需要通过防卫式拷贝予以实现。防卫式拷贝在参数有效性之前执行,并通常不使用Clon方法实现。
1. 仔细的选择方法名
2. 不要在提供便利方法上走过头
3. 避免长参数列表
4. 对于参数类型,青睐接口而非类
5. 谨慎使用功能对象
1. 编译期静态决定调用那个重载方法
2. 重载方法的选择是静态的、重写方法的选择是动态的
3. 要避免重载方法的混淆
4. 不要导出两个参数数相同的重载方法
1. 方法的注释应该描述其功能及与客户的契约
2. 以一致的规范编写注释
最小化局部变量作用域,增强了代码的可读性和可维护性、同时降低了犯错的几率。
1. 当局部变量第一次使用时,声明局部变量
2. 几乎每个局部变量的声明都应同时初始化
3. 循环变量最好置于FOR循环之中
4. 保持方法的短小精悍
1. 应用库你就利用了专家知识和前人的经验
2. 应用库也节约了开发时间
3. 易于掌握新添特性
4. 降低了学习成本
5. 不用重新发明轮子
FLOAT、DOUBLE型变量不适用于准确十进制计算,尤其在财务计算中,这是做好用BigDicimal等类型。
1. String不适于充当其他值类型的替代物
2. String不适于充当枚举类型的替代物
3. String不适于充当聚合类型的替代物
4. String不适于充当功能的替代物
5. 更好的类型存在或能构造时,不要使用String
1. 字符串连接符缺乏性能表现
2. 用StringBuffer
1. 合适的接口存在,则优先使用接口
2. 应用接口,程序更为灵活
3. 不存在接口类型才使用类引用对象
反射的缺点:
1. 失去了编译期类型检查
2. 不容易书写、繁杂
3. 性能损耗
作为规则,运行时对象不应当以反射的方式访问。总之,反射一般仅用于初始化对象,除非对象完全不可知(接口类型都不存在)。
滥用异常进行程序流程控制的缺点:
1. 异常处理的代价高昂
2. JVM优化
3. 用异常进行边界检查没必要
4. 异常不应当用于正常的控制流
5. 设计良好的API不应当让用户处理正常控制异常
JAVA中包含三种异常:受检异常、运行时异常、错误。
异常的使用时机:
1. 当调用者被期望从异常状况中恢复时使用受检异常
2. 使用运行时异常指明程序错误
3. 所有的非受检异常都应该继承自RuntimeException
受检异常往往容易加重使用负担,强迫处理异常状况。仅当下列两种情况同时成立时,才使用受检异常:
1. 异常状况不能通过API的适当使用避免
2. 开发人员希望进行额外的处理
JAVA中提供了一系列的标准非受检异常,开发人员可以在适当的时机予以重用。重用标准异常的好处:
1. API学习成本更低
2. 程序更易阅读
3. 更少的类、更快的类加载
常用的标准异常:
1. 非法参数异常
2. 非法状态异常
3. 空指针异常
4. 下标越界异常
5. 同步修改异常
6. 不支持操作异常
JAVA中的异常链在进行异常传播时,通常对应着异常抽象级别的降低。高层应该捕获底层异常,抛出解释高层抽象的异常。
1. 总是声明受检异常,并通过@Throws精确注释
2. 使用@throws注释非受检异常,但不包含在方法声明中
3. 一个异常因同一原因在类的多个方法出现,则最好在类级别注释该异常
JAVA中未捕获的异常将打出异常信息,该信息来自异常的toString方法。失败捕获信息中应该包含对异常有贡献的参数的值。
失败的方法调用应该将对象的状态恢复到调用之前——失败原子。达到失败原子性的途径:
1. 不可变对象
2. 调用前参数校验
3. 排序计算,将修改置于发生失败的计算之后
4. 工作于对象的临时拷贝
通常,开发人员会通过一个空的catch快忽略异常,然而这通常并非所要的处理方式。这种方式违背了异常的目的——要求进行适当处理。至少,catch代码快应该包含为什么忽略异常处理的合理解释。
线程神话:基于性能的考虑,应当避免使用原子数据的读写同步。同步在线程间通信和排除可变性一样是必修的。总之,只要多个线程共享可变数据,读写数据的线程就应该进行加锁。
1. 为避免死锁,不要在同步方法(代码快)内将控制转到客户端。
2. 作为规则,尽量保持同步代码的短小。
对象的wait方法用来让线程等待适当的条件,Wait方法的标准使用方法是:在被锁定对象的同步代码中调用。Wait方法的使用方式:总是在循环中调用wait方法,以等待特定条件成立。
1. 依赖线程调度的程序更没有可移植性
2. 线程优先级是JAVA平台中最不具备移植性的特性
3. Thread.yield方法仅用于提高测试时的并发量
类应该清楚的表明其线程安全性的原因:
1. 同步代码是实现细节,并非导出API的一部分
2. 为保证安全的多线程使用,类必须在规格层面指明其支持的线程安全性
1. 线程组没有提供任何的安全功能
2. 线程组在线程安全方面很弱
3. 线程组基本上是多余的