【Java学习】接口(interface)的一些理解和要点

本文探讨了Java中接口的重要性和使用,包括接口作为类的抽象,接口的特性,如不能实例化,以及Java 8引入的默认方法和静态方法。强调了接口在解决多继承问题和描述类之间关系上的作用,同时提到了接口与类的区别以及继承冲突的解决策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0 前言

重新回归一下Java中的接口,有了一些更进一步的了解,也整理了一些要点。
在理解过程中,我试着用生活中的接口去解释Java中的接口机制。 个人觉得还OK。
考虑现实,服务提供方把若干功能集合到接口中对客户开放,并生产若干设备实现这些接口里的功能。在Java中,我们把使用该接口的类看做客户,实现接口的类看做实现了该接口功能的设备。可以很方便的解释一些问题。
正如客户通过接口执行功能,我们总是用接口声明一个变量。接口的功能必须通过设备实现,所以接口不能实例化,而应该将实现了该接口的类实例赋给接口变量。

1 为什么需要接口

Java中用接口来描述类具有的特性和功能。

一旦一个类实现了一个接口,它就具有了该接口的所有属性和行为。
【疑问】继承机制已经很好地实现了属性和行为的继承,为什么还需要接口?
【回答】确实可以完全只用继承实现,而且我们也看到C++就是这么做的。不过,很多时候一个类的特性是比较复杂且在不同超类中,那么就有了广为诟病的多继承。Java是从C++发展过来的,既然大家都不喜欢多继承,Java就不允许多继承,而引进接口这一概念,一个类支持继承多个接口,也就可以继承各种复杂特性。在我们现在看来,

Java不支持多继承,这就是我们不得不使用接口的原因。

但是,引进接口这一概念的初衷是什么?为了更好地描述类以及它们之间的关系
看个例子,神话中经常出现会飞翔的马,俗称飞马(pegasus)。在程序中如何去描述这个Pegasus类?

  • 多继承:定义两个超类,Bird类和Horse类,Pegasus类实现它们。
  • 引入接口:定义一个超类Horse和一个接口Flying, Pegasus继承Horse并实现Flying。

回顾一下,继承表示“is-a”(是一个)关系。那么,上面多继承的方案就是说飞马既是一种鸟,又是一种马。但事实上,我们的理解都是飞马是一种特殊的马,它具有飞翔的特性。引入接口后的实现完美地贴合了我们的理解。
基于这点,我不是很赞成“Java中的接口相当于C++中的虚类”的说法,应该说成:

接口是虚类的发展

当然,虚类还是需要的,但是很多在C++上用虚类实现的在Java中都更应该采用接口。
在Java中,什么时候用继承超类,什么时候用实现接口,在于程序员的理解。继承是一种特化,而实现是指具有某种特性。当然,如果你已经有一个超类了,其它的你只能用接口实现。

2 接口的特性

2.1 接口就是类

接口与类的定义,二者的语法框架及其相似。
除了语法形式的相似外,还有几个理由支持这种说法:

  • 可以用接口声明一个变量,并把实现了这个接口的类实例赋值给它。
  • 公有接口也必须与文件名一致,且一个Java文件只能有一个公有接口。
  • 接口在编译后也会形成同名.class文件。

2.2 接口是特殊的类

接口与普通类最大的不同在于:不能实例化一个接口对象,当然虚类也是一样。那接口就是虚类吗?当然不是,接口要更特殊,表现在:

接口的成员必须是公有静态常量(public static final)和公有(public)函数。

为什么有这种限制?既然你提供给别人某个接口,你就没有理由限制对方使用该接口的某项功能。public保证所有能引用该接口的类都能访问接口的所有函数。因为接口不能实例化,所以成员变量必须是静态(static)的。又为了防止某个实现了该接口的类擅自改动该属性的值,属性必须是不可变的(final)。

成员变量的作用是提供具有该接口功能的所有类的共有属性,且这个属性值对所有的类都是唯一确定的。

假设你想为Flying接口添加一个highest属性限制飞行的最高可达高度,因为highest必须是静态常量,你只能设定一个值表示所有飞行物的可达高度。如果你认为不同飞行物的可达高度不同,希望不同的类有不同的值,用继承吧。
因为这个限制,接口中的成员限定符都是唯一确定的,出于简洁的思想:

Java鼓励省略接口成员变量前的public static final 和函数前的public限定符。

【注意】虽然接口中的成员都是public的,但是并非所有的类都可以访问它们。如果一个接口没有定义为public,则包外的类无法访问它,更别提它的成员了。

2.3 接口的新特性

2.3.1 默认函数

引入接口后,我们要清楚:

我们无法预测程序员会用调用该接口的哪一个函数,所以必须保证每个方法都有实现。

一开始,Java规定,实现了某接口的类必须重写该接口的所有方法,至少要写个空壳。可是很多时候我们只需要其中的某一个功能,比如事件监测时,我们只关系个别特定的事件,这样就导致类体臃肿而且麻烦。于是,Java SE 8 引进了默认函数,允许在接口定义时给函数一个默认的实现,没被重写的函数则执行默认内容。这样,当接口中的某个方法执行内容比较固定时,我们也无需在各个类中重复同样的实现。现在,我们说:

实现某个接口的类必须重写该接口的所有非默认方法。

使用默认函数的另一个好处是支持接口演化。当接口A增加一个非默认函数时,任何实现了该接口的类都要重写新增函数,而增加一个默认函数却不需要任何改动。

2.3.2 静态函数

当某个接口的子类对象对某个接口方法都具有相同实现时,因为接口必须由类实现,所以必须定义各类来实现接口,又方法实现对所有对象都是一样的,所以这个方法应该定义为静态方法。这个类称之为接口的伴随类。Java中有很多伴随类,如Collections类是Collection接口的伴随类。Java SE 8支持接口的静态方法,可以通过接口直接调用该方法,也就不用再麻烦构建伴随类了,当然之前建成的伴随类也不值得再重构。

2.4 继承冲突

当类从父类和接口中继承来的方法签名一致,而且子类没有重写该方法时,编译器不知道该用哪个实现,就发生了继承冲突,编译会报错。冲突避免策略如下

  1. 父类优先。如果父类的函数与接口的默认函数冲突,采用父类中的实现。
  2. 两个接口中的方法冲突时,必须重写子类方法。

3 总结

  • 一个类实现了一个接口,表示该类具有该接口的功能。
  • 一个类实现了一个接口,外部可能用该类的实例调用该接口的任何方法,所以必须保证该方法有明确且唯一定义,体现在接口中的非默认方法必须被重写,默认方法之间不产生二义。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值