《Effective Java》读书笔记

《Effective Java》读书笔记

1.静态工厂方法

  • 相对公有构造器的优势
    • 有名字。静态工厂方法可以根据功能定义名字,但构造器名字都是类名。
    • 不必每次调用时都创建对象。如果经常请求创建相同的对象,并且创建对象的代价很高,则这项技术可以极大的提高性能。
    • 可以返回原返回类型的任何子类型。这样在选择返回类时,更加灵活。
    • 在创建参数化实例的时候,它们使得代码更加简洁。
  • 缺点
    • 类如果不含有公有的或者受保护的构造器,就不能被子类化。因为在创建子类对象时,需要调用到父类的公有无参数构造器方法。
    • 它们与其他的静态方法实际上没有任何区别。因为在API文档中未被明确标记,因此对于提供了静态工厂方而不是构造器的类来说,要想查明如何实例化一个类,这是非常困难的。
  • 总结静态工厂方法和公有构造器各有用途,但是应该首选考虑用静态工厂方法替代构造器。

2.Singleton实现机制

  • 三种方式:
    • 功能上和公有域近似。
    • 优势:
    • 代码简洁
    • 无偿提供序列化机制
    • 防止多次序列化
    • 构造器方法设置为private,导出一个final的public静态成员。
    • 构造器方法设置为private,利用公有静态工厂方法导出final的private静态成员。
    • 包含单个元素的枚举类型
  • 总结:
    • 单元素的枚举类型是实现Singleton的最佳方式,是jdk1.5开始有的。
    • 前两种方式核心思想:构造器设置为private+导出公有静态成员


3.内存泄漏的3个来源

  • 类自己管理内存
    • 解决办法:一旦元素被释放,就应该将该元素中包涵的任何对象引用清空。
    • 例子:Stack类,存放元素的数组为elementData。在pop操作后,就应该执行elementData[elementCount] = null;从而使得GC可以对它里面保存引用的对象进行清理(elementData中保存的是对象引用,而不是对象本身)
  • 缓存
    • 缓存应定时清掉无效的项。这项清理工作可以由一个后台线程来完成
    • 在给缓存添加条目时顺便进行清理
    • 解决办法
  • 监听器和其他回调
    • 解决办法:回调立即被当作垃圾回收的最佳方式是:只保存它们的弱引用。弱引用:被弱引用关联的对象只能生存到下一次垃圾回收之前


4. equals()方法分析

  • 覆盖equals时的通用规定
    • 自反性:对象必须等于自身
    • 对称性:任何两个对象对于“它们是否相等”的问题都必须保持一致。
    • 传递性:a和b相等,b和c相等,则a和c相等。
    • 一致性:如果两个对象相等,则它们始终保持相等,除非它们中有一个被修改了。
    • 非空性:所有的对象都必须*不等于*null常见错误用法:许多类的equals方法第一句都是:if(o==null) return false;但这样写是冗余的。因为equals方法在把参数转为适当类型前,必须调用instanceOf操作符,检查其参数是否为正确的类型。null在进行instanceOf检查类型时,如果传入参数为null,则直接返回false。
  • 高质量equals()书写原则:
    • 使用==操作符检查“参数是否为这个对象的引用”。如果是,则返回true。这是一种性能优化,如果比较操作可能很昂贵,则值得这么做。
    • 使用instanceOf操作符检查“参数是否为正确的类型”。
    • 把参数转换成正确的类型。
    • 对该类中每个关键域,检查参数中的域是否与该对象中对应的域匹配。查看jdk源码可以发现,基本都是这样写的,格式完全一致
  • 常用用法说明:
    • 非float、double的基本类型,直接用==进行比较即可。
    • 对象引用域,则递归调用equals方法。
    • 对于float域,则用Float.cmopare()方法。
    • 对于double域,则用Double.compare()方法。
  • 覆写equals时,必须覆盖hashCode()方法
    • 这一点很重要,因为任何两个equal的对象,在调用hashCode()方法时,必须返回同一个整数。


