JAVA中的抽象学习

一、Java SE 中的抽象概念

在 Java 中,抽象(Abstraction)是面向对象编程的重要特性之一。抽象的核心思想是“只关注重要的特性,而忽略不重要的细节”。抽象通常通过抽象类接口来实现,它帮助开发者将复杂的系统隐藏在简单的接口和设计背后。

1. 抽象的定义

抽象的目的是从一组具体事物中提取出通用的特性,而忽略它们的具体实现细节。在 Java 中,抽象可以通过以下两种方式来实现:

  • 抽象类(Abstract Class):用来表示一些基本特性,但不能直接实例化。它可以包含抽象方法(没有实现的方法)和具体方法(有实现的方法)。
  • 接口(Interface):定义一组方法签名,不提供方法实现。实现接口的类需要提供接口中所有方法的实现。
2. 抽象类(Abstract Class)

抽象类是一个包含抽象方法的类,抽象方法是没有方法体的方法。抽象类可以包含具体的方法(已实现的方法)和成员变量。

抽象类的特点:
  • 可以包含抽象方法和具体方法。
  • 不可以直接实例化(无法创建抽象类的对象)。
  • 可以有构造方法和成员变量。
抽象类的例子:
abstract class Animal {
    // 抽象方法,没有实现
    public abstract void sound();

    // 具体方法,有实现
    public void eat() {
        System.out.println("This animal eats food.");
    }
}

class Dog extends Animal {
    // 提供抽象方法的实现
    public void sound() {
        System.out.println("Woof Woof");
    }
}

class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();
        dog.sound();  // 输出: Woof Woof
        dog.eat();    // 输出: This animal eats food.
    }
}

在这个例子中,Animal 是一个抽象类,它定义了一个抽象方法 sound 和一个具体方法 eatDog 类继承了 Animal 类并实现了 sound 方法。

3. 接口(Interface)

接口是一个完全抽象的类,它定义了方法的签名(没有实现),任何类都可以实现一个接口,并提供接口中方法的实现。

接口的特点:
  • 只能包含抽象方法(从 Java 8 开始,接口可以包含默认方法和静态方法)。
  • 类通过 implements 关键字实现接口。
  • 一个类可以实现多个接口。
接口的例子:
interface Animal {
    // 接口中的方法没有实现
    void sound();
}

interface Movable {
    void move();
}

class Dog implements Animal, Movable {
    public void sound() {
        System.out.println("Woof Woof");
    }

    public void move() {
        System.out.println("The dog runs.");
    }
}

class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.sound();  // 输出: Woof Woof
        dog.move();   // 输出: The dog runs.
    }
}

在这个例子中,Dog 类同时实现了 AnimalMovable 两个接口。每个接口定义了一个方法,Dog 类提供了这些方法的实现。

4. 抽象与接口的比较
特性抽象类接口
关键字abstractinterface
方法实现可以包含抽象方法和具体方法只能包含抽象方法(Java 8+ 支持默认方法和静态方法)
构造方法可以有构造方法没有构造方法
继承关系一个类只能继承一个抽象类一个类可以实现多个接口
成员变量可以有实例变量只能有静态常量(默认是 public static final

二、抽象类类型转换

在 Java 中,抽象类的类型转换通常涉及两种类型的转换:

  1. 父类类型转换为子类(向下转型)。
  2. 子类类型转换为父类(向上转型)。

这两种类型转换背后的核心是 继承关系多态。我们将在下面通过实际的代码示例来进一步说明这两种类型转换。

1. 父类转子类(向下转型)

向下转型是指将父类对象转化为子类对象。在实际开发中,这种转换通常是在调用子类特有的方法时使用。前提条件是,父类对象的实际类型是子类类型(即父类引用指向了一个子类实例),否则会抛出 ClassCastException 异常。

例子:父类转子类(向下转型)
abstract class Animal {
    public abstract void sound();
}

class Dog extends Animal {
    public void sound() {
        System.out.println("Woof Woof");
    }

    public void fetch() {
        System.out.println("Dog is fetching the ball!");
    }
}

class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();  // 父类引用指向子类对象

        // 向下转型:将父类引用转换为子类引用
        Dog dog = (Dog) animal;  // 强制转换为 Dog 类型

        dog.sound();  // 输出: Woof Woof
        dog.fetch();  // 输出: Dog is fetching the ball!
    }
}
解释:
  • 在这个例子中,Animal 是一个抽象类,Dog 是它的子类。
  • 我们创建了一个 Animal 类型的引用 animal,它指向了一个 Dog 类型的对象。
  • 由于 animalAnimal 类型,所以我们不能直接调用 Dog 类中定义的 fetch 方法。
  • 为了调用 Dog 特有的方法,我们需要将 Animal 类型的引用向下转型成 Dog 类型。这样就能访问 Dog 类特有的方法 fetch()
  • 转型时,animal 实际上指向的是一个 Dog 对象,所以可以安全地进行向下转型。

