如何理解Java8中的接口和抽象类

96
_Zy
0.1 2018.05.29 14:30* 字数 3159


本文结合Java8,重新整理了接口和抽象类的区别,以及如何理解接口的设计目的。
并且分析了Java8的接口新特性:default method 和 static method

前言

在Java中,提供了两种方式来实现OOP的抽象。接口抽象类
在程序设计的时候,通常建议实现接口要优于类继承。也就是尽量面向组合编程。
所以在很多时候,我们需要对接口和抽象类之间做出比较和选择。

本文主要讲了以下几点:

  1. 接口和抽象类的设计理念
  2. 接口和抽象类的异同
  3. Java8中接口的新特性
  4. Java9接口 private method前瞻



设计理念

接口

接口的设计主要是为了支持运行时动态方法解析
由于动态方法解析都是是需要别的类去实现或重写,所以接口的使用并不需要创建自己的实例,因此为了保证单一工作而废除了构造器。当用户执行某个接口方法时,只需要用接口引用去指向对应的一个实现类即可达到动态方法的目的。

接口被设计为支持运行时动态方法解析。通常情况下,为了能够从一个类中调用另外一个类的方法,在编译时这两个类都需要存在,进而使Java编译器能够进行检查以确保方法签名是兼容的。这个要求本身造成了一个静态的并且是不可扩展的类系统。
对于这类系统,在类层次中,功能不可避免地被堆积得越来越高,导致整个机制中的子类越来越多。
设计接口的目的就是为了避免这个问题。接口断开了一个方法或一系列方法的定义与继承层次之间的关联。由于接口在不同的类层次中,因此就类层次而言,不相关的类可以实现相同的接口。这是接口的功能所在。
——摘自《Java8编程参考官方教程》


抽象类

抽象方法必须用 abstract 关键字修饰,如果一个类含有抽象方法,那么这个类就是抽象类,类也必须用 abstract 关键字来修饰。由于抽象类有未实现的方法,所以抽象类无法被实例化。(但是可以有构造器)

在一个没有良好设计的项目中,可以不存在任何抽象类。塔本来就不是一个必须要存在的东西。但当开发者特别注意代码的质量与架构设计,希望尽量少的向下级用户暴露封装好的信息时,抽象类就起到了独一无二的作用。

本质上,抽象类的设计主要是保护一个类不被实例化,或者允许一个类包含未实现的抽象方法。而接口的设计主要是为了多态。

为什么应该选择接口实现多态

举个栗子,任何一个类,都可以写一个close()方法,让内部或者外部来调用这个方法,而不用实现Closeable接口。
什么时候调用方要调用close()方法?只有当调用者根本不知道调用的具体是什么类,而只知道调用是一个Closeable的close()方法时,也就是说,在多态的情况下。

一个抽象类或者父类引用,同样可以代表不同的子类。但是这里存在两个问题:
1)需要被多态调用的子类,必须是这个家族谱系的,不能是其他家族的。这在单继承下,会有很大的限制,属于强耦合的方式
这个问题表明必须要有接口,事实上,在Java中,文件级作用域也只能看到class和interface。

2)暴露了过多的东西给调用方,如果指定为Closeable接口,只需要暴露close()方法就可以。从这一点可以看出,接口更适合多态,因为它只暴露了必要的method,符合Java的哲学。



接口和抽象类的相同和区别

相同点:

1)都可以定义方法和属性。

2)都可以看做是一种特殊的类,它们被设计出来的目的,就是要让子类实现其中定义的抽象方法。

3)都可以不含有抽象方法。不含有抽象方法的接口可以作为一个标志(比如可序列化的接口Serializable)。

4)都不能被实例化。

5)实现了接口的类或者继承了抽象类的子类,都必须实现定义的 抽象方法。如果存在没有实现的方法,那么该类必须声明为抽象类。

6)都实现了多态。通过方法的动态解析特性,在运行时动态调用实现类的方法。


不同点:

1)继承
接口可以多实现,但是接口只能继承接口。
但是抽象类只能单继承,但是抽象类可以继承普通类,也能继承抽象类。

2)方法
抽象类方法的访问修饰符可以是任意的,并且方法可以是抽象的,也可以不是抽象的。
接口定义的方法默认是 public abstract 的(可以不用显示声明)
重点:在Java8中,接口新增了default 修饰的默认实现方法,和static 声明的静态方法。这两种方法不是抽象的。
关于default 方法 和 static 方法,接下来会细讲。

3)类变量
抽象类变量可以有任意的变量修饰符。
接口的类变量默认并且强制是 public static final。(可以不用显式声明)。实现类可以继承使用,但不能对接口变量覆盖。

4)静态方法和代码块
抽象类可以有静态代码块,也可以有静态方法,静态方法访问修饰符也没有限制。
接口中不允许有静态代码块,接口静态方法默认并且强制是 public的。并且静态方法不能被继承和实现。

