Javase 复习 接口与抽象类的关系 代码示例

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

目录

区别

自我理解

举个例子

接口与抽象类的关系

1. 接口的定义和要求

2. 默认方法的引入

例子:

3. 总结为什么不需要重写某些方法

4. 接口和抽象类的对比

例子:抽象类与接口

5. 总结

为什么抽象实现类不用重写接口里面的抽象方法

1. 接口与抽象类的关系

2. 为什么不需要在抽象类中重写 processPayment 方法

3. 如何继承 AbstractPaymentService 来实现 processPayment 方法

4. 总结


区别

接口的作用主要是它可以定义规范,是我们面向接口去编程,他写了一套规范,然后它实现类去完成它这个规范,但是抽象类它主要是实现了一种类似于模板方程、模板方法的模式,这种设计模式的理念主要是为了复用。

自我理解

通常我们在实际开发中会把接口暴露在外面,然后在核心业务类里面去实现这个接口,如果他这个时间内太多的情况下,而且它的所有时间内有相同的,而且这相同的代码,而且这些代码可以复用。我们可以在接口和实现类中间加一层抽象类,我们把公共的代码抽象到抽出来,然后放到那个抽象类里面。

实际开发中,如果我们有多个实现类共享一些相同的代码,且这些代码是可以复用的,使用抽象类作为中介来抽取公共代码是一个常见的做法。这样做的好处是我们可以避免在多个类中重复编写相同的代码,提高代码的可维护性和复用性。

举个例子

假设我们有一个支付接口 PaymentService,并且有多个支付方式的实现类,如 CreditCardPaymentServicePaypalPaymentService。这些实现类之间有一些公共的逻辑,比如支付成功后需要记录日志,或者在支付失败时发送通知等。为了避免重复代码,我们可以在接口和实现类之间增加一个抽象类 AbstractPaymentService,把这些公共的代码放到抽象类中。

// 定义支付服务接口
public interface PaymentService {
    void processPayment(double amount);
}

// 抽象支付服务类,包含公共逻辑
public abstract class AbstractPaymentService implements PaymentService {                                                                 
    // 公共的日志记录逻辑
    protected void logPaymentSuccess(double amount) {
        System.out.println("Payment of " + amount + " was successful.");
    }

    // 公共的错误通知逻辑
    protected void sendPaymentFailureNotification() {
        System.out.println("Payment failed. Please try again.");
    }
}

// 信用卡支付服务类
public class CreditCardPaymentService extends AbstractPaymentService {
    @Override
    public void processPayment(double amount) {
        // 处理信用卡支付
        boolean success = processCreditCardPayment(amount);            
            logPaymentSuccess(amount);
        } else {
            sendPaymentFailureNotification();
        }
    }

    private boolean processCreditCardPayment(double amount) {
        // 模拟信用卡支付逻辑
        return amount < 1000;  // 假设金额超过1000时支付失败
    }
}

// PayPal支付服务类
public class PaypalPaymentService extends AbstractPaymentService {
    @Override
    public void processPayment(double amount) {
        // 处理PayPal支付
        boolean success = processPaypalPayment(amount);
        if (success) {
            logPaymentSuccess(amount);
        } else {
            sendPaymentFailureNotification();
        }
    }

    private boolean processPaypalPayment(double amount) {
        // 模拟PayPal支付逻辑
        return amount < 500;  // 假设金额超过500时支付失败
    }
}

在这个例子中:

  • PaymentService 是支付服务的接口,定义了一个方法 processPayment
  • AbstractPaymentService 是抽象类,它实现了 PaymentService 接口,并且提供了 logPaymentSuccesssendPaymentFailureNotification 这两个公共方法,所有的支付实现类都可以复用这些方法。
  • CreditCardPaymentServicePaypalPaymentService 都继承自 AbstractPaymentService,并实现了各自的支付逻辑。在支付成功或失败时,它们调用了抽象类中定义的公共方法。

