Java-接口

发布于:2025-02-10 ⋅ 阅读:(46) ⋅ 点赞:(0)

目录

一、接口的定义与基本结构

二、使用接口类型也可以定义引用。

三、抽象类是半抽象的,接口是完全抽象的。接口没有构造方法,也无法实例化

1. 抽象类(半抽象)

2. 接口(完全抽象 → 逐渐演变)

四、接口的特性

五、 接口的继承

六、接口与多态机制

1.接口不能实例化

核心解释

2.为什么“接口和多态联合使用可以降低耦合度”?

关键机制

代码详细解释

七、接口的Java8和Java9新特性

1. Java 8 之后接口的新特性

2. Java 9 之后接口的新特性

代码解释

八、 接口与 Object 类的关系

九、接口的作用

十、接口与抽象类如何选择

1.从代码复用角度选择

2.从功能扩展角度选择

3.从设计理念角度选择

十一、类 转换成 接口的时候,类和接口之间不需要有继承关系,编译器也不会报错。

1. 向上转型(Upcasting)

2. 向下转型(Downcasting)

场景 1:转回原始实现类

场景 2:转换为其他接口(需多重实现)

场景 3:危险的不安全转型

3.接口间的横向转型


接口(interface)在Java中表示一种规范或契约,它定义了一组抽象方法和常量,用来描述一些实现这个接口的类应该具有哪些行为和属性接口和类一样,也是一种引用数据类型。

接口编译完也是.class文件

一、接口的定义与基本结构

语法格式:

[访问修饰符] interface 接口名 {
    // 常量
    // 抽象方法
    // 默认方法(JDK8+)
    // 静态方法(JDK8+)
    // 私有方法(JDK9+)
}

修饰符列表:通常为 public,表示该接口可以被任何类访问。如果省略修饰符,接口将具有包访问权限,即只能在同一个包内被访问。

接口名:遵循 Java 的命名规范,通常采用大写字母开头的驼峰命名法。

示例:

public interface Animal {
    // 常量(public static final 可省略)
    String TYPE = "生物";  // 等同于 public static final String TYPE = "生物";
    
    // 抽象方法(public abstract 可省略)
    void eat();          // 等同于 public abstract void eat();
    
    // 默认方法(JDK8+)
    default void breathe() {
        System.out.println("呼吸氧气");
    }
    
    // 静态方法(JDK8+)
    static void showType() {
        System.out.println("类型:" + TYPE);
    }
    
    // 私有方法(JDK9+)
    private void logAction(String action) {
        System.out.println("动作记录:" + action);
    }
}

二、使用接口类型也可以定义引用。

接口Myinterface

public interface Myinterface {
}

MyinterfaceTest类:

public class MyinterfaceTest {
    public static void main(String[] args) {
        // 这只是一个变量。是一个引用。
        // 使用接口类型也可以定义引用。
        Myinterface myinterface = null;

    }
}

三、抽象类是半抽象的,接口是完全抽象的。接口没有构造方法,也无法实例化

1. 抽象类(半抽象)

  • 定义:用 abstract 声明的类,可以包含抽象方法(无实现)和具体方法(有实现)。

  • 核心特性

    • 构造方法:可以有构造方法(供子类调用,用于初始化抽象类中的状态)。

    • 实例化:不能直接实例化,但可以通过子类间接实例化。

    • 状态(字段):可以包含实例变量和非静态字段。

    • 方法:可以同时包含抽象方法和非抽象方法。

    • 继承关系:单继承(一个类只能继承一个抽象类)。

  • 设计目的:为子类提供共性代码复用部分实现,适合定义“是什么”(is-a关系)。

示例

abstract class Animal {
    private String name; // 实例变量
    
    public Animal(String name) {
        this.name = name; // 构造方法
    }
    
    public abstract void makeSound(); // 抽象方法
    
    public void sleep() { // 具体方法
        System.out.println(name + " is sleeping.");
    }
}

class Dog extends Animal {
    public Dog(String name) {
        super(name); // 调用父类构造方法
    }
    
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

2. 接口(完全抽象 → 逐渐演变)

  • 定义:用 interface 声明的类型,定义行为规范。

  • 核心特性

    • 构造方法:无构造方法(因为不管理实例状态)。

    • 实例化:不能直接实例化,只能通过实现类实例化。

    • 状态(字段):字段默认是 public static final(常量)。

    • 方法

      • Java 8 之前:所有方法都是抽象的(无实现)。

      • Java 8+:支持 default 方法(默认实现)和 static 方法。

    • 继承关系:多继承(一个类可以实现多个接口)。

  • 设计目的:定义“能做什么”(can-do关系),强调行为契约

示例

interface Flyable {
    // 常量
    String UNIT = "meters";
    
    // 抽象方法(隐式 public abstract)
    void fly();
    
    // 默认方法(Java 8+)
    default void glide() {
        System.out.println("Gliding through the air.");
    }
    
    // 静态方法(Java 8+)
    static void printUnit() {
        System.out.println("Unit: " + UNIT);
    }
}

class Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("Bird is flying.");
    }
}

四、接口的特性

  • 完全抽象:接口是完全抽象的,没有构造方法,不能被实例化。它只定义了行为的规范,而不包含实现细节。
  • 成员类型
    • 常量:接口中定义的常量默认是 public static final 的,可以省略这些修饰符。例如:int MAX_SPEED = 100; 等同于 public static final int MAX_SPEED = 100;
    • 抽象方法:接口中的抽象方法默认是 public abstract 的,也可以省略这些修饰符。例如:void run(); 等同于 public abstract void run();

五、 接口的继承

接口和接口之间可以多继承,即一个接口可以继承多个接口。语法如下:

interface SubInterface extends Interface1, Interface2 {
    // 接口体
}

例如:

interface Flyable {
    void fly();
}

interface Swimmable {
    void swim();
}

interface Amphibious extends Flyable, Swimmable {
    // Amphibious接口继承了Flyable和Swimmable接口的抽象方法
}