如果将 animal 指向一个非 Dog 类型的对象(比如 Cat),在进行向下转型时就会抛出 ClassCastException

2. 子类转父类(向上转型)

向上转型是指将子类对象转化为父类对象。由于子类是父类的一种特殊类型,向上转型是安全的,不需要显式强制转换。这种转换常见于多态的实现,它允许子类的对象被当做父类对象来处理。

例子:子类转父类(向上转型)
abstract class Animal {
    public abstract void sound();
}

class Dog extends Animal {
    public void sound() {
        System.out.println("Woof Woof");
    }

    public void fetch() {
        System.out.println("Dog is fetching the ball!");
    }
}

class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();  // 创建 Dog 类型对象
        Animal animal = dog;  // 向上转型:Dog 转换为 Animal 类型

        animal.sound();  // 输出: Woof Woof
        // animal.fetch();  // 编译错误:Animal 类型没有 fetch 方法
    }
}
解释:
  • 在这个例子中,Dog 类是 Animal 类的子类。我们创建了一个 Dog 类型的对象 dog,然后将其赋值给 Animal 类型的变量 animal
  • 这里的转换是 向上转型,因为 Dog 类型的对象被赋值给了 Animal 类型的引用,Java 自动进行这种转换。
  • 向上转型后,animal 变量只能访问父类 Animal 中定义的方法。如果调用 animal.fetch(),编译器会报错,因为 fetch()Dog 类的特有方法,Animal 类中没有定义 fetch() 方法。
  • 然而,由于 animal 实际上是指向 Dog 对象,所以可以调用 sound() 方法,并且会输出 Dog 类的实现。
3. 父类转子类的异常情况

如果父类引用的实际类型并非子类类型,进行类型转换时会抛出 ClassCastException

例子:类型转换异常
abstract class Animal {
    public abstract void sound();
}

class Dog extends Animal {
    public void sound() {
        System.out.println("Woof Woof");
    }
}

class Cat extends Animal {
    public void sound() {
        System.out.println("Meow");
    }
}

class Main {
    public static void main(String[] args) {
        Animal animal = new Cat();  // 创建 Cat 类型的对象,引用类型为 Animal

        // 错误的类型转换:将 Cat 转换为 Dog
        Dog dog = (Dog) animal;  // 会抛出 ClassCastException 异常
        dog.sound();  // 不会执行到这里
    }
}
解释:
  • 在这个例子中,animal 实际上指向一个 Cat 对象,但是我们尝试将它转换为 Dog 类型。这会导致 ClassCastException 异常。
  • 由于 animal 的实际类型是 Cat,而不是 Dog,Java 会抛出 ClassCastException

总结

  • 父类转子类(向下转型):需要使用强制类型转换,但必须确保父类对象的实际类型是子类类型,否则会抛出 ClassCastException 异常。
  • 子类转父类(向上转型):是安全的,Java 会自动进行类型转换,子类对象可以赋值给父类类型的引用,但只能访问父类中定义的方法和属性。

三、抽象类和接口的案例说明

业务场景1:支付系统设计

我们将设计一个支付系统,其中有多种支付方式,比如 支付宝微信支付。每种支付方式都需要实现相同的支付接口,而支付系统的核心逻辑通过一个抽象类来实现。

设计说明:
  • 接口 PaymentMethod 定义了支付的基本操作,如 pay
  • 抽象类 PaymentProcessor 提供了支付的一些通用逻辑,比如生成支付订单、处理支付回调等。
  • 子类 AlipayPaymentProcessorWeChatPaymentProcessor 分别实现具体支付方式的支付流程。
代码实现:
// 定义支付接口
interface PaymentMethod {
    void pay(double amount);  // 支付方法
}

// 抽象支付处理类
abstract class PaymentProcessor {
    protected String orderId;
    
    public PaymentProcessor(String orderId) {
        this.orderId = orderId;
    }

    // 公共的支付处理逻辑
    public void processPayment(double amount) {
        System.out.println("Processing payment for order: " + orderId);
        initiatePayment(amount);
        onPaymentCompleted();
    }
    
