Java的接口
介绍接口的基本使用,对接口多态,及继承类和实现接口时产生方法冲突和常量冲突分析
Author: Msuenb
Date: 2023-02-11
接口概述
多态的使用前提必须是"继承"。而类继承有如下问题:
-
类继承有单继承限制
-
类继承表示的是事物之间
is-a
的关系,但是is-a
的关系要求太严格了。
为了解决这两个问题,引入了接口,接口支持:
-
多实现
-
用实现类和接口的
has-a
关系代替is-a
关系
Bird is a Animal. 鸟是一种动物。
Plane is not a Animal. 飞机不是一种动物。
Plane is a Vehicle. 飞机是一种交通工具。
Bird has a fly(); 鸟具有飞的能力。或 鸟会飞。
Plane has a fly(); 飞机具有飞的功能。或 飞机会飞。
is-a 解决的是:是不是的问题
has-a 解决的是:能不能、会不会、可不可以的问题
语法格式
使用 interface
关键字声明接口。它也会被编译成.class
文件,但它并不是类,而是另外一种引用数据类型。
【修饰符】 interface 接口名{ // 修饰符只能是 public / 缺省
//接口的成员列表:
// 公共的静态常量
// 公共的抽象方法
// 公共的默认方法(JDK1.8以上)
// 公共的静态方法(JDK1.8以上)
// 私有方法(JDK1.9以上)
}
示例:
interface Flyable {
int MAX_SPEED = 299792458; // 省略 public static final
void fly(); // 省略 public
default void end(){ // 省略public
System.out.println("end");
}
static void start(){ // 省略public
System.out.println("start");
}
/*private void f() { // 不能省略 private JDK1.9及以上
System.out.println("私有方法");
}*/
}
注意:接口中不能定义构造方法! 但抽象类中可以定义构造方法(供子类使用)!
接口使用
-
使用接口静态成员
接口不能直接创建对象,但是可以通过接口名直接调用接口的静态方法和静态常量。
Flyable.MAX_SPEED; // 调用接口的静态常量 Flyable.start(); // 调用接口的静态方法
-
类实现接口
类与接口的关系为实现关系,即类实现接口。实现的动作类似继承,格式相仿,实现使用
implements
关键字。【修饰符】 class 实现类 implements 接口{ // 重写接口中抽象方法【必须】,若实现类是抽象类,可以不重写 // 重写接口中默认方法【可选】 } 【修饰符】 class 实现类 extends 父类 implements 接口{ // 重写接口中抽象方法【必须】,若实现类是抽象类,可以不重写 // 重写接口中默认方法【可选】 }
注意:
- 如果接口的实现类是非抽象类,必须重写接口中的所有抽象方法
- 默认方法可以选择保留,也可以重写。default单词就不要再写
- 接口中的静态方法不能被继承也不能被重写
class Animal { public void eat(){ System.out.println("吃东西"); } } class Bird extends Animal implements Flyable{ // 重写父接口的抽象方法,【必选】 @Override public void fly() { System.out.println("我要飞的更高~~~"); } // 重写父接口的默认方法,【可选】 @Override public void end() { System.out.println("轻轻落在树枝上~~~"); } }
-
使用接口的非静态方法
- 对于接口的静态方法,直接(也只能)使用
接口名.
进行调用即可。 - 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用
Bird bird = new Bird(); Flyable.start(); // 调用接口的静态方法,只能通过 接口名. // 必须依赖于实现类的对象 bird.fly(); // 调用接口的抽象方法 bird.end(); // 调用接口的默认方法 bird.eat(); // 调用父类继承的方法
- 对于接口的静态方法,直接(也只能)使用
-
接口多实现
在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的。并且,一个类能继承一个父类,同时实现多个接口。
【修饰符】 class 实现类 implements 接口1, 接口2, 接口3...{ // 重写接口中抽象方法【必须】,若实现类是抽象类,可以不重写 // 重写接口中默认方法【可选】 } 【修饰符】 class 实现类 extends 父类 implements 接口1, 接口2, 接口3...{ // 重写接口中抽象方法【必须】,若实现类是抽象类,可以不重写 // 重写接口中默认方法【可选】 }
接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。
定义多个接口:
interface Jumpable { void jump(); } interface Runnable { void jump(); void run(); }
定义实现类:
class Bird implements Flyable, Jumpable, Runnable{ // 重写父接口的抽象方法,【必选】 @Override public void fly() { System.out.println("我要飞的更高~~~"); } // 重写父接口的默认方法,【可选】 @Override public void end() { System.out.println("轻轻落在树枝上~~~"); } @Override public void jump() { System.out.println("我会跳跳~~~"); } @Override public void run() { System.out.println("我会跑~~"); } }
测试类:
public class TestBird { public static void main(String[] args) { Bird bird = new Bird(); bird.fly(); //调用Flyable接口的抽象方法 bird.jump(); //调用Jumpable接口的抽象方法 bird.run(); //调用Runnable接口的抽象方法 } }
-
接口多继承
一个接口能继承另一个或者多个接口,接口的继承也使用
extends
关键字,子接口继承父接口的方法。interface A { void a(); } interface B { void b(); } interface C extends A,B { void c(); }
接口多态
实现类似继承,因此,接口类型的变量与实现类的对象之间,也可以构成多态引用。
class Plane implements Flyable{
@Override
public void fly() {
System.out.println("我直入云霄");
}
}
class Kite implements Flyable {
@Override
public void fly() {
System.out.println("我怎么飞也挣脱不了线");
}
}
public class TestFlyableImpl {
public static void main(String[] args) {
Flyable f1 = new Bird(); // 接口引用实现类对象
f1.fly(); // 调用实现类实现的方法
Flyable f2 = new Plane();
f2.fly();
Flyable f3 = new Kite();
f3.fly();
}
}
接口特点总结
- 接口本身不能创建对象,只能创建接口的实现类对象,接口类型的变量可以与实现类对象构成多态引用。
- 声明接口用
interface
,接口的成员声明有限制:公共的静态常量、公共的抽象方法、公共的默认方法、公共的静态方法、私有方法(JDK1.9
) - 类可以实现接口,且支持多实现。接口可以继承接口,而且支持多继承。
- 如果接口实现类不是抽象类,就必须实现接口中所有的抽象方法。如果实现类既要继承父类又要实现父接口,那么
extends
在前,implements
在后。 - 接口的默认方法可以选择重写或不重写。子类重写父接口的默认方法,要去掉default,子接口重写父接口的默认方法,不要去掉default。
- 接口的静态方法不能被继承,也不能被重写。接口的静态方法只能通过
接口名.静态方法名
进行调用。
关于接口的其他问题
默认方法冲突问题:
-
亲爹优先原则
当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的抽象方法重名,子类就近选择执行父类的成员方法。
-
左右为难
当一个类同时实现了多个父接口,而多个父接口中包含方法签名相同的默认方法时。
interface FatherInterface {
default void fun() {
System.out.println("父接口中的方法");
}
}
interface FatherInterface2 {
default void fun() {
System.out.println("第二个父接口中的方法");
}
}
class FatherClass {
public void fun() {
System.out.println("父类中的方法");
}
}
class Son1 extends FatherClass implements FatherInterface {
// 不重写 默认保留父类的
}
class Son2 extends FatherClass implements FatherInterface {
@Override
public void fun() { // 重写
// super.fun(); // 保留父类的
FatherInterface.super.fun(); // 保留父接口的
}
}
class Son3 implements FatherInterface, FatherInterface2 {
@Override
public void fun() { // 重写
// FatherInterface.super.fun(); // 保留第一个父接口的
FatherInterface2.super.fun(); // 保留第二个父接口的
}
}
public class Test {
public static void main(String[] args) {
new Son1().fun();
new Son2().fun();
new Son3().fun();
}
}
常量冲突问题:
- 当子类继承父类又实现父接口,而父类中存在与父接口常量同名的成员变量,并且该成员变量名在子类中仍然可见。
- 当子类同时继承多个父接口,而多个父接口存在相同同名常量。
class SuperClass {
int x = 1;
}
interface SuperInterface {
int x = 2;
int y = 2;
}
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);//没有重名问题,可以直接访问
}
}