①类通过 implements 关键字实现接口,一个非抽象类实现接口时,必须实现接口中所有的抽象方法。语法如下:

//一个非抽象的类实现接口必须将接口中所有的抽象方法全部实现。
//(强制要求的,必须的,要不然编译器报错。)

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

②实现多个接口
一个类可实现多个接口,解决单继承限制。

//一个非抽象的类实现接口必须将接口中所有的抽象方法全部实现。
//(强制要求的,必须的,要不然编译器报错。)
public class Bird implements Flyable, Singable {
    @Override
    public void fly() { /* 实现 Flyable 接口的方法 */ }
    
    @Override
    public void sing() { /* 实现 Singable 接口的方法 */ }
}

③默认方法冲突解决
若多个接口有同名默认方法,实现类必须重写该方法。

interface A {
    default void action() { System.out.println("A 的动作"); }
}
interface B {
    default void action() { System.out.println("B 的动作"); }
}
class C implements A, B {
    @Override
    public void action() {
        A.super.action();  // 显式调用 A 的默认方法
    }
}

六、接口与多态机制

接口A:

public interface A {
    void a();
}

接口B:


public interface B {
    void b();
}

接口C:


public interface C extends A,B{
    void c();
}

接口Myinterface:


public interface Myinterface {
    public static final int num1 = 1;
    int num2 = 2;

    public abstract void m1();

    void m2();

}

类MyInterfaceImpl:

class MyInterfaceImpl implements Myinterface,C {

    /*@Override
    public void defaultMethod() {
        System.out.println("MyInterfaceImpl的默认方法执行了。");
    }*/

    @Override
    public void m1() {
        System.out.println("m1执行了");
    }

    @Override
    public void m2() {
        System.out.println("m2执行了");
    }

    @Override
    public void a() {
        System.out.println("a执行了");
    }

    @Override
    public void b() {
        System.out.println("b执行了");
    }

    @Override
    public void c() {
        System.out.println("c执行了");
    }
}

测试类MyinterfaceTest:

public class MyinterfaceTest {
    public static void main(String[] args) {
        // 这只是一个变量。是一个引用。
        // 使用接口类型也可以定义引用。
        Myinterface myinterface = null;

        // 使用了接口之后,为了降低程序的耦合度,一定要让接口和多态联合起来使用。
        // 父类型的引用指向子类型的对象
        Myinterface mi = new MyInterfaceImpl();
        mi.a();  //A接口特有方法
        mi.b();
        mi.c();

        // 面向接口去调用的。
        mi.m1();   //Myinterface接口特有方法
        mi.m2();
    }
}

1.接口不能实例化

核心解释

  1. 接口不能直接实例化
    接口本身没有构造方法,且是抽象的,因此无法直接通过 new 接口名() 创建对象。例如以下代码会编译失败:

    Myinterface mi = new Myinterface(); // 编译错误:接口无法实例化
  2. 接口引用可以指向实现类的对象
    接口类型的变量(如 Myinterface mi)可以指向实现了该接口的类的实例对象。此时:

    • 本质是多态:父类型引用指向子类对象。

    • 行为约束:通过接口引用只能调用接口中定义的方法,无法直接访问实现类的特有方法。

      Myinterface mi = new MyInterfaceImpl();  // 合法:接口引用指向实现类对象
      mi.m1();   // 调用接口中定义的抽象方法
      // mi.a(); // 编译错误:Myinterface 接口中没有定义 a() 方法


2.为什么“接口和多态联合使用可以降低耦合度”?

关键机制

  1. 依赖抽象,而非具体实现

    • 低耦合设计:客户端代码(如 MyinterfaceTest)只需依赖接口 Myinterface,而无需关心具体实现类(如 MyInterfaceImpl)。

    • 可替换性:可以随时替换实现类(例如新增 MyInterfaceImpl2),而无需修改客户端代码。

    示例

// 客户端代码只依赖接口
Myinterface mi = new MyInterfaceImpl();
mi.m1();

// 切换实现类时,客户端代码无需改动
mi = new MyInterfaceImpl2();
mi.m1();

      2.多态的动态绑定

  • 运行时行为:JVM 根据实际对象类型决定调用哪个方法。

  • 灵活扩展:新增实现类时,只需确保实现接口契约,原有代码逻辑不受影响。

示例

接口定义:

interface Payment {
    void pay();
}

这里定义了一个 Payment 接口,它包含一个抽象方法 pay()。此接口规定了任何实现它的类都必须提供支付的具体实现,定义了一种支付行为的规范。

接口实现:

class Alipay implements Payment {
    @Override
    public void pay() {
        System.out.println("支付宝支付");
    }
}

class WechatPay implements Payment {
    @Override
    public void pay() {
        System.out.println("微信支付");
    }
}

Alipay 类和 WechatPay 类都实现了 Payment 接口,并重写了 pay() 方法,分别实现了各自的支付逻辑,这是对接口规范的具体实现。

多态应用:

public class PaymentService {
    public void process(Payment payment) {
        payment.pay();  // 动态绑定到具体实现类的方法
    }

    public static void main(String[] args) {
        PaymentService service = new PaymentService();
        service.process(new Alipay());    // 输出 "支付宝支付"
        service.process(new WechatPay()); // 输出 "微信支付"
    }
}
  • 在 PaymentService 类的 process 方法中,参数类型为 Payment 接口类型。当调用 payment.pay() 时,由于多态的特性,程序会在运行时根据传入对象的实际类型(Alipay 或 WechatPay)来动态调用相应类中重写的 pay() 方法。
  • 在 main 方法中,分别创建了 Alipay 和 WechatPay 对象,并将它们作为参数传递给 process 方法,展示了多态在实际代码中的灵活应用,增强了代码的可扩展性和可维护性。

代码详细解释

1. public void process(Payment payment) { payment.pay(); }

这是 PaymentService 类中的一个方法,名为 process。它接收一个类型为 Payment 的参数 payment。这里体现了多态性,因为 Payment 是一个接口,所以 payment 参数可以引用任何实现了 Payment 接口的类的对象。在方法内部调用 payment.pay() 时,程序会根据 payment 实际引用的对象类型(也就是具体的实现类)来调用相应的 pay 方法,这就是动态绑定。

