Effective Java笔记

创建销毁对象

Item1: 使用静态工厂代理构造器

优点:

1.静态工厂可自定义名字,见名知意,如BigInteger(int, int, Random)BigInteger.probablePrime.有时多个构造器会让人无从选择,容易出错。
2.静态工厂不需要每次调用都创建一个对象。对于某些对象可以重复利用的场景,这种方式效率更高。
3.静态工厂可返回方法返回对象的任意子类,基于接口编程。
4.静态工厂可根据输入参数来创建不同的返回对象
5.静态工厂可以仅仅是一个占位符,根据注入的不同服务,返回不同的对象,如JDBC
缺点:
1.如果类中不包含publicprotected的构造函数,将无法被继承
2.难以区分那些是静态工厂方法,那些是普通的静态方法,通常使用方法名称区分,如of,from,valueOf,getInstance

Item2: 若构造器需要很多参数的时候,考虑使用建造者模式

当构造器需要多个参数,而且部分参数可选的时候,通常有几种处理方式:
1.使用构造器重载,默认参数在简单的构造器中设置,简化客户端调用。
缺点:在参数很多的时候,组合的情况很多。代码难写也难读。
2.使用JavaBean的方式,初始化设置属性的默认值,使用set方法来设置必要的参数。
缺点: 会导致不一致的问题,参数没设置完整,对象已经是可用的。另外会让对象变得可变,多线程就得考虑线程的安全
3.使用建造者模式,Builder使用链式调用,每设置一个参数,返回的是本身,设置完所有的参数,调用build 方法的时候,将构造器传入给要新建的对象,通过构造器设置完参数后,返回。

Item3: 使用私有构造器或者枚举类型来强化单例属性

三种方式:
1.public static final INSTANCE= new Singleton(),私有化构造器,简单直接
2.私有化属性,私有化构造器,静态工厂返回单例,如果不需要对单例对象加工,也不需要使用范型,也不需要使用suppler,如Singleton::INSTANCE,则使用第一种方式
3.使用枚举来实现,通常作为最佳实践

Item4: 通过私有化构造器来强化不可实例化的能力

一些工具类只包含静态方法,静态变量,不需要被继承和实例化,此时采用私有化构造器来保证其不被实例化和继承。

Iemt5: 采用依赖注入来取代硬编码

资源使用依赖注入比使用静态工厂或静态工具类更灵活更容易测试。

Item6: 避免创建不必要的对象

不可变对象复用,原生类型的装箱拆箱。

Item7: 消除过期对象的引用

不再引用的对象置为null,绝大部分情况不需要设置,若在作用域以外仍然会引用废弃的对象,才需要设置。自己管理内存或者对象的情况下,需要考虑。

Item8: 避免使用终结方法

Finalizers不可预料,危险通常是不必要的。JAVA9不推荐使用Finalizers,以Cleaners取而代之,比Finalizers安全,同样不可预料,影响性能,没有必要使用。因为不知道终结方法何时执行。Finalizers会吞噬异常,Cleaners不会吞噬异常。作为安全网或者是终结非关键的本地资源,否则不要使用终结方法。

Item9: 使用try-with-resources代替try-finally

对所有对象都通用的方法

Item10: 覆盖equal的时候遵守通用约定

1.自反性 对于非空对象,x.equal(x)返回ture
2.对称性 x.equal(y)y.equal(x) x,y为继承关系,容易出现违反对称性,因为调用不同的equal方法。
3.传递性 x.equal(y),y.equal(z)x.equal(z) y为父类,x,z为子类,容易违反传递性,将子对象转换成父对象比较,都相等,子对象里判断了其他属性,导致两个子对象不相等。只要父类不可实例化,就不会违反传递性。
4.一致性 在不修改x,y的情况下,x.euqal(y)不变,如果依赖了不可靠的资源,比如域名对应ip,就容易违反。
5.任何非空x,x.equal(null)返回false

我们无法在扩展可实例化的类的同时,即增加新的属性,又保留equal的约定。
实现高质量的equal步骤:
1.使用==判断是否相同的引用
2.使用instanceof检验参数的类型是否正确
3.将参数转换成正确的类型
4.检查关键的字段是否一致
5.考虑对称性、传递性、一致性
实现equal要注意equal的参数是Object类型,如果改成其他类型的,就不是重写,而是重载。重写时使用override注解,重写错类,会提示编译错误。

Item11: 覆盖equal时,一定要覆盖hashcode

相等的对象,hashcode一定相等,不相等的对象,hashcode也可能相等。因此,equal里用到的关键字段,在hashcode的计算时,都应该用到,在equal里没有用到的字段,不要用于hashcode计算。
hashcode计算规则:
1.初始化一个reuslt值,步骤2循环关键字段
2.根据字段计算c,如果字段是原生类型,则调用Type.hashCode(f),如果字段是引用类型,则调用对象的hashCode,如果是数组,使用Arrays.hashCode
3.result = 31* result + c,重复循环计算关键字段。

Item12: 始终覆盖toString

Item13: 谨慎地覆盖clone

Item14: 考虑实现Comparable

类与接口

Item15: 使类和成员的访问最小化

Item16: 在公有类中使用访问方法,不使用公有域

Item17: 使可变性最小化

实现不可变类:
1.不提供任何会修改对象的方法
2.保证类不会被扩展
3.使所有的域都是final
4.使所有的域都成为私有的
5.如果域是可变的,则不让改域能在外部访问,也不要返回可变的域,也不要使用外部传入的可变对象来初始化域

Item18: 组合优与继承

继承破坏了封装。继承其他人编写的具体类时,要有文档说明可以被继承,否则容易出错。只有在确实子类是父类的子类型,采用继承,否则用组合。

Item19: 为继承做好设计,并文档化,否则就不要用继承

Item20: 接口优于抽象类

Item21: 做好接口设计

虽然默认接口能实现在接口里添加方法,不影响原来的实现。但是不要这么做,因为可能导致运行时的问题。默认方法可用于提取接口实现的公有逻辑,不要用来修改接口。

Item22: 接口只用于定义类型

不要使用常量接口,常量可以使用工具类来实现。

Item23: 类层次优于标签类

Item24: 优先考虑静态成员类

Item25: 限制一个文件定义多个顶级类

泛型

Item26: 不要使用原生类型

Item27: 消除非受检警告

尽量消除,无法消除的使用注解SuppressWarnings("unchecked"),并注明原因,SuppressWarnings("unchecked")范围越小越好。

Item28: 列表优先于数组

Item29: 优先考虑泛型

Item30: 优先考虑泛型方法

Item31: 利用有限制通配符来提升API的灵活性

Item32: 明智的组合泛型和可变参数

Item33: 优先考虑类型安全的异构容器

枚举和注解

Item34: 用enum代替int常量

Item35: 用实例域代替序数

Item36: 用EnumSet代替位域

Item37: 用EnumMap代替序数索引

Item38: 用接口模拟可伸缩的枚举

Item39: 注解优先于命名模式

Item40: 坚持使用Override注解

Item41: 用标记接口定义类型

lamda与stream

Item42: lamda优先于匿名类

优先使用lamda,lamda由于没有名字和文档,所以表达式应该清晰明了,代码不要超过3行,。大部分使用匿名类的场景都可以使用lamda替换,除非要使用多个函数。