【Java核心技术卷】深入理解Java的接口

  1. 云栖社区>
  2. 博客>
  3. 正文

【Java核心技术卷】深入理解Java的接口

沉晓 2019-11-12 19:39:37 浏览336
展开阅读全文

接口是Java中非常核心的一部分,我之前写了一篇博文,名字是 了解Java的内存逻辑对象模型
接口结束后除了Mark Word和反射类指针没有说外,其他的都已经说过了.
接口部分对应的是接口偏移量表指针 和 接口方法表指针.
.
学习接口之前,要对方法 和 继承有比较好的理解,下面进行回顾一下 :
在对象模型中,对象之间的关系:
1, 实例与实例的组合关系:有多种语义关系
2, 实例与类的关系:

  • 实例对象—类:
  • 类—反射类:
  • 反射类—反射类:

3、继承关系:

  • 类—类继承: 实现继承(继承的方法有实现)和数据结构的继承
  • 类—接口继承: 声明继承(继承的方法只有声明,没有实现)
  • 接口—接口继承:契约继承(告诉实现类,实现一个接口,还需要实现该接口继承的接口)

    Java语言对象模型中是通过不同的实例对象结构、类型对象结构数据结构来区分实例对象和类型对象。引用类型主要三种:类类型、接口类型、数组类型。如果对接口类型之外的两种类型不太了解,可以参考我的专栏 Java核心技术 卷1

对象之间的关系了解之后,让我们看一下 方法:
1.方法定义
1) 方法声明
ReturnType method_name1(parameter_list)
2) 方法体(方法实现)
有一对花括号括起来的部分就是方法体
{
int x = 26;// 方法体中定义的变量可以初始化
int y = 28;
x = x + y;
}

由JVM完成方法声明到方法实现的映射。
2. 方法的调用
将一个方法调用同一个方法声明连接到一起就称为“绑定“(Binding)。
由编译器和解释器共同完成。 可以参考多态那篇博文,这里仅仅是简单的提一下.
1) 静态绑定调用:由变量的类型决定调用的代码;在编译时,就确定了具体的调用的方法代码,称为静态绑定
编译时静态绑定:变量类型--->类--->静态绑定类或超类中的方法声明
解释时静态绑定调用:直接执行该方法的实现代码
a、 静态方法
b、 实方法(private、final virtual)
c、 super调用的方法
d、 new调用的构造方法
2) 动态绑定调用:由变量的类型、变量引用的实例对象决定调用的代码
动态多态:即需要在运行,再确定,也称为动态绑定。
a. 类虚方法调用:
变量类型--->实例对象--->类--->动态绑定类中的方法--->执行代码
b. 接口方法的调用:
变量类型--->实例对象--->类--->动态绑定接口中的方法--->类中接口方法映射的方法--->执行代码
3) 反射调用

在这里插入图片描述
这张图说明了接口方法动态绑定的多个过程, 如果你还不理解Java类之间的多态,也就是虚方法的动态绑定在各个过程的情况, 我这里解释也没什么用, 如果理解的话,通过对照Java的内存对象模型进行理解.

3.方法的分类
1) 静态方法
2) 实例方法

  • 实方法
  • 虚方法
  • 抽象方法(纯虚方法)

3) 构造方法

再简单回顾一下继承:
继承有下面两种情况(含拓展)
类—类继承的公共成员:
1.公共实例数据结构(实例字段)
2.公共实方法
3.公共虚方法
4.公共抽象方法
类—接口继承的通用成员:
1.通用抽象方法
2.缺省通用虚方法

类—类继承和类—接口继承只有一个相同之处,那就是都继承了抽象方法。