2. PaymentService service = new PaymentService();

这行代码创建了一个 PaymentService 类的实例对象,并将其引用赋值给变量 servicePaymentService 类的作用是提供一个处理支付的服务,它本身不代表具体的支付方式。

3. service.process(new Alipay());

这里调用了 service 对象的 process 方法,并将一个新创建的 Alipay 对象作为参数传入。由于 Alipay 类实现了 Payment 接口,所以它可以作为 process 方法的参数。当 process 方法内部调用 payment.pay() 时,实际上调用的是 Alipay 类中重写的 pay 方法,因此会输出 “支付宝支付”。

4. service.process(new WechatPay());

同理,这行代码创建了一个 WechatPay 对象,并将其作为参数传递给 process 方法。在 process 方法中,会调用 WechatPay 类中重写的 pay 方法,从而输出 “微信支付”。


Myinterface mi = new MyInterfaceImpl(); 与 PaymentService service = new PaymentService(); service.process(new Alipay()); service.process(new WechatPay()); 在代码功能、设计模式运用和多态体现等方面存在明显区别,下面为你详细分析:

代码功能层面

Myinterface mi = new MyInterfaceImpl();

这行代码主要是利用接口和多态创建对象并赋值给接口类型的引用。Myinterface 是一个接口,MyInterfaceImpl 是实现了该接口的类。通过 Myinterface mi = new MyInterfaceImpl(); 这样的语句,创建了 MyInterfaceImpl 类的一个实例,并且将这个实例的引用赋值给 Myinterface 类型的变量 mi。之后可以通过 mi 调用 Myinterface 接口中定义的方法,以及接口 C 继承自 A 和 B 接口的方法。其重点在于使用接口类型的引用操作实现类的对象,展示了接口与实现类之间的关系以及多态的基本使用。

PaymentService service = new PaymentService(); service.process(new Alipay()); service.process(new WechatPay());

这部分代码构建了一个服务类来处理不同的支付方式。PaymentService 是一个服务类,它有一个 process 方法,该方法接收一个实现了 Payment 接口的对象作为参数。首先创建了 PaymentService 类的实例 service,然后分别创建 Alipay 和 WechatPay 对象并将它们传递给 service 的 process 方法。这里的重点是通过服务类来统一处理不同的支付逻辑,利用多态性让服务类能够根据传入的不同实现类对象执行相应的支付操作,更侧重于业务逻辑的处理和服务的提供。

设计模式运用层面

Myinterface mi = new MyInterfaceImpl();

主要体现了面向接口编程的思想,将接口和实现分离,提高了代码的可扩展性和可维护性。如果后续需要添加新的实现类,只需要让新类实现 Myinterface 接口并实现其中的方法,而不需要修改现有的代码逻辑。

PaymentService service = new PaymentService(); service.process(new Alipay()); service.process(new WechatPay());

体现了策略模式的思想。Payment 接口定义了支付的策略,Alipay 和 WechatPay 是具体的策略实现类,PaymentService 类作为上下文,根据传入的不同策略对象(支付方式)执行相应的操作。这种设计模式使得支付方式的添加和修改变得更加灵活,符合开闭原则。

多态体现层面

Myinterface mi = new MyInterfaceImpl();

多态体现在通过接口类型的引用调用实现类的方法。mi 虽然是 Myinterface 类型的引用,但实际指向的是 MyInterfaceImpl 对象,在调用方法时会根据实际对象类型调用相应的实现。例如 mi.m1() 会调用 MyInterfaceImpl 类中重写的 m1 方法。

PaymentService service = new PaymentService(); service.process(new Alipay()); service.process(new WechatPay());

多态体现在 process 方法接收 Payment 接口类型的参数。当传入 Alipay 或 WechatPay 对象时,process 方法内部调用 payment.pay() 会根据传入对象的实际类型动态调用相应类的 pay 方法,实现了不同支付方式的动态切换。


将 PaymentService 相关代码改写成类似 Myinterface mi = new MyInterfaceImpl() 的形式,不过改写后的侧重点会有所不同。以下是具体分析和改写示例:

原代码逻辑回顾

在原代码中,PaymentService 类起到一个服务调度的作用,它提供了一个 process 方法来处理不同的支付逻辑,通过传入实现了 Payment 接口的具体支付类对象,实现不同的支付功能。这体现了策略模式的思想,将支付的逻辑和服务调度分离。

改写思路

若要改成类似 Myinterface mi = new MyInterfaceImpl() 的形式,我们可以直接使用 Payment 接口类型的引用指向具体的支付类对象,然后直接调用这些对象的 pay 方法,而不借助 PaymentService 类进行调度。

改写后的代码示例

// 定义 Payment 接口
interface Payment {
    void pay();
}

// 实现支付宝支付
class Alipay implements Payment {
    @Override
    public void pay() {
        System.out.println("支付宝支付");
    }
}

// 实现微信支付
class WechatPay implements Payment {
    @Override
    public void pay() {
        System.out.println("微信支付");
    }
}

public class PaymentTest {
    public static void main(String[] args) {
        // 使用 Payment 接口类型的引用指向 Alipay 对象
        Payment alipay = new Alipay();
        // 调用支付方法
        alipay.pay();

        // 使用 Payment 接口类型的引用指向 WechatPay 对象
        Payment wechatPay = new WechatPay();
        // 调用支付方法
        wechatPay.pay();
    }
}

代码解释

  • 接口和实现类Payment 接口定义了支付的行为规范,Alipay 和 WechatPay 类分别实现了该接口,并提供了具体的支付逻辑。
  • 多态的使用:在 main 方法中,分别创建了 Alipay 和 WechatPay 对象,并将它们赋值给 Payment 接口类型的引用变量 alipay 和 wechatPay。通过这些接口引用调用 pay 方法时,会根据实际对象的类型调用相应的实现。