5)构造器
抽象方法可以有构造器。
接口不能有构造器。

6)设计理念
抽象类
对类的抽象。是对同一个类型下共有的特性抽象。是作为一系列子类的模板设计。一般用于定义一系列类的公有功能和操作。而留下抽象方法给子类,去实现子类独有的特性。
抽象类是一种 自下而上 的设计,先有了子类,然后提取子类公有的特性与行为,构成抽象类。
抽象类与子类是 is-a 关系,父类和派生的子类在概念上是相同的,存在的是父子关系,是一种重耦合。

接口
对行为的抽象。它定义了一种规范,更多的是辐射类型的设计。一般用于延伸类的行为方式。
接口是一种 自上而下 的设计,先规定行为方法,然后由类去实现这些行为,就可以成为接口的实现类。
接口与实现类是 like-a 关系,接口与实现类的关系只是定义了行为,本质上并无实质关系,只是契约层面的关系。


总结如下

比较 抽象类 接口
默认方法 抽象类可以有默认的方法实现 java 8之前,接口中不存在方法的实现.
实现方式 子类使用extends关键字来继承抽象类.如果子类不是抽象类,子类需要提供抽象类中所声明方法的实现. 子类使用implements来实现接口,需要提供接口中所有声明的实现.
构造器 抽象类中可以有构造器, 接口中不能
和正常类区别 抽象类不能被实例化 接口则是完全不同的类型
访问修饰符 抽象方法可以有public,protected和default等修饰 接口默认是public,不能使用其他修饰符
多继承 一个子类只能存在一个父类 一个子类可以存在多个接口
添加新方法 想抽象类中添加新方法,可以提供默认的实现,因此可以不修改子类现有的代码 如果往接口中添加新方法,则子类中需要实现该方法.



Java8 的接口增强

在Java8中,接口增加了两个新特性:默认方法静态方法

1)默认方法
只能通过接口实现类的对象调用。
Java8允许给接口添加一个非抽象的方法实现。方法使用 default 修饰符修饰,使得接口定义的方法可以有默认实现。
定义了 default 的方法,不需要强制继承了接口的子类实现。子类不实现的话,直接调用该方法会执行接口中定义的默认方法。

Java8接口上的 默认方法 最初的设计目的是让已存在的接口可以演化——添加新方法,而不需要原本已经实现了该接口的类做任何改变(甚至不需要重新编译)就可以使用新版本的接口。
不过从Java8的设计主题来看,默认方法 确实是为了配合JDK标准库的函数式风格而设计的。通过默认方法,很多JDK里原有的接口都添加了可以接收FunctionalInterface参数的方法,使它们更便于以函数风格使用。

但是这样会出现一个特殊情景,如果一个类继承了多个接口,并且这些接口都包含统一的默认方法,那么该类就必须显示使用 @override 实现该方法,否则会编译报错。(相当于又引入了多继承导致的问题——这是在开历史的倒车啊)

2)静态方法
只能通过接口调用。
Java8 中,接口可以使用 static 关键字修饰一个方法,并且提供实现。被称作接口静态方法。
静态方法默认,并且强制是 public 的。

要说到接口的静态方法,我们首先需要回忆一下类的静态方法。

静态方法属于类对象,而不是属于实例对象。虽然静态方法可以用实例来调用,但我们更建议用类来调用,也不能重写。
静态方法不能调用实例中的成员变量或者方法,只能调用类的成员变量和静态方法。

在我们实际开发中,我们会有两种类Utils,和Constants。这两种类的作用是定义静态的工具方法和静态常量。这些东西本来不属于任何一个类,类名在这里其实是起到了命名空间的作用。

但是我们有时候并不希望这些类被实例化,或者说被继承。那我们就要额外的写一个私有的构造器,在类上面定义final。

在Java8中,接口提供了静态方法,那么使用接口来定义这些东西,就可以作为一个新的思路。(其实在Java8之前,用接口定义常量的思路就已经存在了)



Java9 的 private method改变了什么

在Java9中,接口再次增强,可以实现 private methodprivate static method

其实,这样的设计思路很简单。
既然可以写代码了,那么是不是有一些共用的代码需要封装、又不希望暴露给外部?那就加 private 吧。以前只有抽象方法的时候没有这个需求。而Java8增加了 static method 和 default method 之后,没有一起增加这个功能,简直是设计上的缺失!

在有了这两个以后,接口和抽象类的区别,只剩下的构造器,成员变量,以及单继承。



(如果有什么错误或者建议,欢迎留言指出)
(本文内容是对各个知识点的转载整理,用于个人技术沉淀,以及大家学习交流用)


参考资料:
Java8抽象类与接口的区别
Java8新特性下接口和抽象类的异同
浅谈Java8引入的接口默认方法
知乎——为什么java8接口要扩展default方法
深入理解Java接口和抽象类
Java中接口和抽象类设计理念的差异
Java8新特性,接口的默认方法和静态方法

Core Java
Web note ad 1