程序在OS系统中的运行结果:(对象模型、执行模型

  1. 生成数据结构
  2. 访问数据结构
  3. 加工数据结构中的数据
    执行模型离不开对象模型,之前也介绍过了,这里也不再赘述.

关于类、抽象类、接口
普通类(一定有实现)------抽象类(可能有实现)------接口类(一定没实现),
抽象类是普通类与接口之间的一种中庸之道,是一个半成品。

抽象类中可能存在的继承的公共成员:

  1. 公共实例数据结构
  2. 公共实例方法
  3. 公共虚方法
  4. 公共抽象方法

接口中可能存在的继承的通用成员:

  1. 通用抽象方法
  2. 缺省通用虚方法

普通类中可能存在的继承的公共成员:

  1. 实例数据结构
  2. 实例方法
  3. 虚方法

关于Java栈、Java堆、Java方法区

  1. Java栈
    Java栈由JVM建立,程序运行在栈中产生局部变量和形参
  2. Java堆
    Java堆由JVM管理,程序运行在堆中产生实例对象
  3. Java方法区
    Java方法区完全由JVM建立控制,存放类对象、方法代码、常量池(Java8)。

铺垫了那么多,下面进行对接口的理解:
Java语言与C++语言两大区别:没有多继承、没有指针。
通过接口,能够实现多继承
如果Java的类也能够多继承的话,会导致程序的混乱, 耦合度增大,而且也带来了很多的问题和注意事项.

接口定义:
接口是引用类型的一种,与类相似,但也存在诸多不同(类类型、接口类型、数组类型均是引用类型)。
接口名一般为名词,也可以是以able结尾的形容词(例如接口名skinnable 可换肤的)
接口是功能的抽象,而抽象类是概念的抽象(一般具有相同特征值),抽象类是半成品类。
例如:空调车,车是抽象概念,而空调是车的功能。即车是抽象类,而空调是接口。
类之间的继承很好理解, 类继承接口为声明继承也很好理解,关键的是接口与接口之间的继承,有点特殊,它不像类与类之间的继承是父与子之间的那种意义, 它所体现的是功能上的继承. 也就是前面所说的契约继承.

接口是一种规范、是合约,只有方法的声明和方法的功能语义描述,而没有实现;接口中的一组抽象方法构成了实现该接口的在树形层次结构上可能毫不相干的不同实现类共同遵守的约定,描述了实现类应该具有的一组通用功能,而功能方法和实例域应该由实现类来实现。
接口和类的差别在于:类可以维护状态信息,而接口不能。接口不能有实例变量。
常使用接口来建立类和类之间的“协议”。

使用接口的核心原因:
一、 帮助实现类实现多继承,带来灵活性。能够向上转型为多个基类型。
二、 防止客户端程序员创建该类的实例对象
如果要创建只有抽象方法的纯抽象基类,那么就应该选择接口而不是纯抽象类。
接口将方法的声明和方法的实现分离,使得声明和实现完全解耦,因而接口可以应用于(映射到)不同的具体实现,因此代码也就更具可复用性。(这个可以与C++中类的多继承进行比较一下)

接口定义:
1、接口声明

Interface interface_name extends interface1_name, interface2_name //接口可以多继承,是契约继承

2、接口体:有一对花括号括起来的部分就是接口体。
子接口接口体中可以对父接口的方法和常量进行隐藏 new。

多重继承
一个类只能有一个父类,但是可以继承多个接口。可以提供多重继承的大多好处,同时还能避免C++类多重继承的复杂性和低效性。
在这里插入图片描述
接口的继承
专用接口 继承 通用接口。
是契约/合约继承,告诉实现类,实现一个接口,还需要实现该接口继承的接口。
在这里插入图片描述
接口的扩展允许存在多条从具有较高通用性的接口到较高专用性的接口的链。

面向接口编程
方法的形参或返回值的类型可以为接口,字段可以为接口,即为面向接口的编程。
接口变量和类变量引用的实例对象的范围不一样,面向接口的编程比面向类的编程,耦合少、可维护、可扩展。
在这里插入图片描述
在设计类时只要可能/可行,就应该使用接口而不是类作为类型进行以下声明:
1、 方法形参的类型
2、 局部变量的类型是接口类型
3、(私有)实例字段的类型是接口类型
4、 静态字段的类型是接口类型
5、 方法返回值的类型是接口类型

面向接口编程的优点:
1) 面向接口编程,接口可以被多个不同继承结构中的类来实现。接口类型的形参可以引用不同继承结构中的实现类的实例对象。(面向类编程,基类可以被一个相同继承结构中的类来实现。基类型的形参只可以引用相同继承结构中的派生类的实例对象。并且需要强类型转换)
2) 面向接口编程,编译时接口方法调用代码可以和类完全解耦和(代码中不出现类)。接口变量调用的是接口的方法,而类变量调用的是类的方法。