与原代码的对比

  • 原代码:使用 PaymentService 类作为服务调度中心,将支付逻辑和服务调度分离,增强了代码的可维护性和可扩展性。如果后续需要添加新的支付方式,只需要在 PaymentService 类中添加相应的处理逻辑,而不需要修改其他代码。
  • 改写后的代码:直接使用接口引用调用具体实现类的方法,代码更加简洁,但缺少了服务调度的功能。如果需要添加新的支付方式,需要在调用处直接添加新的对象创建和方法调用代码,可维护性和扩展性相对较差。

综上所述,改写后的代码虽然在形式上与 Myinterface mi = new MyInterfaceImpl() 类似,但在设计和功能上与原代码有所不同,需要根据具体的业务需求选择合适的实现方式。


七、接口的Java8和Java9新特性

1. Java 8 之后接口的新特性

  • 默认方法:为了解决接口演变问题,Java 8 引入了默认方法。默认方法有方法体,实现类可以选择是否重写该方法。默认方法使用 default 关键字修饰。例如:
    interface Vehicle {
        void start();
    
        default void stop() {
            System.out.println("The vehicle is stopped.");
        }//该方法可重写,也可不重写
    }
    
    class Car implements Vehicle {
        @Override
        public void start() {
            System.out.println("The car is started.");
        }
    }

    在上述例子中,Car 类实现了 Vehicle 接口,但没有重写 stop 方法,它将使用接口中定义的默认实现。

  • 静态方法:Java 8 还允许在接口中定义静态方法。静态方法只能通过接口名调用,不能通过实现类的类名调用。例如:
    interface MathUtils {
        static int add(int a, int b) {
            return a + b;
        }
    }
    
    // 调用方式
    int result = MathUtils.add(3, 5); 

2. Java 9 之后接口的新特性

  • 私有实例方法:为了支持默认方法,Java 9 允许在接口中定义私有的实例方法。这些方法只能在接口内部的默认方法或其他私有方法中调用。例如:
    interface Utility {
        default void performTask() {
            setup();
            execute();
            cleanUp();
        }
    
        private void setup() {
            System.out.println("Setting up...");
        }
    
        private void execute() {
            System.out.println("Executing task...");
        }
    
        private void cleanUp() {
            System.out.println("Cleaning up...");
        }
    }

  • 私有静态方法:Java 9 也允许在接口中定义私有的静态方法,用于支持接口中的静态方法。例如:
    interface MathHelper {
        static int multiply(int a, int b) {
            return multiplyHelper(a, b);
        }
    
        private static int multiplyHelper(int a, int b) {
            return a * b;
        }
    }

注意:JDK9之后允许接口中定义私有的实例方法(为默认方法服务的)和私有的静态方法(为静态方法服务的)

// 定义一个接口
interface MyInterface {
    // 抽象方法,实现类必须实现该方法
    void abstractMethod();

    // 默认方法,它可以有具体的实现,实现类可以选择重写该方法
    default void defaultMethod() {
        // 调用私有实例方法,用于复用代码
        privateInstanceMethod();
        System.out.println("这是一个默认方法。");
    }

    // 静态方法,可以直接通过接口名调用
    static void staticMethod() {
        // 调用私有静态方法,用于复用代码
        privateStaticMethod();
        System.out.println("这是一个静态方法。");
    }

    // 私有实例方法,只能在接口内部被调用,为默认方法服务
    private void privateInstanceMethod() {
        System.out.println("这是一个私有实例方法,为默认方法服务。");
    }

    // 私有静态方法,只能在接口内部被调用,为静态方法服务
    private static void privateStaticMethod() {
        System.out.println("这是一个私有静态方法,为静态方法服务。");
    }
}

// 实现接口的类
class MyClass implements MyInterface {
    @Override
    public void abstractMethod() {
        System.out.println("正在实现抽象方法。");
    }
}

// 测试类
public class Main {
    public static void main(String[] args) {
        // 创建实现类的对象
        MyClass myClass = new MyClass();
        // 调用抽象方法
        myClass.abstractMethod();
        // 调用默认方法
        myClass.defaultMethod();
        // 调用接口的静态方法
        MyInterface.staticMethod();
    }
}

代码解释

  1. 接口 MyInterface

    • abstractMethod():这是一个抽象方法,任何实现该接口的类都必须提供其具体实现。
    • defaultMethod():默认方法,它有具体的实现代码。在这个方法中,调用了 privateInstanceMethod() 来复用代码。
    • staticMethod():静态方法,可以直接通过接口名调用。在这个方法中,调用了 privateStaticMethod() 来复用代码。
    • privateInstanceMethod():私有实例方法,只能在接口内部被调用,专门为默认方法服务,用于封装一些通用的逻辑。
    • privateStaticMethod():私有静态方法,只能在接口内部被调用,专门为静态方法服务,用于封装一些通用的逻辑。
  2. 类 MyClass
    该类实现了 MyInterface 接口,并重写了 abstractMethod() 方法,提供了具体的实现。

  3. 测试类 Main
    在 main 方法中,创建了 MyClass 的对象,并依次调用了抽象方法、默认方法和接口的静态方法。

运行结果为:

八、 接口与 Object 类的关系

所有接口都隐式地继承 Object 类,因此接口可以调用 Object 类的部分方法,如 equals(Object obj)hashCode() 和 toString() 等。但需要注意的是,接口本身不能重写这些方法,只有实现接口的类可以重写。例如:

interface Printable {
    void print();
}

class Document implements Printable {
    @Override
    public void print() {
        System.out.println("Printing document...");
    }

    @Override
    public String toString() {
        return "This is a document.";
    }
}

九、接口的作用

Computer类:

public class Computer {
    public void conn(HardDrive hardDrive){
        System.out.println("链接数据成功");
        hardDrive.read();
        hardDrive.write();
    }
}

HardDrive类:

//硬盘类
public class HardDrive {
    public void read(){
        System.out.println("硬盘开始读数据");
    }
    public void write(){
        System.out.println("硬盘开始写数据");
    }

}

测试类Test:

