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方法第一句都是:
- 高质量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永远束缚在该类的呃内部表示法上
- 消耗过多的空间
- 消耗过多的时间
因为序列化逻辑不了解对象图的拓扑关系,所以它必须要经过一个昂贵的图遍历过程。 - 引起栈溢出
默认的序列化过程要对对象图执行一次递归遍历,即使对于中等规模的对象图,也可能引起栈溢出。