Effective Java笔记——第4章类和接口

来源:互联网 发布:卫生间垃圾桶 知乎 编辑:程序博客网 时间:2024/06/15 04:51

第13条:使类和成员的可访问性最小化

好处:降低系统模块之间(包之间、类之间)的耦合,使得模块可以独立测试开发,提高可重用性和易维护性

终极目标:尽可能的使每个类或者类的成员不被外接访问。

保护级别和公有的都是类导出的API的一部分,必须永远得到支持,应尽量少用。

注意:
  1、包级私有——声明该成员的包内部的任何类都可以访问这个成员,同时,这也是缺省访问级别,如果没有为成 员指定访问修饰符,就采用这个访问级别。
  2、子类中的访问级别不容许低于超类中的访问级别,以确保任何可以使用超类的地方都可以使用子类。
  3、如果一个类实现了某个接口,那么接口中所有的方法在这个类中必须申明为公有的,因为接口中所有的方法都 隐含着公有的访问级别。
  4、除了包含静态的final的,指向基本类型或者不可变对象的引用的公有域之外,不能有任何公有域。(域是非 final的,或是指向可变对象的final引用,那么一旦是这个域成为公用的,就放弃了对存储在这个域中的值进行限制的能力,就放弃了这个域不可变的能力,包含公有域的类并不是线程安全的。假设常量构成了类提供的整个抽象中的一部分,可以通过公有的静态final域来暴露这些常量。按惯例,这些域的名称由大写字母组成,单词之间用下划线隔开。很重要的一点是,这些域要么包含基本类型和值要么包含指向不可变对象的引用。长度非零的数组总是可变的,类具有公有的静态final数组域,或者返回这种域的访问方法,这几乎总是错误的。)

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

  公有类永远不应该暴露可变的域,让公有类暴露不可变的域其危害比较小。但是,用包级私有或者私有的嵌套类来暴露域是可以的。

第15条:使可变性最小化

不可变类:只是其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期内固定不变。java平台中不可变的类有:String、基本类型的包装类、BigInteger和BigDecimal类。

使类成为不可变,要遵循5条规则:
  1、不要提供任何会修改对象状态的方法
  2、保证类不会被扩展(防止子类化的方法有两种:一是使这个类成为final,二是将类的所有构造函数设为私有的或者包级私有的,并添加公有的静态工厂来代替公有的构造器)
  3、使所有的域都是final的
  4、使所有的域都成为私有的
  5、确保对于任何可变组件的互斥访问

注意:为了提高性能,以上规则可变宽松。比如,许多不可变的类拥有一个或者多个非final的域,它们在第一次被请求执行这些计算的时候,把一些开销昂贵的计算结果缓存在这些域中。如果将来再次请求同样的计算,就直接返回这些缓存的值,从而节约了重新计算的开销,因为对象是不可变的,它的不可变形保证了这些计算如果再次执行,就会产生同样的结果。

不可变对象:
  1、不可变对象本质上是线程安全的,它们不要求同步。
  2、不仅可以共享不可变对象,甚至也可以共享它们的内部信息。
  3、不可变对象为其他对象提供了大量的构件。
  4、不可变对象的缺点是:对于每个不同的值都需要一个单独的对象。

第16条:符合优先于继承

这里说的继承是指实现继承(一个类扩展另一个类),而不是接口继承

对于包内部的继承或者继承专门为了继承而设计并且有很好的文档的类,是安全的。

不推荐的是:跨越包的界限继承普通类,除非你确认子类确实是超类的子类型

跨包继承可能存在的风险可能有:

  1、继承打破了封装性,子类的实现依赖于超类中特定功能的实现细节,但是超类可能在后续版本中获得更新,子类所要求的特定细节就无法保证

  2、超类可能添加新的方法,而使得子类的某些实现依赖的特定条件被打破

  3、超类中可能添加一个与子类中签名相同但返回值不同的方法,从而使得子类不可通过编译

复合:不扩展现有类,而是在新的类中增加一个引用现有类实例的引用。新类的每个实例都可以调用被包含的现有类实例中对应的方法,并返回它的结果,这样的方式叫做“转发”,新的类也叫做“包装类”,除了编码稍有点琐碎,包装类在性能与内存占用上都不会带来很大的影响,但是安全性有了很大提高。

注意:包装类不适合用在回调框架中,在回调框架中,对象把自身的引用传递给其他的对象,用于后续的调用。因为被包装的对象并不知道它外面的包装对象,所以它传递一个指向自身的引用(this),回调时避开了外面的包装对象。

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

好的API文档应该描述一个方法做了什么,而不是描述它是如何做到的。

编写为继承而设计的类,有如下建议:

  1、编写子类进行测试,一般3个子类就足以测试

  2、构造器决不能调用可被覆盖的方法,无论是间接还是直接调用:因为超类的构造器先于子类的构造器运行,如 果在超类的构造器中调用了可覆盖的方法,那么子类中的覆盖方法就会在子类构造器被调用之前被超类构造器提前调用,如若该覆盖方法的执行依赖于子类构造器的某些初始化条件,则难以预期执行。

  3、最好不要实现Cloneable和Serializable接口,因为会把一些实质性的负担转嫁给扩展这个类的程序员,若需如此:由于clone与readObject方法与构造器方法类似,所以他们都不可直接或间接调用可覆盖的方法;如果有readResolve或者writeReplace方法,那应该使他们成为受保护的方法,以避免被扩展该类的程序员忽略。

