第四章_类和接口_effective_java

第十三条:使类和成员访问性最小化

  1. 尽量使每个类或者成员变量不被外界访问

    • 如果一个包级私有的顶层类或者接口只是在某一类内部被使用,则应该考虑将其声明为那个类的私有嵌套类
    • 如果方法a覆盖了超类中的方法b,则a的访问级别就不允许低于b的访问级别,这样可与确保任何可使用超类实例的地方也可以使用子类的实例。
  2. 实例域不可以是共有域。

    • 公有域意味着你放弃了对该域的限制能力。同时包含公有可变域的类并不是线程安全的
    • 不可变静态域可以通过公有静态final域来暴露这些常量。这种域的命名建议使用大写字母加下划线
  3. 长度非零的数组总是可变的,因为公有静态final数据域几乎总是错误的,因为客户端能够修改数据内容。修正这个问题可以返回一个不可变列表或者返回私有数组的备份

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    private static final Thing[] STH = {};
    //method 1
    public static final List<Thing> VALUES=
    Collections.unmodifiableList(Arrays.asList(STH));
    //method 2
    public static final Thing[] values(){
    return STH.clone();
    //这里要注意clone其实只是对数组的第一层引用做了深拷贝,如果thing里面有其他对象,比如String name,修改这个对象原数组还是会改变
    //参考文献 http://www.cppblog.com/baby-fly/archive/2010/11/16/133763.html
    }

第十四条:在公有类中使用访问方法而非公有域

如果类能在他的包外部进行外部访问,就提供访问方法。

如果类是包级私有的或者私有的嵌套类,直接暴露他的数据域并没有本质的错误

第十五条:使可变性最小化

这一条主要讲了一些不可变类的使用。为了是类成为不可变,要遵循以下五条规则:

  1. 不要提供任何会修改对象状态的方法。
  2. 保证类不会被扩展。一般做法是让这个类成为final
  3. 使所有域都成为fianl。
  4. 使所有域都成为私有的。
  5. 确保对于任何可变组件的互斥访问。如果类具有指向可变对象的域,则必须确保该类的客户端无法获得指向这些对象的引用。

使用不可变类的方法可以用函数式的做法,即每次运算都返回新的对象而不是在原对象上修改。

不可变对象本质上是线程安全的,它不需要同步,所以不可变对象可以被自由的共享。我们可以构造一些常量帮助客户端重用现有实例,更进一步可以提供一些不可变类的静态工厂,将频繁使用的实例缓存起来,当现有实力符合请求时返回缓存的实例(BigInteger就有这样的静态工厂)。

不仅可以共享不可变对象,甚至可以共享他们的内部信息。比如BigInteger a调用negate方法返回一个新的BigInteger b,b只需要修改符号位,数值位可以直接共享a的。

不可变类的缺点在于对于每个不同的值都需要一个单独的对象,这在值大量变化时会带来性能问题。解决这一问题的方法是提供一个可变的配套类,比如StringBuffer就是String的配套类。

不可变类与Serializable接口的相关讨论请见76条

第十六条:复合优先于继承

继承的缺点在于打破了封装性。因此尽量只在包内使用继承,或者只有当子类真正的和超类之间存在is-a关系的是再使用继承。否则应该是类B含有一个类A的私有实现,并暴露一个较小,较简单的api(包装类)。

第十七条:要么为继承而设计,并提供文档,要么就禁止继承

  1. 该类必须有文档署名他可覆盖的方法的自用性
  2. 为了方便编写游侠的子类,类必须通过某种形式提供适当的钩子(hook),以便能够进入他的内部工作流程中,这种hook可以使protected方法
  3. 对于为了继承而设计的类,唯一的测试方法就是编写子类
  4. 构造器不能调用可被覆盖的方法

另外说一下java类初始化的构造顺序: http://stone02111.iteye.com/blog/1823446

1 类中的静态对象先于static{}执行
2 static{}中的代码是在构建类对象之前执行的
3 构造子类的时候是按照先父后子的顺序执行的
4 如果在子的构造函数中并没有使用显式的调用父类的构造函数(使用super),则执行无参构造函数。
5 如果使用this(),则会先调用this(),再调用下面的代码(此时父类别的默认构造函数不再执行,而会根据执行this()执行相应的父构造函数)

第十八条:接口优先于抽象类

先说一下接口的基本概念:深入理解Java的接口和抽象类

 接口中可以含有 变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。