    // 每个支付方式的具体支付操作由子类实现
    protected abstract void initiatePayment(double amount);
    
    // 支付完成后的操作
    protected void onPaymentCompleted() {
        System.out.println("Payment for order " + orderId + " completed.");
    }
}

// 实现支付宝支付
class AlipayPaymentProcessor extends PaymentProcessor implements PaymentMethod {

    public AlipayPaymentProcessor(String orderId) {
        super(orderId);
    }

    @Override
    protected void initiatePayment(double amount) {
        System.out.println("Initiating Alipay payment of amount: " + amount);
    }

    @Override
    public void pay(double amount) {
        processPayment(amount);
    }
}

// 实现微信支付
class WeChatPaymentProcessor extends PaymentProcessor implements PaymentMethod {

    public WeChatPaymentProcessor(String orderId) {
        super(orderId);
    }

    @Override
    protected void initiatePayment(double amount) {
        System.out.println("Initiating WeChat payment of amount: " + amount);
    }

    @Override
    public void pay(double amount) {
        processPayment(amount);
    }
}

public class Main {
    public static void main(String[] args) {
        PaymentProcessor alipayProcessor = new AlipayPaymentProcessor("AL123456");
        alipayProcessor.pay(100.0);
        
        PaymentProcessor weChatProcessor = new WeChatPaymentProcessor("WX123456");
        weChatProcessor.pay(200.0);
    }
}
设计说明:
  • PaymentMethod 接口定义了支付行为。

  • PaymentProcessor 抽象类提供了支付流程的通用处理(如 processPayment 方法),而具体的支付操作(如 initiatePayment)则由子类实现。

  • AlipayPaymentProcessorWeChatPaymentProcessor 类分别实现了支付宝和微信支付的具体支付逻辑。

业务场景2:用户通知系统

设计一个通知系统,用户可以选择接收不同类型的通知,如短信、邮件和推送通知。通过接口定义不同通知方式,通过抽象类实现通用的通知发送逻辑。

设计说明:
  • 接口 Notification 定义了通知的发送行为。
  • 抽象类 AbstractNotificationService 提供了发送通知的一些通用方法。
  • 子类 SMSNotificationServiceEmailNotificationServicePushNotificationService 实现了具体的通知发送方式。
代码实现:
// 定义通知接口
interface Notification {
    void sendNotification(String message);
}

// 抽象通知服务类
abstract class AbstractNotificationService implements Notification {
    
    protected String userId;

    public AbstractNotificationService(String userId) {
        this.userId = userId;
    }

    // 发送通知的通用逻辑
    public void sendNotification(String message) {
        System.out.println("Sending notification to user: " + userId);
        sendSpecificNotification(message);
    }
    
    // 每个通知方式的具体发送操作由子类实现
    protected abstract void sendSpecificNotification(String message);
}

// 短信通知服务
class SMSNotificationService extends AbstractNotificationService {

    public SMSNotificationService(String userId) {
        super(userId);
    }

    @Override
    protected void sendSpecificNotification(String message) {
        System.out.println("Sending SMS: " + message);
    }
}

// 邮件通知服务
class EmailNotificationService extends AbstractNotificationService {

    public EmailNotificationService(String userId) {
        super(userId);
    }

    @Override
    protected void sendSpecificNotification(String message) {
        System.out.println("Sending Email: " + message);
    }
    
}

// 推送通知服务
class PushNotificationService extends AbstractNotificationService {

    public PushNotificationService(String userId) {
        super(userId);
    }

    @Override
    protected void sendSpecificNotification(String message) {
        System.out.println("Sending Push Notification: " + message);
    }
}

public class Main {
    public static void main(String[] args) {
        
        Notification smsService = new SMSNotificationService("User123");
        smsService.sendNotification("Your order has been shipped!");

        Notification emailService = new EmailNotificationService("User123");
        emailService.sendNotification("Your invoice is ready!");

        Notification pushService = new PushNotificationService("User123");
        pushService.sendNotification("You have a new message!");
        
    }
}
设计说明:
  • Notification 接口定义了 sendNotification 方法,用于发送通知。
  • AbstractNotificationService 提供了发送通知的通用流程,包括处理用户ID等,而具体的通知发送方式由 sendSpecificNotification 方法在子类中实现。
  • SMSNotificationServiceEmailNotificationServicePushNotificationService 分别实现了发送不同类型通知的逻辑。