5.接口vs抽象类

  • 最大区别为实现抽象类定义的类型,类必须为抽象类的子类。
  • 接口的优势
    • 现有的类可以很容易被更新,以实现新的接口对于一个新的接口,如果一个类想实现它,则只要在类的声明中添加一个implement子句,同时override接口中的方法即可。
    • 接口时定义mixin(混合类型)的理想选择mixing类型是指:类除了实现它的“基本类型”以外,还可以实现这个mixin类型,以表明它提供了某些可供选择的行为。比如JKD中的Clone接口、Comparable接口。一个类如果实现了Comparable接口,则表明这个类的实例可以和其它的可相互比较的对象进行排序。
    • 接口允许构造非层次结构的类型框架


6.列表vs数组

  • 不同点
    • 数组是协变的,泛型是不可变的。协变:如果Sub为Super的子类型,则数组类型Sub[]为Super[]的子类型不可变:任意两个不同的类型Type1和Type2,List既不是List的子类型,也不是List的超类型
    • 数组是具体化的。数组元素的类型:在程序运行时才检查类型约束泛型:在编译时强化类型信息,运行时擦除类型信息
  • 为什么创建泛型数组是非法的?因为它不是类型安全的。因为数组元素的类型检查发生在运行时,如果定义了一个泛型数组,则其类型不能在编译时进行检查,如果定义出现错误,则会在程序运行时发生ClassCastException异常。这就违背了泛型系统提供的保证。因为泛型是在编译时进行类型检查,故不会出现运行时的ClassCastException。


7.可变参数

  • 机制
    • 先创建一个数组
    • 然后将参数值传入到数组中
    • 最后将数组传递给方法
  • 性能问题可变参数方法的每次调用都会导致进行一次数组分配和初始化。


8.基本类型vs装箱类型

  • 主要区别
    • 基本类型只有值,而装箱类型则具有与它们的值不同的同一性。解释:换句话说,两个装箱基本类型可以有相同的功能值,但是有不同的同一性。
    • 基本类型只有功能完备的值,但装箱类型除了有对应的功能值之外,还有一个非功能值null。
    • 基本类型比装箱类型节省空间和时间。
  • 装箱类型的合理用处
    • 作为集合中的元素、键、值– 在参数化类型中,必须使用装箱类型
    • 在进行反射方法的调用时,必须使用装箱类型


9.native方法

  • 定义是指本地程序设计语言(比如c或者c++)来编写的特殊方法
  • 用途
    • 提供了“访问特定于平台的机制”的能力
    • 提供了访问遗留代码库的能力
  • 缺点
    • 程序不再能免受内存损坏错误的影响。因为本地语言是不安全的
    • 程序不再可自由移植。因为本地语言是与平台相关的
    • 程序难调试
    • 进入和退出native方法需要相关开销
    • 需要“胶合代码”的本地方法编写起来单调乏味,难以阅读


10.序列化

  • 序列化的代价
    • 最大代价是:一旦一个类被发布,就大大降低改变这个类的实现的“灵活性”如果一个类实现了Serializable接口,它的字节流编码就变成了它的导出API的一部分。一旦这个类被广泛使用,往往必须永远支持这种序列化形式。
    • 它增加了出现Bug和安全漏洞的可能性。因为序列化机制是一种语言之外的对象创建机制,反序列化机制都是一个“隐藏的构造器”,因为没有显示的构造器,所以很容易忘记确保:反序列化过程必须也要保证“由真正的构造器建立起来的约束关系”,并且不允许攻击者访问正在构造过程中的对象的内部信息。
    • 随着类发行新的版本,相关的测试负担增加。
    • 当一个可序列化的类被修订时,很重要的一点就是:检查是否可以“在新版本中序列化一个实例,然后在旧版本中反序列化”,反之亦然。
  • 使用默认序列化的缺点
    • 它使得这个类的导出API永远束缚在该类的呃内部表示法上
    • 消耗过多的空间
    • 消耗过多的时间因为序列化逻辑不了解对象图的拓扑关系,所以它必须要经过一个昂贵的图遍历过程。
    • 引起栈溢出默认的序列化过程要对对象图执行一次递归遍历,即使对于中等规模的对象图,也可能引起栈溢出。


分享到:


相關文章: