接口

接口的由来

兔子不会游泳, 游泳这个方法放在动物类中, 显然是不合适的. 而青蛙和狗都有游泳这个方法, 那么如果青蛙和狗各自写自己的游泳方法, 则可能导致这个方法不统一.


图 1

于是就可以定义一个接口, 在其中规定了游泳这个方法的书写规则, 让青蛙和狗各自去实现.

而看家这个方法又是狗这个类所特有的, 就不要放进接口中.

所以接口放的就是部分类公共的方法, 给这些方法规定一个统一的规则.


图 2

接口定义的不是继承的规则, 只是一个功能, 或者说是一种规则, 是对行为的抽象.


图 3

接口的定义和形式

接口用关键字 interface 来定义: public interface 接口名 {}

接口里面是抽象方法, 所以接口不能实例化.

接口和类之间是实现关系, 通过 implements 关键字来表示: public class 类名 implements 接口名 {}

接口的子类也叫实现类. 实现类要么重写接口中的所有抽象方法, 要么实现类本身也是一个抽象类. 一般都是重写接口中的所有抽象方法.

接口和类之间的实现关系, 可以是单实现, 也可以是多实现.

多实现: public class 类名 implements 接口名 1, 接口名 2 {}

实现类可以在继承一个类的同时实现多个接口: public class 类名 extends 父类 implements 接口名 1, 接口名 2 {}

新建接口文件:


图 4

如果新建接口文件时, 忘了选择 interface 选项, 可以在建立完文件后将 class 改为 interface, 文件图标也会跟着改过来.


图 5

练习:
编写带有接口和抽象类的标准 Javabean 类
青蛙 属性: 名字, 年龄 行为: 吃虫子, 蛙泳
狗 属性: 名字, 年龄 行为: 吃骨头, 狗刨
兔子 属性: 名字, 年龄 行为: 吃胡萝卜


图 6

Javabean 类:

public abstract class Animal {
    String name;
    int age;

    public Animal() {
    }

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    // 三个子类都有吃这个方法, 而具体的方法体不同, 则将吃这个方法抽取到父类中, 且定义为抽象类, 强制子类重写
    public abstract void eat();
}

接口:

public interface Swim {
    public abstract void swim();
}

Javabean 类:

public class Dog extends Animal implements Swim {
    public Dog() {
    }

    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("狗吃骨头. ");
    }

    @Override
    public void swim() {
        System.out.println("狗在狗刨. ");
    }
}

Javabean 类:

public class Frog extends Animal implements Swim {
    public Frog() {
    }

    public Frog(String name, int age) {
        super(name, age);
    }

    @Override
    public void swim() {
        System.out.println("青蛙在蛙泳. ");
    }

    @Override
    public void eat() {
        System.out.println("青蛙在吃虫子. ");
    }
}

Javabean 类:

public class Rabbit extends Animal {
    public Rabbit() {
    }

    public Rabbit(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("兔子在吃胡萝卜. ");
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        Frog f = new Frog("小青", 23);
        System.out.println(f.getName() + ", " + f.getAge());
        f.eat();
        f.swim();

        Rabbit r = new Rabbit("小白", 21);
        System.out.println(r.getName() + ", " + r.getAge());
        r.eat();
        // r.swim();
    }
}

执行结果:

小青, 23
青蛙在吃虫子. 
青蛙在蛙泳. 
小白, 21
兔子在吃胡萝卜. 

接口中成员的特点

成员变量

接口中的成员变量只能是常量.

默认修饰符: public static final. 默认指的是就算不写, 也默认是存在的. 接口是一种规则, 规则是不能被改变的, 所以接口里的成员变量都是常量, 所以用 final 修饰. 用 static 来修饰是为了方便调用, 用 接口名.常量名 就可以调用. public 表示公共的, 即所有的地方都可以使用接口里的常量.

多个子类的共有属性是抽取到父类中的, 而不是抽取到接口中, 所以接口中不会有类似于 name, age 等成员变量的.

构造方法

没有构造方法. 因为接口不能创建对象, 并且接口也不需要给子类的成员变量赋值.

成员方法

在 JDK 7 之前, 只能写抽象方法, 默认修饰符为 public abstract, 即哪怕不写虚拟机也会自动帮你加上.

JDK 8: 接口中可以定义有方法体的方法.

JDK 9: 接口中可以定义私有方法.

程序示例:

接口:

public interface Inter {
    int a = 10;
}

测试类:

public class Test {
    public static void main(String[] args) {
        System.out.println(Inter.a);  // 打印 10, 此处是用 接口名.变量名 的形式来调用的, 由此可以说明是用 static 来修饰的. 
        // Inter.a = 20;  // 报错: cannot assign a value to final variable 'a'  由此说明是用 final 修饰的. 
    }
}

接口和类之间的关系:

类和类的关系: 继承关系, 只能单继承, 不能多继承, 但是可以多层继承

类和接口的关系: 实现关系, 可以单实现, 也可以多实现, 还可以在继承一个类的同时实现多个接口. 如果一个类实现了多个接口, 那么就要重写全部的抽象方法.

接口和接口的关系: 继承关系, 可以单继承, 也可以多继承. 如果实现类实现了最下面的子接口的话, 那么就需要重写所有的抽象方法.

如果实现多个接口时, 多个接口有相同的方法, 那么在子类中只需要重写一次重复的方法.

接口 1:

public interface Inter1 {
    public abstract void method1();
    public abstract void method_abc();
}

接口 2:

public interface Inter2 {
    public abstract void method2();
    public abstract void method_abc();
}

接口 3:

public interface Inter3 extends Inter2, Inter1 {
    public abstract void method3();
}

实现类:

public class InterImpl implements Inter3 {  // 该类实现了最下面的子接口, 需要重写所有的方法
    @Override
    public void method1() {
    }

    @Override
    public void method2() {
    }

    @Override
    public void method_abc() {  // 重复的方法只需要实现一遍
    }

    @Override
    public void method3() {
    }
}

接口 3 继承自接口 1 和接口 2, 实现类实现了接口 3, 则实现类中要重写三个接口的全部方法.

练习:
编写带有接口和抽象类的标准 Javabean 类
我们现在有乒乓球运动员和篮球运动员, 乒乓球教练和篮球教练.
为了出国交流, 跟乒乓球相关的人员都需要学习英语.
请用所有知识分析, 在这个案例中, 哪些是具体类, 哪些是抽象类, 哪些是接口?
乒乓球运动员: 姓名, 年龄, 学打乒乓球, 说英语
篮球运动员: 姓名, 年龄, 学打篮球
乒乓球教练: 姓名, 年龄, 教打乒乓球, 说英语
篮球教练: 姓名, 年龄, 教打篮球


图 7

Javabean 类:

// 因为现在我不想让外界去直接创建人的对象
// 因为直接创建顶层父类人的对象此时是没有意义的
// 所以我就把他写为抽象的. 

public abstract class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
public abstract class Sporter extends Person {
    public Sporter() {
    }

    public Sporter(String name, int age) {
        super(name, age);
    }

    public abstract void study();
}
public abstract class Coach extends Person {
    public Coach() {
    }

    public Coach(String name, int age) {
        super(name, age);
    }

    public abstract void teach();
}
public class PingpangSporter extends Sporter implements English {
    public PingpangSporter() {
    }

    public PingpangSporter(String name, int age) {
        super(name, age);
    }

    @Override
    public void study() {
        System.out.println("乒乓球运动员" + this.getName() + "在学打乒乓球.");
    }

    @Override
    public void speakEnglish() {
        System.out.println("乒乓球运动员" + this.getName() + "在学说英语.");
    }
}
public class BasketballSporter extends Sporter {
    public BasketballSporter() {
    }

    public BasketballSporter(String name, int age) {
        super(name, age);
    }

    @Override
    public void study() {
        System.out.println("篮球运动员" + this.getName() + "在学习打篮球.");
    }
}
public class PingpangCoach extends Coach implements English {
    public PingpangCoach() {
    }

    public PingpangCoach(String name, int age) {
        super(name, age);
    }

    @Override
    public void teach() {
        System.out.println("乒乓球教练" + this.getName() + "在教别人打乒乓球.");
    }

    @Override
    public void speakEnglish() {
        System.out.println("乒乓球教练" + this.getName() + "在学说英语.");
    }
}

public class BasketballCoach extends Coach {
    public BasketballCoach() {
    }

    public BasketballCoach(String name, int age) {
        super(name, age);
    }

    @Override
    public void teach() {
        System.out.println("篮球教练" + this.getName() + "在教别人打篮球.");
    }
}

接口类:

public interface English {
    public abstract void speakEnglish();
}

测试类:

public class Test {
    public static void main(String[] args) {
        PingpangSporter pingpangSporter = new PingpangSporter("福原爱", 23);
        System.out.println(pingpangSporter.getName() + ", " + pingpangSporter.getAge());
        pingpangSporter.speakEnglish();
        pingpangSporter.study();

        System.out.println("----------------------------------------");

        PingpangCoach pingpangCoach = new PingpangCoach("不懂球的胖子", 56);
        System.out.println(pingpangCoach.getName() + ", " + pingpangCoach.getAge());
        pingpangCoach.speakEnglish();
        pingpangCoach.teach();

        System.out.println("----------------------------------------");

        BasketballSporter basketballSporter = new BasketballSporter("乔丹", 21);
        System.out.println(basketballSporter.getName() + ", " + basketballSporter.getAge());
        basketballSporter.study();

        System.out.println("----------------------------------------");

        BasketballCoach basketballCoach = new BasketballCoach("姚明", 42);
        System.out.println(basketballCoach.getName() + ", " + basketballCoach.getAge());
        basketballCoach.teach();
    }
}

执行结果:

福原爱, 23
乒乓球运动员福原爱在学说英语.
乒乓球运动员福原爱在学打乒乓球.
----------------------------------------
不懂球的胖子, 56
乒乓球教练不懂球的胖子在学说英语.
乒乓球教练不懂球的胖子在教别人打乒乓球.
----------------------------------------
乔丹, 21
篮球运动员乔丹在学习打篮球.
----------------------------------------
姚明, 42
篮球教练姚明在教别人打篮球. 

JDK 8 开始接口中新增的方法

JDK 7 之前, 接口中只能定义抽象方法.

JDK 8 的新特性: 接口中可以定义有方法体的方法, 分两种, 一种是默认的方法, 一种是静态的方法.

JDK 9 的新特性: 接口中可以定义私有方法, 即用 private 修饰.

Java 为什么这么设计?

按照 JDK 7 的规定, 如果接口变了, 那么所有的实现类都需要进行修改.

接口中有方法体的方法, 是在接口升级时, 为了兼容性而使用的. 接口添加了有方法体的方法后, 实现类不需要立马重写, 不重写也不会报错, 当这个实现类要用到这个方法的时候再去重写即可.

JDK 8 开始, 允许在接口中定义默认方法, 用关键字 default 修饰. 作用就是解决接口升级带来的兼容性问题.

接口中默认方法的定义格式:

public default 返回值类型 方法名 (参数列表) {}

例如:

public default void show () {}

默认方法不是抽象方法, 不会被强制重写, 但是如果被重写, 重写的时候要去掉 default 关键字.

public 是默认修饰符, 可以省略, 但是 default 不能被省略.

如果一个实现类实现了多个接口, 多个接口中存在相同名字的默认方法, 子类就必须对这个方法进行重写.

程序示例:

接口:

public interface Inter {
    // 抽象方法
    public abstract void method();
    // 默认方法
    public default void show() {
        System.out.println("接口的默认方法 ---- show");
    }
}

实现:

public class ImplInter implements Inter{
    // 重写抽象方法
    @Override
    public void method() {
        System.out.println("实现类的 method 方法. ");
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        ImplInter ii = new ImplInter();
        ii.method();
        ii.show();  // 类并没有重写接口里面的默认方法, 但是类的对象也可以调用这个默认方法
    }
}

执行结果:

实现类的 method 方法. 
接口的默认方法 ---- show

程序示例:

接口:

public interface Inter {
    // 抽象方法
    public abstract void method();
    // 默认方法
    public default void show() {
        System.out.println("接口的默认方法 ---- show");
    }
}

实现类:

public class ImplInter implements Inter {
    // 重写抽象方法
    @Override
    public void method() {
        System.out.println("实现类的 method 方法. ");
    }
    // 重写接口的默认方法
    @Override
    public void show() {
        System.out.println("重写接口的默认方法. ");
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        ImplInter ii = new ImplInter();
        ii.method();
        ii.show();
    }
}

执行结果:

实现类的 method 方法. 
重写接口的默认方法. 

如果两个接口中有同名的默认方法, 则实现了这两个接口的类必须重写这个方法, 否则类的对象在调用这个默认方法时, 不知道是调用哪个接口中的默认方法. 程序示例:

第一个接口:

public interface InterA {

    public abstract void methodA();

    public default void show() {
        System.out.println("InterA 里面的 show()");
    }
}

第二个接口:

public interface InterB {
    public abstract void methodB();

    public default void show() {
        System.out.println("InterB 里面的 show()");
    }
}

实现类:

public class InterImpl implements InterA, InterB {

    // 重写抽象方法
    @Override
    public void methodA() {
    }

    // 重写抽象方法
    @Override
    public void methodB() {
    }