public class Test {
    public static void main(String[] args) {
        //创建硬盘对象
        HardDrive hardDrive=new HardDrive();

        //创建电脑对象
        Computer computer=new Computer();

        //电脑连接硬盘
        computer.conn(hardDrive);
    }
}

运行结果:

上述代码中如果要新增Printer(打印机)类,增加新类不违背OcP原则,但需修改Computer类和Test测试类,违背了OCP原则

Computer类:

public class Computer {
    public void conn(HardDrive hardDrive){
        System.out.println("链接数据成功");
        hardDrive.read();
        hardDrive.write();
        
    }
    public void conn(Printer printer) {
        System.out.println("连接设备成功");
        printer.read();
        printer.write();}
}

Printer类:

/**
 * 打印机类
 */
public class Printer {

    public void read(){
        System.out.println("打印机开始读取数据");
    }

    public void write(){
        System.out.println("打印机开始打印文件");
    }
}

Test类:

public class Test {
    public static void main(String[] args) {
        //创建硬盘对象
        HardDrive hardDrive=new HardDrive();

        //创建电脑对象
        Computer computer=new Computer();

        //电脑连接硬盘
        computer.conn(hardDrive);


        // 创建打印机对象
        Printer printer = new Printer();
        // 电脑连接打印机
        computer.conn(printer);
    }
}

运行结果:

①面向接口调用的称为:接口调用者

②面向接口实现的称为:接口实现者

③调用者和实现者通过接口达到了解耦合。也就是说调用者不需要关心具体的实现者,实现者也不需要关心具体的调用者,双方都遵循规范,面向接口进行开发。

④面向抽象编程,面向接口编程,可以降低程序的耦合度,提高程序的扩展力。

  1. 接口作用的深入理解
    • 解耦调用者和实现者:接口就像一个契约,明确了一组行为的规范。在 Computer 和 Usb 的例子中,Computer 类作为调用者,只关心 Usb 接口提供的 read() 和 write() 方法,并不关心具体是打印机还是硬盘在实现这些方法。同样,打印机和硬盘作为实现者,只需要按照 Usb 接口的规范实现 read() 和 write() 方法,不需要关心谁在调用它们。这种方式使得调用者和实现者可以独立开发和修改,而不会相互影响,大大降低了程序的耦合度。
    • 提高程序扩展性:如果后续要添加新的 Usb 设备,比如 Scanner(扫描仪),只需要让 Scanner 类实现 Usb 接口,实现 read() 和 write() 方法,Computer 类的 conn 方法无需修改,就能与新的设备进行交互。这体现了接口在提高程序扩展力方面的重要作用。
// Usb接口
interface Usb {
    void read();
    void write();
}

// Printer类实现Usb接口
class Printer implements Usb {
    @Override
    public void read() {
        System.out.println("Printer is reading data.");
    }

    @Override
    public void write() {
        System.out.println("Printer is writing data.");
    }
}

// HardDrive类实现Usb接口
class HardDrive implements Usb {
    @Override
    public void read() {
        System.out.println("HardDrive is reading data.");
    }

    @Override
    public void write() {
        System.out.println("HardDrive is writing data.");
    }
}

// Computer类,接口调用者
class Computer {
    public void conn(Usb usb) {
        System.out.println("连接设备成功");
        usb.read();
        usb.write();
    }
}

public class UsbExample {
    public static void main(String[] args) {
        Computer computer = new Computer();
        Usb printer = new Printer();
        Usb hardDrive = new HardDrive();

        computer.conn(printer);
        computer.conn(hardDrive);
    }
}

再想想,我们平时去饭店吃饭,这个场景中有没有接口呢?食谱菜单就是接口。顾客是调用者。厨师是实现者。

示例代码1:

// 定义食谱菜单接口
interface Menu {
    void cook();
}

// 厨师类,实现食谱菜单接口
class Chef implements Menu {
    private String dish;

    public Chef(String dish) {
        this.dish = dish;
    }

    @Override
    public void cook() {
        System.out.println("厨师正在烹饪 " + dish);
    }
}

// 顾客类,作为接口调用者
class Customer {
    public void order(Menu menu) {
        menu.cook();
    }
}

public class RestaurantScenario {
    public static void main(String[] args) {
        Customer customer = new Customer();
        // 顾客点宫保鸡丁           //对象传给接口引用
        Menu gongbaoChicken = new Chef("宫保鸡丁");//传数据
        customer.order(gongbaoChicken);          //输出

        // 顾客点鱼香肉丝             //对象传给接口引用
        Menu yuxiangShreddedPork = new Chef("鱼香肉丝");
        customer.order(yuxiangShreddedPork);
    }
}

代码分析

  • 接口定义Menu 是一个接口,它定义了一个抽象方法 cook(),此方法代表了烹饪菜品的规范,任何实现该接口的类都必须提供这个方法的具体实现。
  • 实现类Chef 类实现了 Menu 接口,这意味着 Chef 类必须实现 Menu 接口中定义的 cook() 方法。在 Chef 类的构造方法中,接收一个菜品名称作为参数,然后在 cook() 方法里输出正在烹饪该菜品的信息。
  • 多态的应用Menu yuxiangShreddedPork = new Chef("鱼香肉丝"); 这行代码创建了一个 Chef 类的对象,并且将这个对象的引用赋值给了 Menu 接口类型的变量 yuxiangShreddedPork。这里体现了多态,因为 yuxiangShreddedPork 虽然是 Menu 类型的引用,但实际上指向的是一个 Chef 对象。这样做的好处是,在后续的代码中,我们可以通过 Menu 接口类型的引用调用 cook() 方法,而具体调用的是 Chef 类中实现的 cook() 方法。
  • 调用方法:在 Customer 类的 order 方法中,接收一个 Menu 类型的参数,然后调用该参数的 cook() 方法。由于传入的实际对象是 Chef 类的实例,所以会执行 Chef 类中重写的 cook() 方法。

示例代码2:

1. FoodMenu 接口

