您的位置:首页 > 编程语言 > Java开发

JAVA中的接口到底是不是类?

2016-10-23 16:20 246 查看

背景

最近我所在的java培训的地方的课程进度到了抽象类、接口的部分。那里上课的顺序是:类→抽象类→接口。这样的上课顺序就很自然的让我想到了一个问题:接口到底是不是类?

看上去接口只是为了解决java单根继承的问题而产生的一种特殊类,这两者太像了,像到我觉得这两者根本不是一种东西——如果是这样的话java没有必要弄一种接口类出来。

做了一些搜索查找后,我个人的结论倾向于:接口不是类。

(学习蛤乎,先上结论)

以下所有内容均为本人自己的理解,如果有哪位大手子觉得哪里有问题请务必告诉我,这篇文章的目的就是讨论。

过程

首先,我翻了一下《java核心技术 卷I》第九版的接口部分,上面很明确的表示:接口不是一种类,而是对类的一组需求描述。然后表示会在后面详细再说为什么不是类。结果我往后翻,发现后面只是详细的讲了类和接口的表观区别,没有讲更本质的东西。

接着我百度了一下:”接口是不是类”和”接口的意义”(为什么不谷歌?我英语很水…),百度知道的答案直接跳过不看后,前者的搜索结果有若干博客,和两个CSDN上的帖子,后者的搜索结果内有一个知乎的相关问题和若干博客。

博客上的东西大部分是搬运的,看来看去都一样,也没有讲到核心的东西;CSDN帖子中也各分两派,有认为是特殊抽象类的,有认为不是类的;知乎上的那个问题也只是讲了接口的意义,而没有人提接口和抽象类有没有本质的区别。

第二天上课,我询问了老师,老师认为接口是特殊的抽象类——之前教接口的时候他也是这么说的。

老师的一个重要观点就是,当使用类实现接口时:

interface 接口A{
方法a{};
}

class 类B implement 接口A{
方法a{
语句;
}
}

接口A a = new 类B();
a.toString();//不会报错


最后的输出都不会报错,而java中实现接口时,调用的方法必须是接口A里面存在的,不然无法通过编译,所以接口A应该是一个特殊的类,它继承了object的相关方法。不然调用toString()时应该无法通过编译。

我的理解

显然,我关注的并不是“接口使用interface声明、抽象类使用class声明;接口只有常量和抽象静态方法(java8可以声明非抽象方法了)、抽象类可以有非抽象方法;接口可以多实现、类只能单继承”这样的表观区别,我想问的是,这两个到底本质上一不一样。

有这么一篇02年的文章:深入理解abstract class和interface,如果你直接搜这个标题你会发现一大堆盗这篇文章的博客和网站(我顺手举报了几个恬不知耻标着原创的博客)。因为这篇文章确实讲的很好。看完这篇文章,就能理解到java的作者为什么将抽象类和接口这两个看上去很像的东西拆分开;尤其是在java8中开始允许接口中声明非抽象的静态方法了——这使得接口和静态类更像了。

但这并不能满足我,当我看完那篇文章后,我知道了这两个东西为何要这么设计,领会到了一点java作者的设计思想。这么像的两个东西却没有合并反而一直保留了下来,还在不断改进,显然这两个东西绝不是一个东西,不然没必要这么设计。

那么那个toSting()方法的例子怎么解释?我找到了答案,但我最后再解释,这篇文章的目的是交(打)流(脸),我必须把我自己的理解写出来,否则达不到交流的意义,所以以下是我对于接口和抽象类表观区别和思想区别的理解,如果哪位路过的大手子觉得有问题请务必留言打脸,我好学到姿势。

语法区别

这一点需要纠结的地方不多,照本宣科即可:

接口抽象类
通过interface声明通过abstract修饰符声明某个类为抽象类
只有静态抽象方法(java8可以有非抽象默认方法)可以没有抽象方法
只有公有常量无限制
一个类可以实现多个一个类只能继承一个
只能继承接口可以实现接口和继承其他类
但即使是能写出一堆形式上的区别,这两个东西还是很像。