第18条:接口优于抽象类

接口和抽象类的区别:
     1、抽象类允许包含某些方法的实现,但是接口不允许。
     2、为了实现由抽象类定义的类型,类必须成为抽象类的一个子类。

接口优于抽象类的地方:
     1、现有的类可以很容易被更新,以实现新的接口
    2、接口是定义混合类型的理想选择:比如可比较行为
    3、利用接口的继承可以构造非层次结构的类型框架:比如同时继承歌唱家和作曲家

抽象类较于接口的优势:
    1、 演变更加方便,即如需添加新的方法,那么只需在抽象类中增加方法并给出默认的实现,那么所有继承他的子类也就可以提供这个方法,而对于接口则不行。
    2、接口一旦被公开发行,并且已被广泛实现,再想改变这个接口几乎是不可能的。

    接口通常是定义容许多个实现的最佳途径,除非演变的容易性比灵活性和功能更加重要的时候;若设计了重要的接口,则应坚决考虑提供骨架类。通过对导出的每个重要接口都提供一个抽象的骨架实现类,把接口和抽象类的优点结合起来。
    骨架类的实现:首先,必须认真研究接口,并确定哪些方法是基本的,其他的方法则可以根据他们来实现。这些基本方法将成为骨架实现类中的抽象方法。然后,必须为接口中所有其他的方法提供具体的实现。骨架类实现了重要的接口,同时又是为继承的目的而设计的。

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

常量接口:不包含任何方法,它只包含静态的final域,每个域都导出一个常量。这种做法是不可取的,接口应该只被用来定义类型,它们不应该被用来导出常量。

导出常量的几种正确做法:

  1、如若常量与类或接口密切相关,就把常量写入该类或接口

  2、使用枚举类型

  3、使用不可实例化的工具类(Utility)

第20条:类层次优于标签类

标签类:即一些使用比如type域来区别某个实力的类型。比如使用使用一个标签来区分形状类的实例是圆形还是矩形

坏处:

  1、代码可读性变差:会有很多样板代码,即在类中对不同的type会有不同的代码、type的判断以及枚举等。

  2、增加内存占用:比如矩形实例中也会保存圆形type的东西。

  3、不能定义final域:比如在矩形中,长宽域理应在final的,即应该在构造函数中初始化。但是在圆形中这两个域却又是无用的,但因为它是final又必须初始化,这样就需要借助对type的判断来分别初始化。

  4、构造器不能借助编译器来设置标签域并初始化正确的域:无论你的type是矩形还是圆形,对于编译器来说都是属于同一类的实例,编译器才不知道你的type是啥呢

  5、无法增加type:除非能修改源码,且增加type必须在判断type的所有地方都做相应修改

使用类层次来代替标签类: 

  1、把依赖type值的方法写成abstract方法放到抽象类中。

  2、所有type类型都有的数据域放到上述抽象类中。

  3、不依赖于type标签值的方法,则在上述抽象类中实现。

总之:标签类很少有适用的场合

第21条:用函数对象表示策略

  函数指针的主要用途是实现策略模式。为了在Java中实现这种模式,要声明一个接口来表示该策略,并且为每个具体策略声明一个实现了该接口的类。当一个具体策略只被使用一次时,通常使用匿名类来声明和实例化这个具体策略类。当一个具体策略是设计用来重复使用的时候,它的类通常就要被实现为私有的静态成员类,并通过公有的静态final被导出,其类型为该策略接口。

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

嵌套类:指定义在另一个类的内部的类,嵌套类存在的目的应该只是为了他的外围类提供服务。嵌套类有四种:静态成员类、非静态成员类、匿名类和局部类,非静态成员类、匿名类和局部类称为内部类。

静态成员类:可以访问外围类的所有成员,包括私有的成员。私有静态成员类只有在外围类的内部才能被访问。静态成员类的一种常见用法是作为公有的辅助类,私有静态成员类的一种常见用法是用来代表外围类所代表的对象的组件。如果嵌套类的实例可以在它外围类的实例之外独立存在,这个嵌套类就必须是静态成员类。

非静态成员类:非静态成员类的每个实例都隐含着与外围类的一个外围实例上的方法,或利用修饰过的this构造获得外围实例的引用。当非静态成员类的实例被创建的时候,他和外围实例之间的关联关系也随之被建立起来,而且这种关系以后不能被修改。非静态成员类的一种常见用法是定义一个Adapter。

匿名类:当嵌套类属于一个方法的内部,且只需在一个地方创建实例且有一个预置类型可以说明这个类的特征

成员类:如果一个嵌套类需要在方法之外仍然是可见的,或者太长以至于不适合放在方法内部,就应该使用成员类

注意:如果声明成员类不要求访问外围实例,就要始终把static修饰符放在它的声明中,使它成为静态成员类。如果省略了static修饰符,则每个实例都包含一个额外的指向外围对象的引用。保存这份引用要消耗时间和空间,并且会导致外围实例在符合垃圾回收时却仍然得以保留。





0 0
原创粉丝点击