/**
 * 食谱菜单
 * 该接口定义了饭店提供的菜品列表,任何实现该接口的厨师类都需要实现这些菜品的制作方法
 */
public interface FoodMenu {
    // 定义制作西红柿炒蛋的方法
    void xiHongShiChaoDan();
    // 定义制作鱼香肉丝的方法
    void yuXiangRouSi();
    // 定义制作油泼面的方法
    void youPoMian();
}

这个接口定义了三个抽象方法,分别对应西红柿炒蛋、鱼香肉丝和油泼面的制作。任何实现 FoodMenu 接口的类都必须实现这三个方法。

2. ShanDongCooker 类

/**
 * 山东厨师
 * 接口的实现者,负责实现 FoodMenu 接口中定义的菜品制作方法
 */
public class ShanDongCooker implements FoodMenu{
    // 厨师的名字
    private String name;

    // 构造方法,用于初始化厨师的名字
    public ShanDongCooker(String name) {
        this.name = name;
    }

    // 获取厨师名字的方法
    public String getName() {
        return name;
    }

    // 设置厨师名字的方法
    public void setName(String name) {
        this.name = name;
    }

    // 实现 FoodMenu 接口中制作西红柿炒蛋的方法
    @Override
    public void xiHongShiChaoDan() {
        System.out.println(this.getName() + "做的西红柿炒蛋");
    }

    // 实现 FoodMenu 接口中制作鱼香肉丝的方法
    @Override
    public void yuXiangRouSi() {
        System.out.println(this.getName() + "做的鱼香肉丝");
    }

    // 实现 FoodMenu 接口中制作油泼面的方法
    @Override
    public void youPoMian() {
        System.out.println(this.getName() + "做的油泼面");
    }
}

ShanDongCooker 类实现了 FoodMenu 接口,代表山东厨师。它有一个私有属性 name 表示厨师的名字,通过构造方法进行初始化。重写了接口中的三个方法,在每个方法中输出该厨师制作相应菜品的信息。

3. ShanXiCooker 类

/**
 * 陕西厨师
 * 接口的实现者,负责实现 FoodMenu 接口中定义的菜品制作方法
 */
public class ShanXiCooker implements FoodMenu{
    // 厨师的名字
    private String name;

    // 构造方法,用于初始化厨师的名字
    public ShanXiCooker(String name) {
        this.name = name;
    }

    // 获取厨师名字的方法
    public String getName() {
        return name;
    }

    // 设置厨师名字的方法
    public void setName(String name) {
        this.name = name;
    }

    // 实现 FoodMenu 接口中制作西红柿炒蛋的方法
    @Override
    public void xiHongShiChaoDan() {
        System.out.println(this.getName() + "做的西红柿炒蛋");
    }

    // 实现 FoodMenu 接口中制作鱼香肉丝的方法
    @Override
    public void yuXiangRouSi() {
        System.out.println(this.getName() + "做的鱼香肉丝");
    }

    // 实现 FoodMenu 接口中制作油泼面的方法
    @Override
    public void youPoMian() {
        System.out.println(this.getName() + "做的油泼面");
    }
}

ShanXiCooker 类同样实现了 FoodMenu 接口,代表陕西厨师。与 ShanDongCooker 类类似,也有一个私有属性 name 表示厨师的名字,通过构造方法进行初始化,并重写了接口中的三个方法。

4. Customer 类

/**
 * 顾客面向菜单点菜。
 * 该类是接口的调用者,通过传入 FoodMenu 接口类型的参数来点菜,不关心具体是哪个厨师来做菜
 */
public class Customer {
    // 点菜方法,接收一个 FoodMenu 接口类型的参数
    public void order(FoodMenu foodMenu){
        // 调用接口中的方法来点菜,体现了面向接口编程的思想
        // 降低了程序的耦合度,提高了程序的扩展力
        foodMenu.xiHongShiChaoDan();
        foodMenu.yuXiangRouSi();
        foodMenu.youPoMian();
    }
}

Customer 类是接口的调用者,它有一个 order 方法,接收一个 FoodMenu 接口类型的参数。在 order 方法中,调用了接口中的三个方法来点菜,体现了面向接口编程的思想,降低了顾客和厨师之间的耦合度。

5. Test 类

public class Test {
    public static void main(String[] args) {
        // 创建山东厨师对象,并传入厨师的名字
        ShanDongCooker shanDongCooker = new ShanDongCooker("山东爷们");
        // 创建顾客对象
        Customer customer = new Customer();
        // 顾客让山东厨师做菜
        customer.order(shanDongCooker);

        // 创建陕西的厨师对象,并传入厨师的名字
        ShanXiCooker shanXiCooker = new ShanXiCooker("陕西大汉");
        // 顾客让陕西厨师做菜
        customer.order(shanXiCooker);
    }
}

Test 类是程序的入口,在 main 方法中,首先创建了一个 ShanDongCooker 对象和一个 Customer 对象,然后调用 Customer 对象的 order 方法,让山东厨师做菜。接着创建了一个 ShanXiCooker 对象,再次调用 Customer 对象的 order 方法,让陕西厨师做菜。

运行结果:

十、接口与抽象类如何选择

1.从代码复用角度选择

适用抽象类的情况

当多个类之间存在一些共同的属性和方法,并且希望实现代码复用,同时部分方法的具体实现可能因子类而异时,使用抽象类是一个不错的选择。抽象类可以包含具体的方法实现,这些实现可以被所有子类共享。例如,在一个图形系统中,有圆形、矩形、三角形等图形类,它们都有一些共同的属性(如颜色)和方法(如获取颜色),同时也有各自独特的属性和方法(如圆形有半径,矩形有长和宽)。此时可以创建一个抽象的 Shape 类:

// 抽象类 Shape
abstract class Shape {
    protected String color;

    public Shape(String color) {
        this.color = color;
    }

    // 公共方法,可被所有子类共享
    public String getColor() {
        return color;
    }

    // 抽象方法,需要子类具体实现
    public abstract double area();
}