业务场景3:订单管理系统

设计一个订单管理系统,其中有多种订单类型(如普通订单和促销订单),它们共享一些通用的订单处理逻辑,但也有不同的处理方式。我们使用抽象类来实现通用订单处理逻辑,接口来定义每种订单类型的额外操作。

设计说明:
  • 接口 OrderType 定义了不同订单类型的行为。
  • 抽象类 Order 提供了订单的基本属性和通用操作。
  • 子类 NormalOrderPromotionalOrder 分别实现普通订单和促销订单的处理逻辑。
代码实现:
// 定义订单类型接口
interface OrderType {
    void applyDiscount();  // 订单折扣
}

// 抽象订单类
abstract class Order {
    
    protected String orderId;
    protected double amount;

    public Order(String orderId, double amount) {
        this.orderId = orderId;
        this.amount = amount;
    }

    // 处理订单
    public void processOrder() {
        System.out.println("Processing order: " + orderId);
        applyDiscount();
        printReceipt();
    }

    // 每个订单的折扣操作由子类实现
    protected abstract void applyDiscount();
    
    // 打印订单收据
    protected void printReceipt() {
        System.out.println("Order " + orderId + " with amount: " + amount);
    }
}

// 普通订单
class NormalOrder extends Order implements OrderType {

    public NormalOrder(String orderId, double amount) {
        super(orderId, amount);
    }

    @Override
    protected void applyDiscount() {
        System.out.println("Applying no discount for normal order.");
    }
}

// 促销订单
class PromotionalOrder extends Order implements OrderType {

    public PromotionalOrder(String orderId, double amount) {
        super(orderId, amount);
    }

    @Override
    protected void applyDiscount() {
        System.out.println("Applying 20% discount for promotional order.");
        amount = amount * 0.8;  // 20% 折扣
    }
    
}

public class Main {
    
    public static void main(String[] args) {
        Order normalOrder = new NormalOrder("ORD12345", 500.0);
        normalOrder.processOrder();

        Order promoOrder = new PromotionalOrder("ORD54321", 500.0);
        promoOrder.processOrder();
    }
}
设计说明:
  • OrderType 接口定义了应用折扣的行为。
  • Order 抽象类提供了订单的基础逻辑,包含订单的 ID 和金额等字段,以及通用的订单处理方法。
  • NormalOrderPromotionalOrder 类分别处理普通订单和促销订单的折扣计算,并在 processOrder 方法中执行具体的折扣应用。
总结:

我们通过抽象类和接口的结合来实现业务逻辑的高内聚和低耦合。抽象类提供了通用的功能实现,而接口则定义了各类支付方式、通知方式和订单类型的特定行为。这样设计不仅提高了代码的可扩展性,还增强了系统的灵活性,使得新功能的添加变得简单。

四、练习题目

1、选择题
  1. 以下哪个选项是正确的,关于Java中的接口?

    • A. 一个类可以实现多个接口
    • B. 一个类只能实现一个接口
    • C. 接口不能有任何方法
    • D. 接口中的方法只能是静态方法

    正确答案:A

  2. 下列关于抽象类的描述,哪个是正确的?

    • A. 抽象类不能有构造方法
    • B. 抽象类可以有实现的方法和抽象方法
    • C. 抽象类不能有成员变量
    • D. 抽象类必须定义一个抽象方法

    正确答案:B

  3. 接口与抽象类的区别是什么?

    • A. 接口不能有成员变量,而抽象类可以
    • B. 接口不能包含任何方法实现,而抽象类可以
    • C. 接口不可以继承其他接口,而抽象类可以
    • D. 抽象类和接口没有任何区别

    正确答案:A

  4. 下面的代码是如何工作?

    interface Animal {
        void sound();
    }
    
    class Dog implements Animal {
        public void sound() {
            System.out.println("Bark");
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            Animal myAnimal = new Dog();
            myAnimal.sound();
        }
    }
    
    • A. 会打印“Meow”
    • B. 会打印“Bark”
    • C. 编译错误,因为接口没有实现
    • D. 编译错误,因为类Dog没有提供sound方法实现

    正确答案:B

  5. 如果一个类继承了抽象类,并且没有实现所有抽象方法,那么这个类必须是:

    • A. 抽象类
    • B. 接口
    • C. 具体类
    • D. 编译错误

    正确答案:A