接口相比于抽象类的优点:

  1. 现有的类可以很容易的被更新,以实现新的类。只需要加一个implements子句即可,而抽象类则需要找到类层次中的一处添加新的功能
  2. 接口是定义mixin(混合类型)的理想选择。
  3. 接口允许我们构造飞车呢过次结构的类型框架

接口不允许方法实现。于是可以通过对你导出到每一个重要接口都提供抽象的骨架实现类把接口和抽象类的优点结合起来。比如Collections Framework中的AbstractCollection,AbstractSet等都是骨架实现。股价实现的好处在于,他们为抽象类提供了实现上的帮助,又保留了接口的灵活性。对于接口的大多数实现来讲,扩展股价类是一个很显然的选择,但是如果你无法扩展股价类,实现接口即可。

相比于接口,抽象类有一个明显的优势:抽象类比接口的演变容易的多。如果在后续版本你希望在抽象类中增加新的方法,添加具体的方法即可,子类都会继承这个方法。而接口增加新的方法则需要在所有implements的类中增加实现(否则无法通过编译),这在接口被广泛实现的情况下几乎是不可能的。

第十九条:接口只用于定义类型

接口应该植被用于定义类型,他们不应该被用来导出常量。

常量接口(只有静态final域没有方法)是对接口的不良使用。例子如参考文献

如果需要导出常量,应该是用枚举类型或者不可实例化的工具类(工具类的域是public static final,构造函数是私有的)。

第二十条:类层次优先于标签类

用类层次的模式可以比标签类更容易扩展并且内存用量更少。(标签类总会有一些冗余数据)

第二十一条:用函数对象表示策略

这一条指的是策略模式,java中主要是通过指定一个接口(抽象策略类),对于不同的实现策略用不同的类实现(具体策略类),最后宿主类调用时通过制定不同的具体策略类执行不同的策略。基本程序结构就是宿主类里面有一个域是抽象策略类,一个set函数来设置这个域。网上的一片参考文献讲的比书里细致:设计模式 ( 十八 ) 策略模式Strategy(对象行为型)

当一个具体的策略只需要被使用一次时,通常使用匿名类来声明和实例化这个具体策略类。当一个具体策略是设计出来供重复使用时,他的类通常就要被实现为私有的亭台成员类,并且通过公有的静态final域导出,导出类型为策略接口。

第二十二条:优先考虑静态成员类

这一条讲的是嵌套类。嵌套类分为两大部分,静态内部类和普通内部类,普通内部类又分为非静态成员类,匿名类和局部类。

静态内部类其实可以看成一个普通的类,因为他和外部类的具体实例没有关联。非静态类的每个实例都隐含着一个与外围实例的关联。如果声明成员类不要求访问外部实例,则始终要把static关键字放在他的声明中,使之成为一个静态内部类。

静态类的一个常见用法是作为公有的辅助类,比如Calculator的静态内部类Operator。另一种用法是用来代表外围类的对象的组件。比如Map内部的Entry(具体为hashmap的static class Node<K,V> implements Map.Entry<K,V> )。因为enrty上的方法getkey,getvalue等都不需要访问map,所以可以声明为静态内部类。

普通内部类的一个常见用法是定义一个Adapter,比如Map中用它来是先keyset,entrySet,以及各种集合类用来实现iterator。

匿名类不能拥有静态成员变量,除了声明时不能被实例化,也不能用instanceof,所以匿名类应该尽量保持简介。匿名类主要用于创建函数对象(sort中的comparator)或者过程对象(各种runnable,TimerTask等)

局部类用的比较少,他声明在任何可以声明局部变量的地方。使用它主要要注意作用域。Java 局部类/匿名类

最后附一个静态内部类和非静态成员类的对比:

  • 普通内部类
    • 内部类拥有普通类的所有特性,也拥有类成员变量的特性
    • 内部类可以访问其外部类的成员变量,属性,方法,其它内部类
  • 静态内部类
    • 只有内部类才能声明为static,也可以说是静态内部类
    • 只有静态内部类才能拥有静态成员,普通内部类只能定义普通成员
    • 静态类跟静态方法一样,只能访问其外部类的静态成员
    • 如果在外部类的静态方法中访问内部类,这时候只能访问静态内部类

本文采用创作共用保留署名-非商业-禁止演绎4.0国际许可证,欢迎转载,但转载请注明来自http://thousandhu.github.io,并保持转载后文章内容的完整。本人保留所有版权相关权利。

本文链接:http://thousandhu.github.io/2016/10/08/第四章-类和接口-effective-java/