// 圆形类继承自 Shape 抽象类
class Circle extends Shape {
    private double radius;

    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

// 矩形类继承自 Shape 抽象类
class Rectangle extends Shape {
    private double length;
    private double width;

    public Rectangle(String color, double length, double width) {
        super(color);
        this.length = length;
        this.width = width;
    }

    @Override
    public double area() {
        return length * width;
    }
}

在上述代码中,Shape 抽象类提取了所有图形类的公共属性 color 和公共方法 getColor(),同时定义了抽象方法 area(),由子类根据自身特点进行具体实现。

接口在代码复用方面的局限性

接口主要侧重于定义行为规范,它不能包含具体的方法实现(Java 8 之前),因此在代码复用方面不如抽象类。接口中的方法都是抽象的,实现类需要自己实现所有方法,无法共享接口中的代码实现。

2.从功能扩展角度选择

适用接口的情况

当需要为一些类添加特定的功能,而这些功能并不是所有类都需要时,使用接口更为合适。接口可以让类在不改变原有继承体系的情况下,灵活地添加新的功能。例如,在一个动物系统中,有些动物可以飞行,有些动物可以游泳,此时可以定义 Flyable 和 Swimmable 接口:

// 飞行接口
interface Flyable {
    void fly();
}

// 游泳接口
interface Swimmable {
    void swim();
}

// 鸟类,实现 Flyable 接口
class Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("Bird is flying.");
    }
}

// 鱼类,实现 Swimmable 接口
class Fish implements Swimmable {
    @Override
    public void swim() {
        System.out.println("Fish is swimming.");
    }
}

// 鸭子类,同时实现 Flyable 和 Swimmable 接口
class Duck implements Flyable, Swimmable {
    @Override
    public void fly() {
        System.out.println("Duck is flying.");
    }

    @Override
    public void swim() {
        System.out.println("Duck is swimming.");
    }
}

在上述代码中,Flyable 和 Swimmable 接口分别定义了飞行和游泳的行为,不同的动物类可以根据自身特点选择实现相应的接口,从而灵活地扩展功能。

抽象类在功能扩展方面的限制

抽象类由于 Java 单继承的特性,一个类只能继承一个抽象类。如果使用抽象类来实现功能扩展,会受到继承关系的限制,无法让一个类同时具备多个不同抽象类的功能。而接口可以实现多实现,一个类可以同时实现多个接口,更适合用于功能的灵活扩展。

3.从设计理念角度选择

抽象类体现的是 “is - a” 关系

抽象类通常用于表示一种 “is - a” 的关系,即子类是父类的一种特殊类型。例如,Circle 是 Shape 的一种,Rectangle 也是 Shape 的一种,它们都继承自 Shape 抽象类,符合 “is - a” 的设计理念。

接口体现的是 “can - do” 关系

接口主要用于表示一种 “can - do” 的关系,即类可以具备某种行为或能力。例如,Bird 可以飞行,所以实现了 Flyable 接口;Fish 可以游泳,所以实现了 Swimmable 接口,体现了 “can - do” 的设计思想。

综上所述,在选择使用抽象类还是接口时,需要根据具体的业务需求和设计理念来决定。如果更注重代码复用和 “is - a” 关系,抽象类是更好的选择;如果更强调功能扩展和 “can - do” 关系,则接口更为合适。

十一、类 转换成 接口的时候,类和接口之间不需要有继承关系,编译器也不会报错。

在面向对象编程中,接口同样支持向上转型(Upcasting)和向下转型(Downcasting)。它们的核心逻辑是类型兼容性,具体规则如下:

在 Java 编程的概念和使用场景中,可以从一定意义上将接口类比为 “父”,不过这种类比和传统的类继承中的父类有相似之处,也存在明显区别


1. 向上转型(Upcasting)

定义:将子类对象或接口实现类的对象赋值给接口类型的引用。
特点

  • 自动完成(无需强制转换)

  • 安全(因为对象必定实现了该接口)

  • 只能调用接口中声明的方法

代码示例

interface Flyable {
    void fly();
}

class Bird implements Flyable {
    public void fly() { System.out.println("Bird flies"); }
    public void chirp() { System.out.println("Bird chirps"); }
}

public class Main {
    public static void main(String[] args) {
        // 向上转型:Bird对象赋值给Flyable接口类型
        Flyable flyable = new Bird();  // 自动转型
        flyable.fly();                 // 输出 "Bird flies"
        // flyable.chirp();           // 编译错误!接口类型无法调用Bird特有方法
    }
}

2. 向下转型(Downcasting)