通过这种方式,我们避免了在每个支付实现类中都写相同的日志记录和失败通知代码,实现了代码的复用和解耦。

我们可以定义一个接口,接口名叫那个支付服务,然后里面有一个支付的那个服务的一个方法,因为接口里面的方法是没有方法体的,然后我们再去设置一个抽象类,抽象类去实现这个接口,现个接口,然后就需要我们去在车辆那里面可以写两个那个方法,写两个方法一个是可能是正确通知,一个是错误通知,然后我们去在这个抽象类的基础上去去写两个那个子类,去继承这个父类抽象方法,然后我们就可以在它的子类里面分别重写两个这两个方法,一个是正确通知,一个错误通知就行了。

关键是他那个抽象类继承他那个接口,然后不用重写他那个接口那个方法,但是抽象类它继规定了模板,模板又往下给给到了它的子类,它子类是继承了他抽象类嘛,然后他那个抽象类又实现了那个接口,而且我这种方法,它的接口里面的方法,然后他的,然后它那个抽象类的子类要做统一的实现。

接口与抽象类的关系

1. 接口的定义和要求

在 Java 中,接口定义了一组方法的契约,任何实现该接口的类都需要实现接口中的所有抽象方法(没有方法体的方法)。不过,Java 8 引入了 默认方法default 方法)后,接口的行为发生了一些变化。

  • 抽象方法:接口中的抽象方法是没有方法体的,必须被实现类重写。
  • 默认方法:接口可以为方法提供默认实现,实现在接口中提供了方法体的“默认实现”,因此实现接口的类不一定需要重写这些方法。

2. 默认方法的引入

Java 8 允许接口中定义有方法体的 default 方法,这意味着接口可以为某些方法提供一个默认的实现,而不要求实现该接口的类去重写这些方法。

例子:
java


复制代码
interface Animal {
    // 抽象方法,必须由实现类重写
    void makeSound();

    // 默认方法,具有方法体,子类可以选择性地重写
    default void sleep() {
        System.out.println("This animal is sleeping.");
    }
}

class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.makeSound();  // 输出 "Woof"
        dog.sleep();      // 输出 "This animal is sleeping."
    }
}

在这个例子中:

  • makeSound()抽象方法Dog 类必须实现这个方法。
  • sleep()默认方法,它已经在接口中提供了实现。因此,Dog 类可以 选择不实现它,直接使用接口提供的默认实现。如果需要,Dog 类可以重写 sleep() 方法以提供自定义的实现。

3. 总结为什么不需要重写某些方法

  • 如果接口中的方法是默认方法,实现接口的类可以 不重写 该方法,直接使用接口中的默认实现。
  • 如果接口中的方法是抽象方法(没有默认实现),实现接口的类必须 重写 该方法。

因此,Java 的接口设计引入了默认方法后,类实现接口时,不再强制要求实现所有方法。对于那些有默认实现的方法,类可以直接继承这个默认实现,而不需要手动提供方法体。

4. 接口和抽象类的对比

  • 接口:默认方法让接口不仅仅是方法的声明,还可以包含方法的实现。实现接口的类只需重写需要定制化的部分,其余的可以直接继承接口的默认实现。
  • 抽象类:抽象类中既可以有抽象方法,也可以有已经实现的方法。抽象类通常用于提供一些默认行为,并要求子类必须实现抽象方法。
例子:抽象类与接口
java


复制代码
abstract class Animal {
    abstract void makeSound(); // 抽象方法,子类必须实现

    void sleep() {
        System.out.println("This animal is sleeping.");
    }
}

interface Flyable {
    default void fly() {
        System.out.println("This animal can fly.");
    }
}

class Bird extends Animal implements Flyable {
    @Override
    void makeSound() {
        System.out.println("Chirp");
    }

    // 可以选择是否重写 Flyable 接口中的 fly() 方法
}