思想区别

那么为啥这两个明显很像的东西还要留着,显然是有用的。

为什么会有类继承和接口实现?只是为了省代码?

面向对象的一个重要思想就是将一类东西的共性提取出来,写成类的方法和属性,转而直接操作类对象。从一堆子类中抽象出公有的属性然后写一个共同的父类看上去也很方便。

比如我建一个学生类、一个老师类、一个工人类。而这堆类有一个共同父类,人类。

class Person{
性别,年龄
呼吸、走路、跑步
}

class Student extends Parson{
逃课
}

class Teacher extends Parson{
开会
}

class Worker extends Parson{
加班
}


人类有性别、年龄、等属性,有走路、跑步、呼吸等方法。在描述每个类时只需要为各个子类实现自己的方法即可(逃课、开会、加班什么的)。而在开发中这样可以节省大量的代码。

然而并不是,抽象类和接口哪里省代码了?反正每个子类还要再写一遍,倒不如说多写了抽象类和接口里的那部分代码。当然,抽象类和接口的好处是显而易见的:在实际开发中,可以限制子类的写法,有利于合作开发、可以封装、隔离大量的子类,使程序条理清晰、抽象类解决了多继承带来的一系列问题、接口解决了java的单根继承的限制……

但这不是重点,面向对象开发的本质是将需要解决的问题抽象为一个个对象,将这些对象的共性抽象为一个个类。而抽象类呢?抽象类是类的抽象,是抽象的抽象。抽象类描述的是类的共性。

比如我建一个圆形类、一个方形类、一个椭圆,它们都是封闭图形类,封闭图形必然有面积、周长,只是没有一个共通的方法(提微积分的泥奏凯)求解其面积和周长,于是这几个类可以写成这样。

class Figure{
周长、面积
}

class Round extends Figure{
周长、面积
}

class Square extends Figure{
周长,面积
}

class Ellipse extends Figure{
周长、面积
}


看吧,根本不省代码。那么为什么要写成这样?虽然说抽象类实际上用起来的效果是,在合作开发时提供一个强制的写法要求。

“比如我建一个圆形类、一个方形类、一个椭圆,它们都是封闭图形类,封闭图形必然有面积、周长,只是没有一个共通的方法去求”这里面隐含了这样的一种思路:是因为子类有重复的方法和属性才抽象出一个公有的父类的。

“因为圆、方形、椭圆都有面积周长,所以抽象出一个有面积和周长的图形类。”你不觉得这段话哪里有问题?

不是因为圆有面积周长所以它才是图形,而是因为它是图形所以它才有面积周长!

不是因为圆有面积周长所以它才是图形,而是因为它是图形所以它才有面积周长!

不是因为圆有面积周长所以它才是图形,而是因为它是图形所以它才有面积周长!

父类,或者说抽象类,抽象自子类的共性,这给人一种父类或抽象类是从子类中诞生的感觉。但他们的从属关系一直都是:子类继承父类。抽象类描述了这群子类的共性,这群类派生自抽象类才是正确的。面向对象开发的抽象性正体现于此

那接口是什么?接口也是抽象了一堆类共有的属性,但接口与类并不存在谁继承谁的关系,这两者之间是平行的。两个类有大量的共同的方法,并不代表它们就可以抽象出同一个父类来,而接口不受此限制,有同样的方法即可。

(python的类鸭子属性比这个好用也好理解…虽然效果差不多,不过配上python的动态方法后简直神器)

《深入理解abstract class和interface》中的防盗门和报警器的离子简直破费(perfect)。我深觉得自己举的例子太辣鸡。我还是用这里面的例子吧。

一扇门,有打开和关闭这两个方法

class abstract Door{
打开;关闭;
}


一扇有报警功能的防盗门,除了有打开、关闭外,还有一个报警方法

