1. 接口说明
接口和类是一种并列关系,由于类的继承是单继承的,所以一个子类不能具有多个父类的特征,接口就可以用来解决这个问题,接口可以进行多实现!同时,在逻辑上,接口没有子父类一说,而是是否实现一说。可以认为接口定义了很多功能,而当类实现这个接口时,就代表这个类具有这些功能。
2. 接口定义
使用关键字 interface 来定义接口,interface 和 class 是一种并列关系,定义接口的方式和定义类的方式类似。
- 在JDK 7以及之前:只能定义全局常量和抽象方法。没有可变属性
- 全局常量:public static final 的
- 抽象方法:public abstract
- 在JDK 8 以及之后:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
先看 JDK 7:
上述接口定义了一个全局常量和一个抽象方法,可以看到,public static final 和 public abstract 两处修饰符都成了灰色,是因为接口中默认变量均是全局常量,方法均是抽象方法,因此加了修饰符和不加修饰符是一样的。则上述代码等价于:
注意,接口中是不能定义可变属性的。
更重要的,接口不能定义构造器,这也是接口与抽象类最本质的区别。没有构造器,说明接口无法被实例化,也没有子类一说。更一般点,接口不能含有任何实体方法。
3. 接口实现
在 Java 开发中,接口通过让类去实现(implements)的方式来使用。如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化。
package pkg5;
public interface Flyable {
// public static final
int speed = 7900 ;
// public abstract
void fly() ;
void speedUp() ;
void speedDown() ;
}
这样一来,类Jet就实现了接口Flyable,可以进行实例化。
同理抽象类,如果接口的抽象方法没有完全实现,在该类必须定义为abstract,否在无法通过编译。不过这样没什么意义,一般而言,就用实体类来实现一个接口的所有方法。
当一个类实现了某一个接口时,它的对象就可以作为该接口的类型进行传参。
package pkg5;
public class InterfaceTest {
public static void main(String[] args) {
Jet J20 = new Jet(100,"歼20");
InterfaceTest i1 = new InterfaceTest();
i1.test(J20);
}
public void test(Flyable f){
f.fly();
}
}
就这点来说,接口更像是一种规范,代表“只有完成了….才算作…..”或者“只要完成了….就算作…..”。这点在应用举例那里会细说。
4. 接口可以多实现
接口一个非常非常重要的功能,就是类可以一次实现多个接口,也就是多实现性,这样就弥补了子父类单继承的局限性。比如,定义一个接口 Flyable(如上述),再定义一些接口如Attackable(可攻击的),Refitable(可改装的),那么一个类 Jet(喷气机)就可以同时实现上述三个接口,代表它同时具有 可飞行、可攻击、可改装这三样功能。
若想完成接口的多实现,则实体类必须要实现其待实现的所有接口的所有方法。在类声明处,只需要在 implements 关键字后一次写入待实现接口然后用逗号分隔即可。
public class Jet implements Flyable,Attackable,Refitable
具体实现过程如下所示<br />Flayable接口:
package pkg5;
public interface Flyable {
// public static final
int speed = 7900 ;
// public abstract
void fly() ;
void speedUp() ;
void speedDown() ;
}
Attackable接口:
package pkg5;
public interface Attackable {
// 子弹数量
int bulletNum = 9999 ;
void shot() ; // 射击
void bomb() ; // 轰炸
}
Refitable接口:
package pkg5;
public interface Refitable {
void changeColor() ; // 换色
void upgrade() ; // 升级
}
Jet类来实现上述接口:
package pkg5;
public class Jet implements Flyable,Attackable,Refitable{
@Override
public void shot() {
System.out.println("jetShout");
}
@Override
public void bomb() {
System.out.println("jetBomb");
}
@Override
public void fly() {
System.out.println("jetFly");
}
@Override
public void speedUp() {
System.out.println("jetSpeedUp");
}
@Override
public void speedDown() {
System.out.println("jetSpeedDown");
}
@Override
public void changeColor() {
System.out.println("jetChangeColor");
}
@Override
public void upgrade() {
System.out.println("jetUpgrade");
}
}
这样一来,Jet就可以实例化了,Jet对象就拥有了三个接口的全部功能。<br /><br />可以看出,接口的实现与类的继承在结构上非常相似,但逻辑上却大不相同。对于类的继承而言,可以理解为 A是B的什么什么...,而对于接口的实现而言,可以理解为 A能够完成什么什么...。前者是“是不是”的关系,后者是“能不能”的关系,二者相互弥补。<br />既然说了二者是相互弥补的,那么二者就可以同时进行。也即,一个类在定义时能够同时继承一个父类并实现多个接口。<br />Aircraft 类:
package pkg5;
public class Aircraft {
private int size ;
private String name ;
public Aircraft(int size, String name) {
this.size = size;
this.name = name;
}
public int getSize() {
return size;
}
public String getName() {
return name;
}
public void drive(){
}; // 可以驾驶
}
Jet 继承 Aircraft 并实现三个接口:
package pkg5;
public class Jet extends Aircraft implements Flyable,Attackable,Refitable{
// 实现父类的构造器
public Jet(int size, String name) {
super(size, name);
}
// 实现接口,代码同上,略
// ...
}
这样,父类继承与多接口实现就同时完成了。<br /><br />可以理解为,J20是一个 Jet 喷气机,它属于 Aircraft 因此拥有 Aircraft 拥有的所有属性和功能,同时,它又实现了 Flyable、Attackable、Refitable 三个接口,具有飞行、攻击、改装三个功能。
5. 接口可以多继承
继承并不是类的专属特性,接口之间也有继承操作,且允许多继承。
父接口:
package pkg5;
public interface Move {
void move() ; // 可以动
}
子接口:
package pkg5;
public interface Run extends Move{
void run(); // 可以跑
}
当一个类要实现一个接口时,这个接口的所有父接口的所有方法,这点也很好理解
package pkg5;
public class Person implements Run{
@Override
public void move() {
}
@Override
public void run() {
}
}
6. 应用举例
在 Java 开发中,常用接口作为一种行为规范,或是协议。比如,前文中定义了接口 Flyable 意为能飞行的,这个接口下有方法 fly、speedUp、speedDown。这就代表着我们认为,当一个类实现了fly、speedUp和speedDown,才将其看作是 Flyable 的。也即,只有一个类能做到飞、加速、减速,我们才认为他是符合Flyable规范的。
再举一个更形象的例子 :
USB接口: // 定义USB传输的规范与行为
public interface USB { // 常量,长、宽、高什么的 double length = 2.0 ; double width = 1.5 ; double height = 1.0 ; // public static void start(); void stop(); }
Flash类: // 模拟U盘 ```java package pkg7;
public class Flash implements USB{ public void start(){ System.out.println(“U盘开始工作”); } public void stop(){ System.out.println(“U盘停止工作”); } }
3. Printer: // 模拟打印机
```java
package pkg7;
public class Printer implements USB{
public void start(){
System.out.println("打印机开始工作");
}
public void stop(){
System.out.println("打印机停止工作");
}
}
- Computer: // 模拟电脑, 接收USB传输的数据 ```java package pkg7;
public class Computer { public void usbTransfer(USB usb){ usb.start(); System.out.println(“数据传输中”); usb.stop(); } }
5. USBTest测试 :
```java
package pkg7;
public class USBTest {
public static void main(String[] args) {
Computer com = new Computer();
Flash flash = new Flash();
Printer printer = new Printer();
com.usbTransfer(flash);
System.out.println("****************");
com.usbTransfer(printer);
}
}
7. 匿名实现类
创建一个接口的匿名实现类的非匿名对象,和创建抽象类的匿名对象格式基本一样:
USB phone = new USB() {
@Override
public void start() {
}
@Override
public void stop() {
}
};
或者直接用匿名对象也可以.
com.usbTransfer(new USB() {
@Override
public void start() {
}
@Override
public void stop() {
}
});
8. JDK8 中的接口新特性
在 JDK8 中,除了能定义全局常量和抽象方法之外,还可以定义静态方法、默认方法。
package pkg7;
public interface USB {
// 静态方法
public static void staticMethod(){
}
// 默认方法
public default void defaultMethod(){
}
}
同理,public可以省略(接口中能定义的所以的结构都默认为 **public**,都可以省略)<br /><br />静态方法和默认放法都是不需要类来实现的。<br />
8.1 静态方法
接口的静态方法无法被实现类调用,也无法被实现类的对象调用。
但是,接口的静态方法能被接口直接调用,这种特性非常像工具类。
【总结】
接口中可以用 static 关键字定义一个静态方法,该方法必须具有方法体。外界能够且只能通过接口直接调用该静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。可以在标准库中找到像 Collection \ Collections 或者 Path \ Paths 这样成对的接口和类。
8.2 默认方法
【知识点1.】
通过实现类的对象,可以调用接口中的默认方法
而且只有这一种调用方式,直接通过接口调用或者通过实现类来调用都不行!
默认方法是可以被实现类重写的。换句话说,除了 静态方法 不能被重写、抽象方法 被称作实现。其余的方法都是能被重写的。
package pkg7;
public class Printer implements USB{
@Override
public void defaultMethod() {
System.out.println("Printer重写了USB的defaultMethod");
}
}
【知识点2.】
如果子类(实现类)继承的父类中声明了和实现接口中的默认方法同名同参数的方法,那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数方法,而不是实现接口中的默认方法。如下例所示:
USB接口:
package pkg7;
public interface USB {
// 静态方法
public static void staticMethod(){
}
// 默认方法
public default void defaultMethod(){
System.out.println("Method in USB");
}
}
SubClass父类:
package pkg7;
public class SubClass {
public void defaultMethod(){
System.out.println("Method in SubClass");
}
}
Printer继承了SubClass同时又实现了USB,但是没有重写方法:
package pkg7;
public class Printer extends SubClass implements USB{
}
测试:
说明子类调用的是父类中同名同参的方法,而不是接口中的默认方法。(类优先原则)
【知识点3.】
如果实现了类同时实现了多个接口,而这多个接口中定义了同名同参数的默认方法。如果实现类重写了这个方法,那就没事,覆盖了;但如果实现类没有重写该方法,编译会直接报错 ==> 接口冲突