一、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
和一个具体方法 eat
。Dog
类继承了 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
类同时实现了 Animal
和 Movable
两个接口。每个接口定义了一个方法,Dog
类提供了这些方法的实现。
4. 抽象与接口的比较
特性 | 抽象类 | 接口 |
---|---|---|
关键字 | abstract | interface |
方法实现 | 可以包含抽象方法和具体方法 | 只能包含抽象方法(Java 8+ 支持默认方法和静态方法) |
构造方法 | 可以有构造方法 | 没有构造方法 |
继承关系 | 一个类只能继承一个抽象类 | 一个类可以实现多个接口 |
成员变量 | 可以有实例变量 | 只能有静态常量(默认是 public static final ) |
二、抽象类类型转换
在 Java 中,抽象类的类型转换通常涉及两种类型的转换:
- 父类类型转换为子类(向下转型)。
- 子类类型转换为父类(向上转型)。
这两种类型转换背后的核心是 继承关系 和 多态。我们将在下面通过实际的代码示例来进一步说明这两种类型转换。
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
类型的对象。 - 由于
animal
是Animal
类型,所以我们不能直接调用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
提供了支付的一些通用逻辑,比如生成支付订单、处理支付回调等。 - 子类
AlipayPaymentProcessor
和WeChatPaymentProcessor
分别实现具体支付方式的支付流程。
代码实现:
// 定义支付接口
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
)则由子类实现。 -
AlipayPaymentProcessor
和WeChatPaymentProcessor
类分别实现了支付宝和微信支付的具体支付逻辑。
业务场景2:用户通知系统
设计一个通知系统,用户可以选择接收不同类型的通知,如短信、邮件和推送通知。通过接口定义不同通知方式,通过抽象类实现通用的通知发送逻辑。
设计说明:
- 接口
Notification
定义了通知的发送行为。 - 抽象类
AbstractNotificationService
提供了发送通知的一些通用方法。 - 子类
SMSNotificationService
、EmailNotificationService
、PushNotificationService
实现了具体的通知发送方式。
代码实现:
// 定义通知接口
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
方法在子类中实现。SMSNotificationService
、EmailNotificationService
和PushNotificationService
分别实现了发送不同类型通知的逻辑。
业务场景3:订单管理系统
设计一个订单管理系统,其中有多种订单类型(如普通订单和促销订单),它们共享一些通用的订单处理逻辑,但也有不同的处理方式。我们使用抽象类来实现通用订单处理逻辑,接口来定义每种订单类型的额外操作。
设计说明:
- 接口
OrderType
定义了不同订单类型的行为。 - 抽象类
Order
提供了订单的基本属性和通用操作。 - 子类
NormalOrder
和PromotionalOrder
分别实现普通订单和促销订单的处理逻辑。
代码实现:
// 定义订单类型接口
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 和金额等字段,以及通用的订单处理方法。NormalOrder
和PromotionalOrder
类分别处理普通订单和促销订单的折扣计算,并在processOrder
方法中执行具体的折扣应用。
总结:
我们通过抽象类和接口的结合来实现业务逻辑的高内聚和低耦合。抽象类提供了通用的功能实现,而接口则定义了各类支付方式、通知方式和订单类型的特定行为。这样设计不仅提高了代码的可扩展性,还增强了系统的灵活性,使得新功能的添加变得简单。
四、练习题目
1、选择题
-
以下哪个选项是正确的,关于Java中的接口?
- A. 一个类可以实现多个接口
- B. 一个类只能实现一个接口
- C. 接口不能有任何方法
- D. 接口中的方法只能是静态方法
正确答案:A
-
下列关于抽象类的描述,哪个是正确的?
- A. 抽象类不能有构造方法
- B. 抽象类可以有实现的方法和抽象方法
- C. 抽象类不能有成员变量
- D. 抽象类必须定义一个抽象方法
正确答案:B
-
接口与抽象类的区别是什么?
- A. 接口不能有成员变量,而抽象类可以
- B. 接口不能包含任何方法实现,而抽象类可以
- C. 接口不可以继承其他接口,而抽象类可以
- D. 抽象类和接口没有任何区别
正确答案:A
-
下面的代码是如何工作?
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
-
如果一个类继承了抽象类,并且没有实现所有抽象方法,那么这个类必须是:
- A. 抽象类
- B. 接口
- C. 具体类
- D. 编译错误
正确答案:A
2、代码检查题
-
检查以下代码并找出错误:
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();
-
检查以下代码并找出错误:
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"); }
-
检查以下代码并找出错误:
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、代码设计题
-
设计一个抽象类
Employee
,该类包含name
、salary
属性,并有一个抽象方法calculateBonus()
,然后实现两个子类Manager
和Developer
。其中,Manager
类根据固定比例计算奖金,Developer
类根据工作年限计算奖金。提示:
Employee
类包含name
和salary
。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; } }
-
设计一个接口
Flyable
,该接口包含fly()
方法。创建类Bird
和Plane
分别实现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"); } }
-
设计一个接口
Playable
,并实现FootballPlayer
和BasketballPlayer
类。每个类都有一个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"); } }
-
设计一个抽象类
Appliance
,包含turnOn()
和turnOff()
方法。创建WashingMachine
和Refrigerator
两个子类,并实现这些方法。提示:
WashingMachine
的turnOn()
方法打印“Washing machine is running”。Refrigerator
的turnOn()
方法打印“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"); } }