class SecurityDoor{
打开;关闭;报警;
}


那么我们现在来看一个报警器,显然,报警器也有打开、关闭、报警这三个方法

class Alarm{
打开;关闭;报警;
}


那么防盗门和报警器能方便的抽象出一个父类吗?显然不能,逻辑上讲这两个东西要一直到安防物品类才能扯上关系,并且也不是所有安防物品都有开关报警方法。

我再建一个方法

interface 报警{
报警;
}


那么我如果要让防盗门继承门这个父类,写成这样是合乎逻辑的。

class SecurityDoor extents Door implement 报警{
报警{call 110}
}


当然我要是建一个抽象类,我不知道它是个什么鬼东西,但这个类就是有开关报警功能

class abstract SomeThing{
打开;关闭;
报警;
}


然后让防盗门继承这个类,并实现方法

class SecurityDoor extents SomeThing{
打开;关闭;报警;
}


这似乎也行,但这并不合乎逻辑,至少相比继承门类然后实现报警方法来讲是不合逻辑的。这种不合逻辑就体现了面向对象开发的一个核心思想,抽象。同时这也体现了我的设计思想:门能开关是门类的固有属性,而报警则是我另外需要实现的方法。反过来能不能写?我的防盗门是一个有(门的)开关属性的报警器,这样可以,但这不合乎一般逻辑。

抽象类和接口的区别就在于此,而java作者保留这两个很像的东西东西的意图也在于此:同一类东西一定有一些共性,但有共性的东西并不一定就是一类,但面向接口的开发,只需要看你有没有接口对应的方法即可,至于你是哪个类,与我无关。作者希望开发者在使用面向对象时能区分出类和共性的区别,并按照实际情况来决定,是使用一个接口还是一个抽象类。这才是真正的面向对象。

抽象类是一类“类”的抽象,而接口则是一个或一类“类”应当具有的方法。这两者在面向对象开发的思维上是不能混用的,实际上你非要混用也没人拦得住你,然而接口确实不是类。不然SUN最初便不需要设计接口这种东西,一个完全抽象类就足够替代接口了。

最初的问题

interface 接口A{
方法a{};
}

class 类B implement 接口A{
方法a{
语句;
}
}

接口A a = new 类B();
a.toString();//不会报错


为什么这个方法不会报错?

oracle出版的The Java Language Specification(java编程规范)中关于接口的部分有这么一段:

If an interface has no direct superinterfaces, then the interface implicitly declares a public abstract member method m with signature s, return type r,and throws clause t corresponding to each public instance method m with signature s, return type r, and throws clause t declared in Object, unless a method with the same signature, same return type, and a compatible throws clause is explicitly declared by the interface. It is a compile-time error if the interface explicitly declares such a method m in the case where m is declared to be final in Object.

(如果一个接口没有直接的超接口,那么接口会隐式地声明一个具有签名s,返回类型r和throws子句t的公共抽象成员方法m。它对应于Object中声明的具有签名s,返回类型r和throws子句t 的每个公共实例方法m,除非该接口显示的声明具有相同签名,相同返回类型和兼容throws子句的方法。 如果接口在对象中声明为final的情况下显式声明了这样的方法m,那么这是一个编译时错误。)

这解释了为什么实现接口的方法在调用接口中并不存在的object方法时不会因通不过编译时多态而报错:接口中已经隐式的声明了object的所有方法,如果接口中显式的声明了object的最终方法的话,就会报错。

然而这看起来和普通的类继承很像?那么是不是可以认为接口其实是特殊类?

然而接口并不能继承任何类

interface 接口名 extends object


这种写法都是不被允许的,接口只能继承接口。

而且这段描述使用了“隐式的声明了……”这种绕口的描述。显然的,接口中的toString并不是继承自object类,而是被JVM自动添加上的,这是为了不出现编译时多态的错误,而这种绕远路的描述方法则正是接口不是类的的证据。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 继承 接口