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

简介: 探究Java接口的本质

接口是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());
    }

}

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

目录
相关文章
|
10天前
|
算法 Java 数据处理
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。HashSet基于哈希表实现,提供高效的元素操作;TreeSet则通过红黑树实现元素的自然排序,适合需要有序访问的场景。本文通过示例代码详细介绍了两者的特性和应用场景。
31 6
|
10天前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
21 2
|
2天前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
|
2天前
|
SQL 监控 Java
技术前沿:Java连接池技术的最新发展与应用
本文探讨了Java连接池技术的最新发展与应用,包括高性能与低延迟、智能化管理和监控、扩展性与兼容性等方面。同时,结合最佳实践,介绍了如何选择合适的连接池库、合理配置参数、使用监控工具及优化数据库操作,为开发者提供了一份详尽的技术指南。
17 7
|
4天前
|
移动开发 前端开发 Java
过时的Java技术盘点:避免在这些领域浪费时间
【10月更文挑战第14天】 在快速发展的Java生态系统中,新技术层出不穷,而一些旧技术则逐渐被淘汰。对于Java开发者来说,了解哪些技术已经过时是至关重要的,这可以帮助他们避免在这些领域浪费时间,并将精力集中在更有前景的技术上。本文将盘点一些已经或即将被淘汰的Java技术,为开发者提供指导。
31 7
|
2天前
|
Java 数据库连接 数据库
优化之路:Java连接池技术助力数据库性能飞跃
在Java应用开发中,数据库操作常成为性能瓶颈。频繁的数据库连接建立和断开增加了系统开销,导致性能下降。本文通过问题解答形式,深入探讨Java连接池技术如何通过复用数据库连接,显著减少连接开销,提升系统性能。文章详细介绍了连接池的优势、选择标准、使用方法及优化策略,帮助开发者实现数据库性能的飞跃。
14 4
|
1天前
|
Java
Java基础(13)抽象类、接口
本文介绍了Java面向对象编程中的抽象类和接口两个核心概念。抽象类不能被实例化,通常用于定义子类的通用方法和属性;接口则是完全抽象的类,允许声明一组方法但不实现它们。文章通过代码示例详细解析了抽象类和接口的定义及实现,并讨论了它们的区别和使用场景。
|
1天前
|
Java 测试技术 API
Java零基础-接口详解
【10月更文挑战第19天】Java零基础教学篇,手把手实践教学!
9 1
|
2天前
|
SQL Java 数据库连接
打破瓶颈:利用Java连接池技术提升数据库访问效率
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,避免了频繁的连接建立和断开,显著提升了数据库访问效率。常见的连接池库包括HikariCP、C3P0和DBCP,它们提供了丰富的配置选项和强大的功能,帮助优化应用性能。
16 2
|
4天前
|
前端开发 Java API
过时Java技术的退役:这些技能你不再需要掌握!
【10月更文挑战第22天】 在快速变化的技术领域,一些曾经流行的Java技术已经逐渐被淘汰,不再适用于现代软件开发。了解这些过时的技术对于新手开发者来说尤为重要,以避免浪费时间和精力学习不再被行业所需的技能。本文将探讨一些已经或即将被淘汰的Java技术,帮助你调整学习路径,专注于那些更有价值的技术。
15 1