2、代码检查题
  1. 检查以下代码并找出错误:

    abstract class Shape {
        abstract void draw();
    }
    
    class Circle extends Shape {
        void draw() {
            System.out.println("Drawing Circle");
        }
    }
    
    class Test {
        public static void main(String[] args) {
            Shape s = new Shape();  // 错误
            s.draw();
        }
    }
    

    问题: 代码中存在一个错误,Shape是抽象类,不能直接实例化,应该实例化Circle类。

    修复:

    Shape s = new Circle();  // 正确
    s.draw();
    
  2. 检查以下代码并找出错误:

    interface Animal {
        void eat();
        void sleep();
    }
    
    class Dog implements Animal {
        void eat() {
            System.out.println("Dog eating");
        }
    
        void sleep() {
            System.out.println("Dog sleeping");
        }
    }
    

    问题: Dog类中没有正确实现Animal接口中的方法,方法声明缺少public修饰符。

    修复:

    public void eat() {
        System.out.println("Dog eating");
    }
    
    public void sleep() {
        System.out.println("Dog sleeping");
    }
    
  3. 检查以下代码并找出错误:

    abstract class Vehicle {
        abstract void move();
    }
    
    class Car extends Vehicle {
        void move() {
            System.out.println("Car moving");
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            Vehicle v = new Car();
            v.move();
        }
    }
    

    问题: 代码没有问题。Car类正确地继承了Vehicle并实现了move方法,程序会正常运行并输出“Car moving”。

    修复: 不需要修复,代码是正确的。


3、代码设计题
  1. 设计一个抽象类Employee,该类包含namesalary属性,并有一个抽象方法calculateBonus(),然后实现两个子类ManagerDeveloper。其中,Manager类根据固定比例计算奖金,Developer类根据工作年限计算奖金。

    提示:

    • Employee类包含namesalary
    • Manager类奖金为salary * 0.1
    • Developer类奖金为yearsOfExperience * 500

    代码示例:

    abstract class Employee {
        String name;
        double salary;
    
        Employee(String name, double salary) {
            this.name = name;
            this.salary = salary;
        }
    
        abstract double calculateBonus();
    }
    
    class Manager extends Employee {
        Manager(String name, double salary) {
            super(name, salary);
        }
    
        double calculateBonus() {
            return salary * 0.1;
        }
    }
    
    class Developer extends Employee {
        int yearsOfExperience;
    
        Developer(String name, double salary, int yearsOfExperience) {
            super(name, salary);
            this.yearsOfExperience = yearsOfExperience;
        }
    
        double calculateBonus() {
            return yearsOfExperience * 500;
        }
    }
    
  2. 设计一个接口Flyable,该接口包含fly()方法。创建类BirdPlane分别实现Flyable接口,并在fly()方法中输出不同的飞行信息。

    提示:

    • Bird类输出“Bird is flying”。
    • Plane类输出“Plane is flying”。

    代码示例:

    interface Flyable {
        void fly();
    }
    
    class Bird implements Flyable {
        public void fly() {
            System.out.println("Bird is flying");
        }
    }
    
    class Plane implements Flyable {
        public void fly() {
            System.out.println("Plane is flying");
        }
    }
    
  3. 设计一个接口Playable,并实现FootballPlayerBasketballPlayer类。每个类都有一个play()方法,分别打印“Playing football”和“Playing basketball”。

    提示:

    • FootballPlayer类和BasketballPlayer类都实现Playable接口。

    代码示例:

    interface Playable {
        void play();
    }
    
    class FootballPlayer implements Playable {
        public void play() {
            System.out.println("Playing football");
        }
    }
    
    class BasketballPlayer implements Playable {
        public void play() {
            System.out.println("Playing basketball");
        }
    }
    
  4. 设计一个抽象类Appliance,包含turnOn()turnOff()方法。创建WashingMachineRefrigerator两个子类,并实现这些方法。

    提示:

    • WashingMachineturnOn()方法打印“Washing machine is running”。
    • RefrigeratorturnOn()方法打印“Refrigerator is cooling”。

    代码示例:

    abstract class Appliance {
        abstract void turnOn();
        abstract void turnOff();
    }
    
    class WashingMachine extends Appliance {
        void turnOn() {
            System.out.println("Washing machine is running");
        }
    
        void turnOff() {
            System.out.println("Washing machine is off");
        }
    }
    
    class Refrigerator extends Appliance {
        void turnOn() {
            System.out.println("Refrigerator is cooling");
        }
    
        void turnOff() {
            System.out.println("Refrigerator is off");
        }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昔我往昔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值