public class Main {
    public static void main(String[] args) {
        Bird bird = new Bird();
        bird.makeSound();  // 输出 "Chirp"
        bird.sleep();      // 输出 "This animal is sleeping."
        bird.fly();        // 输出 "This animal can fly."
    }
}
  • 在这个例子中,Bird 类必须重写抽象类 Animal 中的 makeSound() 方法,但它 不必 重写 Flyable 接口中的 fly() 方法,因为 fly() 有默认实现。

5. 总结

  • 当接口中的方法是抽象方法时,实现接口的类必须实现这些方法。
  • 当接口中的方法是 default 方法时,实现接口的类 可以选择不重写 该方法,因为接口已经提供了默认实现。
  • 抽象类和接口的结合使得 Java 在多态和扩展性上提供了灵活性,开发者可以根据需要决定是否覆盖默认行为。

为什么抽象实现类不用重写接口里面的抽象方法

在 Java 中,AbstractPaymentService 之所以不需要重写 PaymentService 接口中的 processPayment 方法,主要是因为它 是一个抽象类,而且在实现接口时,抽象类有一些特殊的规则和机制。

让我们通过分析代码并逐步解答这个问题。

1. 接口与抽象类的关系

  • 接口:接口只定义方法的签名,没有实现任何具体的逻辑。它定义了一组方法,任何实现该接口的类都需要提供这些方法的具体实现。
  • 抽象类:抽象类是一个不能直接实例化的类,它可以包含已实现的方法,也可以包含抽象方法(没有方法体的、需要子类实现的方法)。抽象类可以通过继承接口来实现接口的部分或全部方法。

2. 为什么不需要在抽象类中重写 processPayment 方法

抽象类 AbstractPaymentService 实现了 PaymentService 接口,但它不需要提供 processPayment 方法的具体实现。原因是:

  1. AbstractPaymentService 是抽象类:抽象类允许部分方法不实现,而是将它们留给子类来实现。这种设计让抽象类可以实现接口的部分方法(例如,logPaymentSuccesssendPaymentFailureNotification),而对于其他方法(如 processPayment),可以要求 子类来实现
  2. PaymentService 接口定义了 processPayment 方法:接口中的 processPayment 是一个抽象方法,它要求所有实现该接口的类提供具体实现。但是,因为 AbstractPaymentService 是抽象类,所以它不需要提供 processPayment 的实现。它可以将这个方法的具体实现留给继承它的非抽象子类来实现。

3. 如何继承 AbstractPaymentService 来实现 processPayment 方法

具体实现 processPayment 方法的是继承 AbstractPaymentService 的非抽象子类。

例如:

java


复制代码
public class CreditCardPaymentService extends AbstractPaymentService {
    @Override
    public void processPayment(double amount) {
        // 这里是 CreditCardPaymentService 对 processPayment 的具体实现
        System.out.println("Processing credit card payment of " + amount);
        // 处理完支付后,调用抽象类中的公共方法
        logPaymentSuccess(amount);
    }
}

在这个例子中,CreditCardPaymentService 类继承了 AbstractPaymentService,并实现了接口 PaymentService 中的 processPayment 方法。它还可以使用 AbstractPaymentService 中定义的公共方法(如 logPaymentSuccess)来减少重复代码。

4. 总结

  • 抽象类可以继承接口并实现接口的方法,但它不强制要求所有方法都实现。它可以将一些方法的实现留给具体的子类,子类必须提供具体实现(除非子类也是抽象类)。
  • AbstractPaymentService 作为一个抽象类,继承了 PaymentService 接口,要求其具体子类去实现 processPayment 方法,而 AbstractPaymentService 可以实现接口中的其他方法或提供公共的辅助方法,如 logPaymentSuccesssendPaymentFailureNotification,供子类使用。

这种设计模式提供了一种 模板方法模式,即抽象类定义了一个框架,而子类则实现其中的细节逻辑。