    @Override
    public void show() {  // 这个 default 方法被强制重写, 因为两个接口都有这个方法
        System.out.println("实现类中的重写的默认方法");
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        InterImpl inter = new InterImpl();
        inter.show();  // 实现类中的重写的默认方法
    }
}

JDK 8 开始允许在接口中定义静态方法, 用 static 修饰.

接口中的静态方法的定义格式:

public static 返回值类型 方法名 (参数列表) {}

例如:

public static void show () {}

接口中静态方法的注意事项:

静态方法只能通过接口名调用, 不能通过实现类名或对象名来调用.

public 可以省略, 但是 static 不能被省略. 如果 static 省略不写, 则会被当为抽象方法, 因为 abstract 是默认的, 不写就默认是 abstract.

静态方法不需要重写. 静态方法不能被重写, 强制重写则代码报错.

程序示例:

接口代码:

public interface Inter {
    // 接口中的抽象方法
    public abstract void method();

    // 接口中的静态方法
    public static void show() {
        System.out.println("接口中的静态方法");
    }
}

实现类的代码:

public class InterImpl implements Inter{
    // 重写接口的抽象方法
    @Override
    public void method() {
        System.out.println("InterImpl 重写的抽象方法.");
    }

    // 接口的静态方法不需要重写

    // 这个不是重写, 只是实现类里面有一个和接口里面同名的静态方法
    // 如果调用时, 用 InterImpl.show(), 则调用的是子类里面的 show() 方法
    // 用 Inter.show(), 则调用的是接口里面的 show() 方法
    public static void show(){
        System.out.println("InterImpl 的 show() 方法.");
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        // 调用接口中的 show() 方法
        Inter.show();  // 接口中的静态方法

        // 调用实现类中的静态方法
        InterImpl.show();  // InterImpl 的 show() 方法.
    }
}

JDK 9 开始接口中新增的方法

在 JDK 9 之后, 接口中可以写私有方法.

私有方法分两种: 普通的和静态的.

接口中私有方法的定义格式:

普通私有方法, 是给默认方法服务的:

格式: private 返回值类型 方法名(参数列表) {}

范例: private void show() {}

加了 static 的私有方法, 即静态的私有方法, 是给静态方法服务的:

格式: private static 返回值类型 方法名(参数列表) {}

范例: private static void method() {}

程序示例:

接口:

public interface Inter {
    public default void show1() {
        System.out.println("show1 方法开始执行. ");
        System.out.println("记录程序在开始执行之后的各种细节, 这里有 100 行代码. ");
    }

    public default void show2() {
        System.out.println("show2 方法开始执行. ");
        System.out.println("记录程序在开始执行之后的各种细节, 这里有 100 行代码. ");
    }
}

显然两个方法中有大量的重复代码, 可以进行抽取.

public interface Inter {
    public default void show1() {
        System.out.println("show1 方法开始执行. ");
        show3();
    }

    public default void show2() {
        System.out.println("show2 方法开始执行. ");
        show3();
    }

    public default void show3() {
        System.out.println("记录程序在开始执行之后的各种细节, 这里有 100 行代码. ");
    }
}

抽取出来之后在 show1() 和 show2() 中调用这个抽取出来的 show3() 即可.

但是这个方法不希望被外面的其他类调用, 因为毫无意义. 因此规定其为 private 并删掉 default.

public interface Inter {
    public default void show1() {
        System.out.println("show1 方法开始执行. ");
        show3();
    }

    public default void show2() {
        System.out.println("show2 方法开始执行. ");
        show3();
    }

    private void show3() {  // 这就是普通的私有方法, 即没有用 static 修饰的私有方法
        System.out.println("记录程序在开始执行之后的各种细节, 这里有 100 行代码. ");
    }
}

针对静态方法则需要静态私有方法为其服务:

public interface Inter {
    public static void show1() {
        System.out.println("show1 方法开始执行. ");
        show4();
    }

    public static void show2() {
        System.out.println("show2 方法开始执行. ");
        show4();
    }

    private void show3() {
        System.out.println("记录程序在开始执行之后的各种细节, 这里有 100 行代码. ");
    }

    private static void show4() {  // 加 static 修饰的静态私有方法
        System.out.println("记录程序在开始执行之后的各种细节, 这里有 100 行代码. ");
    }
}

当一个方法的形参是接口时, 可以传递接口所有实现类的对象, 这种方式称为接口多态.

posted @ 2024-09-11 21:59  有空  阅读(56)  评论(0)    收藏  举报