文章目录
- 接口的理解
- 接口的声明
- 接口的成员说明
- 接口的使用规则
- 类实现接口(implements)
- 接口的多实现
- 接口的多继承(extends)
- 接口与实现类对象构成多态引用
- 使用接口的静态成员
- 使用接口的非静态方法
- JDK8中相关冲突问题
- 总结
- 小测试
- 接口与抽象类之间的对比
- 练习
接口的理解
生活中大家每天都在用USB接口,那么USB接口与接口有什么相同点呢?
其实,不管是电脑上的USB插口,还是其他设备上的USB插口都只是遵循了USB规范
的一种具体设备而已。
只要设备遵循USB规范的,那么就可以与电脑互联,并正常通信。至于这个设备、电脑是哪个厂家制造的,内部是如何实现的,我们都无需关心。
Java的软件系统会有很多模块组成,那么各个模块之间也应该采用这种面向接口
的低耦合
,为系统提供更好的可扩展性和可维护性。
-
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的is-a关系,而接口实现则是 "能不能"的
has-a
关系。 -
例如:Java程序是否能够连接使用某种数据库产品,那么要看该数据库产品能否实现Java设计的JDBC规范
接口的本质是契约、标准、规范,就像我们的法律一样。制定好后大家都要遵守。
接口的声明
[修饰符] interface 接口名{
//接口的成员列表:
// 公共的静态常量
// 公共的抽象方法
// 公共的默认方法(JDK1.8以上)
// 公共的静态方法(JDK1.8以上)
// 私有方法(JDK1.9以上)
}
示例代码:
public interface USB3{
//静态常量
long MAX_SPEED = 500*1024*1024;//500MB/s
//抽象方法
void in();
void out();
//默认方法
default void start(){
System.out.println("开始");
}
default void stop(){
System.out.println("结束");
}
//静态方法
static void show(){
System.out.println("USB 3.0可以同步全速地进行读写操作");
}
}
接口的成员说明
在JDK8.0 之前,接口中只允许出现:
(1)公共的静态的常量:其中public static final
可以省略
(2)公共的抽象的方法:其中public abstract
可以省略
理解:接口是从多个相似类中抽象出来的规范,不需要提供具体实现
在JDK8.0 时,接口中允许声明默认方法
和静态方法
:
(3)公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略
(4)公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略
在JDK9.0 时,接口又增加了:
(5)私有方法
除此之外,接口中没有构造器,没有初始化块,因为接口中没有成员变量需要动态初始化。
接口的使用规则
类实现接口(implements)
接口不能创建对象,但是可以被类实现(implements
,类似于被继承)。
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements
关键字。
【修饰符】 class 实现类 implements 接口{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
【修饰符】 class 实现类 extends 父类 implements 接口{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
注意:
-
如果接口的实现类是非抽象类,那么必须
重写接口中所有抽象方法
。 -
默认方法可以选择保留,也可以重写。
重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了
-
接口中的静态方法不能被继承也不能被重写
举例:
interface USB{ //
public void start() ;
public void stop() ;
}
class Computer{
public static void show(USB usb){
usb.start() ;
System.out.println("=========== USB 设备工作 ========") ;
usb.stop() ;
}
};
class Flash implements USB{
public void start(){ // 重写方法
System.out.println("U盘开始工作。") ;
}
public void stop(){ // 重写方法
System.out.println("U盘停止工作。") ;
}
};
class Print implements USB{
public void start(){ // 重写方法
System.out.println("打印机开始工作。") ;
}
public void stop(){ // 重写方法
System.out.println("打印机停止工作。") ;
}
};
public class InterfaceDemo{
public static void main(String args[]){
Computer.show(new Flash()) ;
Computer.show(new Print()) ;
Computer.show(new USB(){
public void start(){
System.out.println("移动硬盘开始运行");
}
public void stop(){
System.out.println("移动硬盘停止运行");
}
});
}
};
接口的多实现
对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现
。并且,一个类能继承一个父类,同时实现多个接口。
实现格式:
【修饰符】 class 实现类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
【修饰符】 class 实现类 extends 父类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。
举例:
定义多个接口:
public interface A {
void showA();
}
public interface B {
void showB();
}
定义实现类:
public class C implements A,B {
@Override
public void showA() {
System.out.println("showA");
}
@Override
public void showB() {
System.out.println("showB");
}
}
测试类
public class TestC {
public static void main(String[] args) {
C c = new C();
c.showA();
c.showB();
}
}
接口的多继承(extends)
一个接口能继承另一个或者多个接口,接口的继承也使用 extends
关键字,子接口继承父接口的方法。
定义父接口:
public interface Chargeable {
void charge();
void in();
void out();
}
定义子接口:
public interface UsbC extends Chargeable,USB3 {
void reverse();
}
定义子接口的实现类:
public class TypeCConverter implements UsbC {
@Override
public void reverse() {
System.out.println("正反面都支持");
}
@Override
public void charge() {
System.out.println("可充电");
}
@Override
public void in() {
System.out.println("接收数据");
}
@Override
public void out() {
System.out.println("输出数据");
}
}
- 所有父接口的抽象方法都有重写。
- 方法签名相同的抽象方法只需要实现一次。
接口与实现类对象构成多态引用
实现类实现接口,类似于子类继承父类,因此,接口类型的变量与实现类的对象之间,也可以构成多态引用。通过接口类型的变量调用方法,最终执行的是你new的实现类对象实现的方法体。
接口的不同实现类:
public class Mouse implements USB3 {
@Override
public void out() {
System.out.println("发送脉冲信号");
}
@Override
public void in() {
System.out.println("不接收信号");
}
public class KeyBoard implements USB3{
@Override
public void in() {
System.out.println("不接收信号");
}
@Override
public void out() {
System.out.println("发送按键信号");
}
}
测试类
public class TestComputer {
public static void main(String[] args) {
Computer computer = new Computer();
USB3 usb = new Mouse();
computer.setUsb(usb);
usb.start();
usb.out();
usb.in();
usb.stop();
System.out.println("--------------------------");
usb = new KeyBoard();
computer.setUsb(usb);
usb.start();
usb.out();
usb.in();
usb.stop();
System.out.println("--------------------------");
usb = new MobileHDD();
computer.setUsb(usb);
usb.start();
usb.out();
usb.in();
usb.stop();
}
}
使用接口的静态成员
接口不能直接创建对象,但是可以通过接口名直接调用接口的静态方法和静态常量。
public class TestUSB3 {
public static void main(String[] args) {
//通过“接口名.”调用接口的静态方法 (JDK8.0才能开始使用)
USB3.show();
//通过“接口名.”直接使用接口的静态常量
System.out.println(USB3.MAX_SPEED);
}
}
使用接口的非静态方法
- 对于接口的静态方法,直接使用“
接口名.
”进行调用即可- 也只能使用“接口名."进行调用,不能通过实现类的对象进行调用
- 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用
- 接口不能直接创建对象,只能创建实现类的对象
public class TestMobileHDD {
public static void main(String[] args) {
//创建实现类对象
MobileHDD b = new MobileHDD();
//通过实现类对象调用重写的抽象方法,以及接口的默认方法,如果实现类重写了就执行重写的默认方法,如果没有重写,就执行接口中的默认方法
b.start();
b.in();
b.stop();
//通过接口名调用接口的静态方法
// MobileHDD.show();
// b.show();
Usb3.show();
}
}
JDK8中相关冲突问题
(1)类优先原则
当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的抽象方法重名,子类就近选择执行父类的成员方法。代码如下:
定义接口:
public interface Friend {
default void date(){//约会
System.out.println("吃喝玩乐");
}
}
定义父类:
public class Father {
public void date(){//约会
System.out.println("爸爸约吃饭");
}
}
定义子类:
public class Son extends Father implements Friend {
@Override
public void date() {
//(1)不重写默认保留父类的
//(2)调用父类被重写的
// super.date();
//(3)保留父接口的
// Friend.super.date();
//(4)完全重写
}
}
定义测试类:
public class TestSon {
public static void main(String[] args) {
Son s = new Son();
s.date();
}
}
(2)接口冲突(左右为难)
- 当一个类同时实现了多个父接口,而多个父接口中包含方法签名相同的默认方法时,怎么办呢?
- 声明接口:
package com.atguigu.interfacetype;
public interface BoyFriend {
default void date(){//约会
System.out.println("神秘约会");
}
}
选择保留其中一个,通过“接口名.super.方法名
"的方法选择保留哪个接口的默认方法。
public class Girl implements Friend,BoyFriend{
@Override
public void date() {
//(1)保留其中一个父接口的
// Friend.super.date();
// BoyFriend.super.date();
//(2)完全重写
System.out.println("学Java");
}
}
测试类
public class TestGirl {
public static void main(String[] args) {
Girl girl = new Girl();
girl.date();
}
}
子接口重写默认方法时,default关键字可以保留。
子类重写默认方法时,default关键字不可以保留。
2 常量冲突问题
- 当子类继承父类又实现父接口,而父类中存在与父接口常量同名的成员变量,并且该成员变量名在子类中仍然可见。
- 当子类同时实现多个接口,而多个接口存在相同同名常量。
此时在子类中想要引用父类或父接口的同名的常量或成员变量时,就会有冲突问题。
父类和父接口:
public class SuperClass {
int x = 1;
}
public interface SuperInterface {
int x = 2;
int y = 2;
}
public interface MotherInterface {
int x = 3;
}
子类:
public class SubClass extends SuperClass implements SuperInterface,MotherInterface {
public void method(){
// System.out.println("x = " + x);//模糊不清
System.out.println("super.x = " + super.x);
System.out.println("SuperInterface.x = " + SuperInterface.x);
System.out.println("MotherInterface.x = " + MotherInterface.x);
System.out.println("y = " + y);//没有重名问题,可以直接访问
}
}
总结
- 接口本身不能创建对象,只能创建接口的实现类对象,接口类型的变量可以与实现类对象构成多态引用。
- 声明接口用interface,接口的成员声明有限制:
- (1)公共的静态常量
- (2)公共的抽象方法
- (3)公共的默认方法(JDK8.0 及以上)
- (4)公共的静态方法(JDK8.0 及以上)
- (5)私有方法(JDK9.0 及以上)
- 类可以实现接口,关键字是implements,而且支持多实现。如果实现类不是抽象类,就必须实现接口中所有的抽象方法。如果实现类既要继承父类又要实现父接口,那么继承(extends)在前,实现(implements)在后。
- 接口可以继承接口,关键字是extends,而且支持多继承。
- 接口的默认方法可以选择重写或不重写。如果有冲突问题,另行处理。子类重写父接口的默认方法,要去掉default,子接口重写父接口的默认方法,不要去掉default。
- 接口的静态方法不能被继承,也不能被重写。接口的静态方法只能通过“接口名.静态方法名”进行调用。
小测试
1、为什么接口中只能声明公共的静态的常量?
因为接口是标准规范,那么在规范中需要声明一些底线边界值,当实现者在实现这些规范时,不能去随意修改和触碰这些底线,否则就有“危险”。
例如:USB1.0规范中规定最大传输速率是1.5Mbps,最大输出电流是5V/500mA
USB3.0规范中规定最大传输速率是5Gbps(500MB/s),最大输出电流是5V/900mA
例如:尚硅谷学生行为规范中规定学员,早上8:25之前进班,晚上21:30之后离开等等。
2、为什么JDK8.0 之后允许接口定义静态方法和默认方法呢?因为它违反了接口作为一个抽象标准定义的概念。
静态方法
:因为之前的标准类库设计中,有很多Collection/Colletions或者Path/Paths这样成对的接口和类,后面的类中都是静态方法,而这些静态方法都是为前面的接口服务的,那么这样设计一对API,不如把静态方法直接定义到接口中使用和维护更方便。
默认方法
:(1)我们要在已有的老版接口中提供新方法时,如果添加抽象方法,就会涉及到原来使用这些接口的类就会有问题,那么为了保持与旧版本代码的兼容性,只能允许在接口中定义默认方法实现。比如:Java8中对Collection、List、Comparator等接口提供了丰富的默认方法。(2)当我们接口的某个抽象方法,在很多实现类中的实现代码是一样的,此时将这个抽象方法设计为默认方法更为合适,那么实现类就可以选择重写,也可以选择不重写。
3、为什么JDK1.9要允许接口定义私有方法呢?因为我们说接口是规范,规范是需要公开让大家遵守的。
私有方法:因为有了默认方法和静态方法这样具有具体实现的方法,那么就可能出现多个方法由共同的代码可以抽取,而这些共同的代码抽取出来的方法又只希望在接口内部使用,所以就增加了私有方法。
接口与抽象类之间的对比
练习
interface A {
int x = 0;
}
class B {
int x = 1;
}
class C extends B implements A {
public void pX() {
System.out.println(x);
}
public static void main(String[] args) {
new C().pX();
}
}
java: 对x的引用不明确
B 中的变量 x 和 A 中的变量 x 都匹配
方法类优先原则:
interface A {
int x = 0;
default void me(){
System.out.println("interface_me");
}
}
class B {
int x = 1;
public void me(){
System.out.println("class me");
}
}
class C extends B implements A {
public static void main(String[] args) {
new C().me();
}
}
输出:class me