您的位置:首页 > Web前端

effective java 读书笔记---第四章类与接口

2017-04-17 00:01 134 查看
20170411

13.要使类与成员的可访问性最小

尽可能的使每个类或成员不被外界访问

对于方法覆盖,子类中的访问级别不能低于父类的访问级别

实例域决不能是公有的

静态域除了静态常量,也不应该是公有的

长度非零的数组总是可变的,所有静态 final 数组域或者返回这种域的访问方法几乎总是错误的,因为客户端可以很容易的修改数组中的内容,解决这种问题有两种方法,1.定义私有静态数组域,并增加一个公有的不可变列表:

private static final Thing[] PRIVATE_THINGS = {...}
private static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_THINGS));


注意这种方法也仅仅只能防止数组元素的指向不能指向新的内存地址,不能保证数组指向的对象的域不会改变(unmodifiableList返回的 list 指向的对象与原list指向的对象地址一致,asList 也是如此)

第二种方法:定义私有静态数组域,并增加一个公有方法,返回私有数组的一个备份

(这种方法返回数组也不能保证不会改变原有数组元素的对象内的域不被改变,仅能保证原有数组元素指向的地址不变),注意如果这两种方法中所引用的元素对象是不可变的,则可以保证集合不可变

14.在公有类中使用公有方法而非公有域

如果类可以在它所在包的外部进行访问,就提供访问方法.但如果类是包级私有的,或者私有的嵌套类,直接暴露它的数据域并没有本质的错误

15.使可变性最小化

如果要定义不可变类,需要遵循以下规则:

不要提供任何可以修改对象状态的方法

保证类不会被扩展

所有的域都是 final

所有的域都是私有的

确保对于任何可变组件的互斥访问,如果类持有指向可变对象的域,必须确保该类的客户端无法获得指向这些对象的引用,并且永远不要用客户端提供的对象引用来初始化这样的域,也不要从任何访问方法中返回该对象的引用.在构造器,访问方法和 readObject方法中使用保护性拷贝

一般函数式的编程方法就使用以上逻辑,返回对象总是新建对象,而不是修改对象的引用

不可变对象本质上是线程安全的,他们不要求同步,不可变对象可以被自由的共享,因此不需要为不可变对象提供 clone 方法或者拷贝构造器

不仅可以共享不可变对象,甚至可以共享他们的内部信息

不可变对象唯一缺点是,对于每一个不同的值都需要一个单独的对象,而创建这些对象的代价可能很高

如果可以精确的预测出客户端将要在不可变的类上执行哪些复杂的多阶段操作,可以提供包级私有可变配套类的方法.如果无法预测,最好的方法是提供一个公有的可变配套类,例如 String 对象的可变配套类 StringBuilder(StringBuffer)

如果类不能作为不可变类,也应该尽可能的降低它的可访问性

16.复合优于继承

对于包内使用或者专门为继承而设计并且具有很好的文档说明的类来使用继承是非常安全的.但对于普通具体类,进行跨包边界的继承则是非常危险的.

继承打破了封装性

如果不清楚超类方法的实现细节,继承中覆盖方法往往容易出错,超类在后续的发行版本中可以获得新的方法.而如果继承如果只增加方法而不修改方法,也会出现问题,超类新的发行版本中可能含有与你定义的子类相同的方法

使用复合,可以有效避免上述问题,新类中每个实例方法都返回被包含的现有类实例对应的方法,这种被称为转发

包装类几乎没有缺点,但包装类,不适合在回调框架中使用,会引发 SELF 问题

只有在子类是真正的超类子类型时才适用于继承.包装类不仅比子类更加健壮,而且功能也很强大

17.要么为继承而设计,并提供文档说明,要么就禁止继承

必须有文档说明,它可覆盖方法的自用性,类必须在文档中说明,在那些情况下可能调用可覆盖的方法

按照惯例,如果方法调用了可覆盖的方法,在它的文档注释的末尾应该包含关于这些调用的描述信息:”This implementation…(该实现…)”,意味着这段描述关注该方法内部的工作情况.

好的 api 文档应该描述该方法做了什么工作,而不是它是如何做的

必须在发布类之前先编写子类进行测试(一般而言3个子类即可完成测试工作)

构造器决不能调用可被覆盖的方法.子类中覆盖版本的方法将会在子类构造器运行之前被调用,如果该覆盖方法依赖于子类构造器所执行的任何初始化工作,程序将会失败

clone 与 readObject 都不可以调用可被覆盖的方法

readObject,覆盖版本的方法将会在子类状态被反序列化之前运行

clone,覆盖版本方法则是在子类的 clone 方法有机会修正被克隆对象的状态之前运行

如果类实现了Serializable,并且该类有一个readResolve 或者 writeReplace 方法,就必须使得这两个方法称为 prodected 的否则子类会忽略掉这两个方法

如果标准的类没有实现标准接口,为安全,继承应该禁止调用任何可被覆盖的方法,完全消除这个类中可覆盖方法的自用性,就可以安全的进行子类化,可以将可覆盖方法的代码移植到私有的辅助方法中

18.接口优于抽象类

java 只允许单继承

可以提供骨架实现类

jdk1.8之后接口可以提供默认实现(子类可以不再实现这种提供了默认实现的方法),接口几乎完全具备抽象类的所有功能(接口仅能持有 public static final 的变量,方法全部是 public)

19.接口只用于定义类型

常量接口是对接口的不良使用

如果常量与现有类或者接口紧密相连,应该将这些常量置于相应的类或者接口中,如果这些常量应该被看成枚举类型,就应该使用枚举类型,否则应该使用不可实例化的工具类.如果大量使用工具类常量,可以使用静态导入,避免需要使用类名来修饰常量

20.类层次优于标签类

标签类过于臃肿,容易出错,并且效率低下.使用子类型化代替标签类,标签类是类层次的一种简单的仿效

类层次可以用来反映类型之间本质上的层次关系

标签类很少有适用的时候

21.用函数对象表示策略

策略模式,声明一个接口来表示该策略,并且为每一个具体的策略声明一个实现了该接口的类,当一个具体策略只被使用一次时使用匿名内部类,当被设计用来重复使用时,通常被实现为私有的静态成员类,并通过公有的静态 final 域被导出

22.优先考虑静态成员类

嵌套类被设计成只为它的外围类提供服务,嵌套类如果可能使用到其他环境,就应该设计成顶层类.嵌套类有四种:静态成员类,非静态成员类,匿名类和局部类,除了第一种,其他三种都被称作内部类

静态成员类,可以被看做是普通类.

非静态成员类,需要依赖于外围类才能初始化,每一个实例都持有外围类的引用

如果声明成员类,不要求访问外围实例,就要始终把static 修饰符放到它的声明中,如果忽略 static,那么每一个实例都将包含一个外围类引用,保留这份引用要消耗时间与空间,并且导致外围实例符合垃圾回收时依然得以保留.如果没有外围实例,就不能使用非静态成员类

匿名类主要用来动态创建函数对象

局部类(在任何可以创建局部对象的地方都可以创建局部类(一般为方法内部),局部类也可以分为静态与非静态)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 读书笔记