前言
这个系列记录我读完Java编程思想后整理的干货笔记。
为什么要整理呢?因为原著作者讲过于详细了,而且写得有点“随性”,往往是想到哪讲到哪。这对于新手的阅读就不太友好,因为你往往抓不住重点,文字会因为作者的笔风(加上英文翻译造成的语言习惯问题)而感到晦涩难懂。所以我把它讲的东西根据我的理解给整理出来,方便更多人的阅读和反复查阅。
一、抽象类和方法
1.抽象类的目的
为了通过通用接口操纵一系列类。
2.抽象类定义
包含抽象方法的类叫做抽象类。
3.抽象类要注意的几点
抽象方法是不完整的:它只有声明没有方法体。
如果一个类包含一个或多个抽象方法,那么类本身也必须限定为抽象的,否则,编译器会报错。
如果一个抽象类是不完整的,当试图创建这个类的对象时,它不会创建抽象类的对象,所以我们只会得到编译器的错误信息。
如果创建一个继承抽象类的新类并为之创建对象,那么就必须为基类的所有抽象方法提供方法定义。
可以将一个不包含任何抽象方法的类指明为 abstract,在类中的抽象方法没啥意义但想阻止创建类的对象时,这么做就很有用。
为了创建可初始化的类,就要继承抽象类,并提供所有抽象方法的定义,在这个过程中如果没有@Override这个注解的话并且你没有定义相同的方法名或签名,抽象机制会认为你没有实现抽象方法从而产生编译时错误。
private abstract 被禁止了是有意义的,因为你不可能在 AbstractAccess 的任何子类中合法地定义它。
接口自动将其方法指明为 public。事实上,接口只允许 public 方法,如果不加访问修饰符的话,接口的方法不是 friendly 而是 public。
二、接口创建
1.接口定义
和抽象类类似,都是为了通过通用接口操纵一系列类,使用 interface 关键字创建接口。
2.使用接口需要注意的点
我们不用为方法加上 abstract 关键字,因为方法在接口中。Java 知道这些方法不能有方法体(仍然可以为方法加上 abstract 关键字,但是看起来像是不明白接口,徒增难堪罢了)。
Java 8 允许接口包含默认方法和静态方法
和类一样,需要在关键字 interface 前加上 public 关键字(但只是在接口名与文件名相同的情况下),否则接口只有包访问权限,只能在接口相同的包下才能使用它。
接口同样可以包含属性,这些属性被隐式指明为 static 和 final。
使用 implements 关键字使一个类遵循某个特定接口(或一组接口)。
你可以选择显式地声明接口中的方法为 public,但是即使你不这么做,它们也是 public 的。
3.默认方法
目的
当在接口中使用它时,任何实现接口却没有定义方法的时候可以使用 default 创建的方法体。
它允许在不破坏已使用接口的代码的情况下,在接口中增加新的方法。默认方法有时也被称为守卫方法或虚拟扩展方法。
使用注意点
关键字 default 允许在接口中提供方法实现——在 Java 8 之前被禁止。
4.多继承
定义
即一个类可以继承多个接口
需要注意的点
两个接口有相同的方法名但是签名不同——方法签名包括方法名和参数类型,编译器也是用它来区分方法。为了解决这个问题,需要覆写冲突的方法。
5.接口中的静态方法
目的
Java 8 允许在接口中添加静态方法。这么做能恰当地把工具功能置于接口中,从而操作接口,或者成为通用的工具。
三、抽象类和接口的区别
接口与抽象类最明显的区别可能就是使用上的惯用方式。接口的典型使用是代表一个类的类型或一个形容词,如 Runnable 或 Serializable,而抽象类通常是类层次结构的一部分或一件事物的类型,如 String 或 ActionHero。
特性 | 接口 | 抽象类 |
组合 | 新类可以组合多个接口 | 只能继承单一抽象类 |
状态 | 不能包含属性(除了静态属性,不支持对象状态) | 可以包含属性,非抽象方法可能引用这些属性 |
默认方法 和 抽象方法 | 不需要在子类中实现默认方法。默认方法可以引用其他接口的方法 | 必须在子类中实现抽象方法 |
构造器 | 没有构造器 | 可以有构造器 |
可见性 | 隐式 public | 可以是 protected 或友元 |
使用建议
除非必须使用,否则不要用接口和抽象类。大多数时候,普通类已经做得很好,如果不行的话,再移动到接口或抽象类中。
四、使用接口(抽象类)的好处——完全解耦
当方法操纵的是一个类而非接口时,它就只能作用于那个类或其子类。如果想把方法应用于那个继承层级结构之外的类,就会触霉头。接口在很大程度上放宽了这个限制,因而使用接口可以编写复用性更好的代码。
五、接口的使用技巧
1.多接口结合
一个类可以有任意多个接口,并可以向上转型为每个接口,因为每个接口都是独立的类型。
需要注意的点
完全相同的方法没有问题,但是如果它们的签名或返回类型不同,覆写、实现和重载令人不快地搅和在一起带来了困难。同时,重载方法仅根据返回类型是区分不了的。
当打算组合接口时,在不同的接口中使用相同的方法名通常会造成代码可读性的混乱,尽量避免这种情况。
2.使用继承扩展接口
通过继承,可以很容易在接口中增加方法声明,还可以在新接口中结合多个接口。
3.接口适配
我们可以再次使用适配器模式,但这里适配器类可以实现两个接口。因此,通过关键字 interface 提供的多继承,我们可以创建一个既是 RandomDoubles,又是 Readable 的类,因为你可以以这种方式在已有类中增加新接口,所以这就意味着一个接受接口类型的方法提供了一种让任何类都可以与该方法进行适配的方式。这就是使用接口而不是类的强大之处。
4.接口字段
因为接口中的字段都自动是 static 和 final 的,所以接口就成为了创建一组常量的方便的工具。注意 Java 中使用大写字母的风格定义具有初始化值的 static final 变量。接口中的字段自动是 public 的,所以没有显式指明这点。
自 Java 5 开始,我们有了更加强大和灵活的关键字 enum,那么在接口中定义常量组就显得没什么意义了。然而当你阅读遗留的代码时,在很多场合你还会碰到这种旧的习惯用法。
5.接口嵌套
接口可以嵌套在类或其他接口中。
添加这些特性的最初原因看起来像是出于对严格的语法一致性的考虑,但是我(作者)通常认为,一旦你了解了某种特性,就总能找到其用武之地。
6.接口和工厂方法模式
接口是多实现的途径,而生成符合某个接口的对象的典型方式是工厂方法设计模式。不同于直接调用构造器,只需调用工厂对象中的创建方法就能生成对象的实现——理论上,通过这种方式可以将接口与实现的代码完全分离,使得可以透明地将某个实现替换为另一个实现。
六、本章小结
任何抽象性都应该是由真正的需求驱动的。当有必要时才应该使用接口进行重构,而不是到处添加额外的间接层,从而带来额外的复杂性。这种复杂性非常显著,如果你让某人去处理这种复杂性,只是因为你意识到“以防万一”而添加新接口,而没有其他具有说服力的原因——好吧,如果我碰上了这种设计,就会质疑此人所作的所有其他设计了。
恰当的原则是优先使用类而不是接口。从类开始,如果使用接口的必要性变得很明确,那么就重构。接口是一个伟大的工具,但它们容易被滥用。