接口的多态
在这里插入图片描述
接口和抽象类
抽象类:
1、编译器:语法上,告知编译器该类不能new创建实例,只能被当作父类被继承。new该类将语法出错。
2、编程原因:
1)在经过抽象后,生成的类在现实中,不存在该类的实例,即该类的实例在现实中无意义。
2)在应用程序中,不需要生成该类的实例。
3、抽象类作为子类的模版,避免了子类设计的随意性。
抽象方法:
1、编译器:语法上,告知编译器该方法只有声明,而没有实现,类为抽象类。调用该方法将语法出错。
2、编程原因:
1)在该抽象类中,无法实现该方法。即该方法的实现无意义。
2)在应用程序中,不需要调用该类的方法,代码的实现在派生类中。

语法上的抽象类在语义上可以分为:
1、普通抽象类: 含有方法实现或含有实例字段
2、纯抽象类:只含有抽象方法

在这里插入图片描述
应该使用接口的四种情况:
在这里插入图片描述
接口程序
1. 接口声明及接口成员

//1 定义接口
public interface MyInterface {
    // 接口里定义的成员变量只能是静态常量
    //必须进行初始化
    //命名规范,所有字母大写
    //默认:public static final
    int MAX_SIZE = 50;

    // 接口里定义的普通方法只能是public的抽象方法
    //默认:public abstract
    void delMsg();

    // 接口里定义的普通方法只能是public的抽象方法
    //默认:public abstract
    void addMsg(String msg);

    //Java8及其以上版本,允许在接口中定义默认方法
    //不能直接使用接口来调用默认方法,一个接口中可以有多个默认方法
    // 在接口中定义默认方法,需要使用default修饰,带实现的public的接口虚方法
    //默认:public
    default void print(String... msgs) {
        for (String msg : msgs) {
            System.out.println(msg);
        }
    }

    //Java8及其以上版本,允许在接口中定义类方法
    // 在接口中定义类方法,需要使用static修饰
    //默认:public
    static String staticTest() {
        return "接口里的类方法";
    }
}

2. 单个接口的实现---default

//2 实现接口(类--接口的继承)

//2-1 实现单接口---default

//实现接口
public class Main implements MyInterface {

    // 定义个一个字符串数组,长度是接口中定义的常量MAX_SIZE
    private String[] msgs = new String[MyInterface.MAX_SIZE];
    // 记录消息个数
    private int num = 0;

    // 实现接口中的方法
    public void delMsg() {
        if (num <= 0) {
            System.out.println("消息队列已空,删除失败!");
        } else {
            // 删除消息,num数量减1
            msgs[--num] = null;
        }
    }

    // 实现接口中的方法
    public void addMsg(String msg) {
        if (num >= MyInterface.MAX_SIZE) {
            System.out.println("消息队列已满,添加失败!");
        } else {
            // 将消息添加到字符串数组中,num数量加1
            msgs[num++] = msg;
        }
    }

    // 定义一个实现类自己的方法
    public void showMsg() {
        // 输出消息队列中的信息
        for (int i = 0; i < num; i++) {
            System.out.println(msgs[i]);
        }
    }

    public static void main(String[] args) {
        // 实例化一个接口实现类的对象,并将其赋值给一个接口变量引用
        MyInterface mi = new Main();
        // 调用接口的默认方法,默认方法必须通过实例对象来调用
        mi.print("张三", "李四", "王五");
        // 调用接口的类方法,直接通过“接口名.类方法()”来调用
        System.out.println(MyInterface.staticTest());

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

        // 实例化接口实现类
        Main ifd = new Main();
        // 添加信息
        ifd.addMsg("Java 8应用开发");
        ifd.addMsg("欢迎来实训");
        ifd.addMsg("My name's zhaokel");
        ifd.addMsg("这是一个测试");
        // 输出信息
        ifd.showMsg();

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

        // 删除一个信息
        ifd.delMsg();
        System.out.println("删除一个数据后,剩下的信息是:");
        ifd.showMsg();
    }

}