定义:将接口类型的引用强制转换回具体实现类或其他兼容接口类型。
特点

  • 必须显式强制转换

  • 存在风险(可能抛出ClassCastException

  • 需通过instanceof确保类型安全

场景 1:转回原始实现类

// 定义 Flyable 接口,该接口表示具备飞行能力
interface Flyable {
    // 定义飞行方法,任何实现 Flyable 接口的类都需要实现该方法
    void fly();
}

// 定义 Bird 类,实现 Flyable 接口
class Bird implements Flyable {
    // 实现 Flyable 接口的 fly 方法
    @Override
    public void fly() {
        System.out.println("Bird is flying");
    }

    // Bird 类特有的方法,用于模拟鸟叫
    public void chirp() {
        System.out.println("Bird chirps");
    }
}

public class Main {
    public static void main(String[] args) {
        // 向上转型:将 Bird 对象赋值给 Flyable 接口类型的引用变量 flyable
        // 这里体现了多态性,即一个 Flyable 类型的引用可以指向任何实现了 Flyable 接口的对象
        Flyable flyable = new Bird();

        // 调用 Flyable 接口中定义的 fly 方法
        // 由于 flyable 实际指向的是 Bird 对象,所以会调用 Bird 类中实现的 fly 方法
        flyable.fly(); 

        // 使用 instanceof 运算符检查 flyable 引用的对象是否是 Bird 类型
        // instanceof 运算符用于判断一个对象是否是某个类或接口的实例
        if (flyable instanceof Bird) {
            // 向下转型:将 Flyable 类型的引用 flyable 强制转换为 Bird 类型的引用 bird
            // 因为已经通过 instanceof 检查,所以这里的转换是安全的
            Bird bird = (Bird) flyable;

            // 调用 Bird 类特有的 chirp 方法
            // 只有将 flyable 向下转型为 Bird 类型后,才能调用 Bird 类特有的方法
            bird.chirp();
        }
    }
}

代码解释

  1. Flyable 接口

    • 定义了一个抽象方法 fly(),表示具备飞行能力的对象需要实现该方法。
  2. Bird 类

    • 实现了 Flyable 接口,并重写了 fly() 方法,提供了鸟飞行的具体实现。
    • 定义了一个特有的方法 chirp(),用于模拟鸟叫。
  3. Main 类的 main 方法

    • 向上转型Flyable flyable = new Bird(); 这行代码将 Bird 对象赋值给 Flyable 接口类型的引用变量 flyable,这是向上转型的过程。向上转型是安全的,因为 Bird 类实现了 Flyable 接口。
    • 调用接口方法flyable.fly(); 调用 Flyable 接口中定义的 fly() 方法,由于 flyable 实际指向的是 Bird 对象,所以会调用 Bird 类中实现的 fly() 方法。
    • instanceof 检查if (flyable instanceof Bird) 使用 instanceof 运算符检查 flyable 引用的对象是否是 Bird 类型。如果是,则进行向下转型。
    • 向下转型Bird bird = (Bird) flyable; 将 Flyable 类型的引用 flyable 强制转换为 Bird 类型的引用 bird。向下转型需要谨慎,因为如果转换的类型不匹配,会抛出 ClassCastException 异常,所以在转型前使用 instanceof 进行检查是很有必要的。
    • 调用特有方法bird.chirp(); 调用 Bird 类特有的 chirp() 方法,只有将 flyable 向下转型为 Bird 类型后,才能调用该方法。

场景 2:转换为其他接口(需多重实现)

// 定义 Flyable 接口,包含飞行方法
interface Flyable {
    void fly();
}

// 定义 Swimmable 接口,包含游泳方法
interface Swimmable {
    void swim();
}

// Duck 类同时实现了 Flyable 和 Swimmable 接口
class Duck implements Flyable, Swimmable {
    // 实现 Flyable 接口的 fly 方法
    public void fly() { 
        System.out.println("Duck flies"); 
    }
    // 实现 Swimmable 接口的 swim 方法
    public void swim() { 
        System.out.println("Duck swims"); 
    }
}

public class Main {
    public static void main(String[] args) {
        // 向上转型:创建一个 Duck 对象,并将其赋值给 Flyable 接口类型的引用 flyable
        Flyable flyable = new Duck();

        // 使用 instanceof 运算符检查 flyable 引用的对象是否实现了 Swimmable 接口
        if (flyable instanceof Swimmable) {
            // 向下转型:如果对象实现了 Swimmable 接口,则将 flyable 引用转换为 Swimmable 接口类型的引用 swimmable
            Swimmable swimmable = (Swimmable) flyable;
            // 调用 Swimmable 接口的 swim 方法
            swimmable.swim();  // 输出 "Duck swims"
        }
    }
}

场景 3:危险的不安全转型

此场景演示了不安全的类型转换会导致运行时异常。当尝试将一个不实现目标接口的对象引用转换为该接口类型时,会抛出 ClassCastException

// 定义 Flyable 接口,包含飞行方法
interface Flyable {
    void fly();
}

// 定义 Swimmable 接口,包含游泳方法
interface Swimmable {
    void swim();
}

// Bird 类只实现了 Flyable 接口
class Bird implements Flyable {
    // 实现 Flyable 接口的 fly 方法
    public void fly() { 
        System.out.println("Bird flies"); 
    }
}

public class Main {
    public static void main(String[] args) {
        // 向上转型:创建一个 Bird 对象,并将其赋值给 Flyable 接口类型的引用 flyable
        Flyable flyable = new Bird();

        // 尝试将 flyable 引用转换为 Swimmable 接口类型的引用,这是不安全的,因为 Bird 类没有实现 Swimmable 接口
        // 运行时会抛出 ClassCastException 异常
        Swimmable swimmable = (Swimmable) flyable;  
    }
}

3.接口间的横向转型

当一个类同时实现多个接口时,可以在这些接口类型之间进行安全的转换。同样,使用 instanceof 运算符来确保转换的安全性。

// 定义 Flyable 接口,包含飞行方法
interface Flyable {
    void fly();
}

// 定义 Walkable 接口,包含行走方法
interface Walkable {
    void walk();
}

// Robot 类同时实现了 Flyable 和 Walkable 接口
class Robot implements Flyable, Walkable {
    // 实现 Flyable 接口的 fly 方法
    public void fly() { 
        System.out.println("Robot flies"); 
    }
    // 实现 Walkable 接口的 walk 方法
    public void walk() { 
        System.out.println("Robot walks"); 
    }
}

public class Main {
    public static void main(String[] args) {
        // 向上转型:创建一个 Robot 对象,并将其赋值给 Flyable 接口类型的引用 flyable
        Flyable flyable = new Robot();

        // 使用 instanceof 运算符检查 flyable 引用的对象是否实现了 Walkable 接口
        if (flyable instanceof Walkable) {
            // 接口间转型:如果对象实现了 Walkable 接口,则将 flyable 引用转换为 Walkable 接口类型的引用 walkable
            Walkable walkable = (Walkable) flyable;
            // 调用 Walkable 接口的 walk 方法
            walkable.walk();  // 输出 "Robot walks"
        }
    }
}

关键原则总结
向上转型:自动且安全,用于抽象和统一行为。

向下转型:需显式强制转换,必须用instanceof检查类型安全。

接口设计:

优先通过接口定义行为(而不是强制转型)。

通过多重接口实现组合功能(避免脆弱的类型转换)。

最终建议:尽量减少向下转型,优先通过接口和多态实现逻辑,确保代码的健壮性和可维护性。


网站公告

今日签到

点亮在社区的每一天
去签到