《Effective Java》读书笔记

来自:CSDN(作者:iCoding91)
原文链接:
https://blog.csdn.net/caoxiaohong1005/article/details/79521960

1.静态工厂方法

相对公有构造器的优势

有名字。
静态工厂方法可以根据功能定义名字,但构造器名字都是类名。
不必每次调用时都创建对象。
如果经常请求创建相同的对象,并且创建对象的代价很高,则这项技术可以极大的提高性能。
可以返回原返回类型的任何子类型。
这样在选择返回类时,更加灵活。
在创建参数化实例的时候,它们使得代码更加简洁。

缺点

类如果不含有公有的或者受保护的构造器,就不能被子类化。
因为在创建子类对象时,需要调用到父类的公有无参数构造器方法。
它们与其他的静态方法实际上没有任何区别。
因为在API文档中未被明确标记,因此对于提供了静态工厂方而不是构造器的类来说,要想查明如何实例化一个类,这是非常困难的。

总结

静态工厂方法和公有构造器各有用途,但是应该首选考虑用静态工厂方法替代构造器。

2.Singleton实现机制

三种方式:

功能上和公有域近似。
优势:
代码简洁
无偿提供序列化机制
防止多次序列化
构造器方法设置为private,导出一个final的public静态成员。
构造器方法设置为private,利用公有静态工厂方法导出final的private静态成员。
包含单个元素的枚举类型

总结:

单元素的枚举类型是实现Singleton的最佳方式,是jdk1.5开始有的。
前两种方式核心思想:
构造器设置为private+导出公有静态成员

3.内存泄漏的3个来源

类自己管理内存

解决办法:
一旦元素被释放,就应该将该元素中包涵的任何对象引用清空。
例子:
Stack类,存放元素的数组为elementData。
在pop操作后,就应该执行
elementData[elementCount] = null;
从而使得GC可以对它里面保存引用的对象进行清理(elementData中保存的是对象引用,而不是对象本身)

缓存

缓存应定时清掉无效的项。
这项清理工作可以由一个后台线程来完成
在给缓存添加条目时顺便进行清理
解决办法

监听器和其他回调

解决办法:
回调立即被当作垃圾回收的最佳方式是:
只保存它们的弱引用。
弱引用:被弱引用关联的对象只能生存到下一次垃圾回收之前

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

你可能感兴趣的:(《Effective Java》读书笔记)