结果:
在这里插入图片描述
这里对接口方法表指针做一个介绍,它 相当于数组,虽然这里仅仅只有一个接口,但我还是演示一下吧

JVM设置接口方法表的时候,要通过接口偏移量表找到所有的接口(相当于把该类或者接口继承的 接口 的逻辑绑在一起)
注意的是接口虚表和类的虚表不在同一数据结构内, 当JVM把接口表设置完成后,要进行接口方法声明和类方法声明的映射(要求有代码实现),如果确认没问题了, 将接口虚表索引与接口方法表指针联系在一起,通过接口方法表指针就能够找到所有继承的接口方法了..

以 ifd.addMsg("Java 8应用开发");
这行代码为例,首先通过ifd引用找到Main类的实现类, 通过接口方法表指针查找接口方法addMsg(),找到之后,通过接口方法和该类实现方法的映射,找到对应的代码,进行执行.
其他的都与此类似.后面相同的问题,也不多说.

3. 多个接口的实现

//2 实现接口(类--接口的继承)
//2-2 实现多接口
// Multiple interfaces.

interface CanFight {
    void fight();
}

interface CanSwim {
    void swim();
}

interface CanFly {
    void fly();
}

class ActionCharacter {
    public void fight() {
        System.out.println("CanFight...");
    }
}

class Hero extends ActionCharacter
        implements CanFight, CanSwim, CanFly {
    public void swim() {
        System.out.println("CanSwim...");
    }

    public void fly() {
        System.out.println("CanFly...");
    }
}

public class Main {
    public static void t(CanFight x) { x.fight(); }
    public static void u(CanSwim x) { x.swim(); }
    public static void v(CanFly x) { x.fly(); }
    public static void w(ActionCharacter x) { x.fight(); }
    public static void main(String[] args) {
        Hero h = new Hero();
        t(h); // Treat it as a CanFight
        u(h); // Treat it as a CanSwim
        v(h); // Treat it as a CanFly
        w(h); // Treat it as an ActionCharacter
    }
}

结果:
在这里插入图片描述
这个例子涉及到多态
Hero extends ActionCharacter implements CanFight, CanSwim, CanFly
因为 ActionCharacter 实现了CanFight方法且能够与继承的接口对象方法形成映射, 无需再次定义.

public static void u(CanSwim x) { x.swim(); } 以这个为例子
传入的参数为Hero
首先确认swim()方法在CanSwim接口中的槽号, 编译器记录槽号, 然后通过接口偏移量表指针找到该接口,记录偏移量,接着通过接口方法表指针访问接口方法表,找到对应的槽号,然后通过映射找到相应的代码,进行执行.

4. 类的派生和接口的重新实现

//2 实现接口(类--接口的继承)
//2-3 重新实现接口--多态
// Multiple interfaces.

interface CanFight {
    void fight();
}

interface CanSwim {
    void swim();
}

interface CanFly {
    void fly();
}

class ActionCharacter
        implements CanFight {

    public void fight() {
        System.out.println("CanFight1...");
    }
}

class Hero extends ActionCharacter
        implements CanSwim, CanFly {

    public void fight() {
        System.out.println("CanFight2...");
    }

    public void swim() {
        System.out.println("CanSwim...");
    }

    public void fly() {
        System.out.println("CanFly...");
    }

}

public class Main {
    public static void t(CanFight x) { x.fight(); }
    public static void u(CanSwim x) { x.swim(); }
    public static void v(CanFly x) { x.fly(); }
    public static void w(ActionCharacter x) { x.fight(); }
    public static void main(String[] args) {
        ActionCharacter a = new ActionCharacter();
        Hero h = new Hero();

        //虚方法多态
        w(a); // Treat it as an ActionCharacter
        w(h); // Treat it as an ActionCharacter

        //接口多态
        t(a); // Treat it as a CanFight
        t(h); // Treat it as a CanFight

        u(h); // Treat it as a CanSwim
        v(h); // Treat it as a CanFly
    }
}

结果:
在这里插入图片描述
5. 接口的继承1

//3 接口的继承(接口--接口的继承)
//3-1

//接口的继承
//第一个接口
interface InterfaceA {
    int V_A = 10;

    void testA();
}

// 第二个接口
interface InterfaceB {
    int V_B = 20;

    void testB();
}

// 第三个接口
interface InterfaceC extends InterfaceA, InterfaceB {
    int V_C = 30;

    void testC();
}

// 实现第三个接口
public class Main implements InterfaceC {
    // 实现三个抽象方法
    public void testA() {
        System.out.println("testA()方法");

    }

    public void testB() {
        System.out.println("testB()方法");

    }

    public void testC() {
        System.out.println("testC()方法");

    }

    public static void main(String[] args) {
        // 使用第三个接口可以直接访问V_A、V_B和V_C常量
        System.out.println(InterfaceC.V_A);
        System.out.println(InterfaceC.V_B);
        System.out.println(InterfaceC.V_C);
        // 声明第三个接口变量,并指向其实现类的实例对象
        InterfaceC ic = new Main();
        // 调用接口中的方法
        ic.testA();
        ic.testB();
        ic.testC();
    }

}

结果:
在这里插入图片描述
6. 接口的继承2

//3 接口的继承(接口--接口的继承)
//3-2 组合接口时的名字冲突

//原因:实现类中不允许出现方法签名相同,而返回类型不同的方法声明。


interface I1 { 
    void f(); 
}
interface I2 { 
    int f(int i); 
}
interface I3 { 
    int f(); 
}

class C { 
    public int f() { 
        return 1; 
    } 
}    //virtual

class C2 implements I1, I2 {
    public void f() {}
    public int f(int i) {
        return 1; 
    }         // overloaded, virtual
}

class C3 extends C implements I2 {
    public int f(int i) { 
        return 1; 
    }         // overloaded, virtual
}

class C4 extends C implements I3 {          //override, reuse
    // Identical, no problem:
    public int f() { 
        return 1; 
    }
}

// Methods differ only by return type:

//! class C5 extends C implements I1 {}

//! interface I4 extends I1, I3 {}

接口并不继承Object的虚方法
接口方法表的设置也相对比较简单

7. 面向接口编程,而不是面向类编程

//优点:
//1、减少了耦和
//2、可维护
//3、可扩展


/*
 * 产品的抽象接口
 */
interface IProduct {
    //获取产品
    String get();
}


//ProductA实现IProduct接口
class ProductA implements IProduct{
    //实现接口中的抽象方法
    public String get() {
        return "ProductA生产完毕!";
    }
}


//ProductB实现IProduct接口
class ProductB implements IProduct {
    // 实现接口中的抽象方法
    public String get() {
        return "ProductB生产完毕!";
    }
}


//ProductC实现IProduct接口
class ProductC implements IProduct{
    //实现接口中的抽象方法
    public String get() {
        return "ProductC生产完毕!";
    }
}


//工厂类
class Factory {
    // 根据客户要求生产产品
    public static IProduct getProduct(String name) {
        IProduct p = null;
        if (name.equals("ProductA")) {
            p = new ProductA();
        } else if (name.equals("ProductB")) {
            p = new ProductB();
        }
        //} else if (name.equals("ProductC")) {
        //p = new ProductB();
        //}
        return p;
    }

}


//具有通用性程序
public class Main {
    public static void main(String[] args) {
        // 客户要求生产ProductA
        IProduct p = Factory.getProduct("ProductA");
        System.out.println(p.get());
        // 客户要求生产ProductB
        p = Factory.getProduct("ProductB");
        System.out.println(p.get());
        // 客户要求生产ProductC
        //p = Factory.getProduct("ProductC");
        //System.out.println(p.get());
    }

}

结果:
在这里插入图片描述

网友评论

登录后评论
0/500
评论
沉晓
+ 关注