Java面向对象编程核心:封装、继承与多态
文章目录
- 1. 引言
- 2. 封装
- 2.1 什么是封装
- 2.2 访问修饰符
- 2.3 getter和setter方法
- 2.4 封装的优点
- 2.5 封装实践示例
- 3. 继承
- 3.1 什么是继承
- 3.2 继承的类型
- 3.3 super关键字
- 3.4 方法覆盖
- 3.5 构造函数与继承
- 3.6 final关键字
- 3.7 继承的优缺点
- 3.8 继承实践示例
- 4. 多态
- 4.1 什么是多态
- 4.2 运行时多态
- 4.3 编译时多态
- 4.4 抽象类
- 4.5 接口
- 4.6 多态实践示例
- 5. 封装、继承与多态的关系
- 5.1 三者之间的联系
- 5.2 三者的协同工作
- 6. 面向对象设计的最佳实践
- 6.1 封装的最佳实践
- 6.2 继承的最佳实践
- 6.3 多态的最佳实践
- 7. 常见面试问题
- 7.1 封装相关问题
- 7.2 继承相关问题
- 7.3 多态相关问题
- 8. 总结
1. 引言
Java是一种面向对象的编程语言,它的核心特性包括封装、继承和多态。这三大特性是Java面向对象编程的基石,理解并掌握这些概念对于编写高质量的Java代码至关重要。本文将深入详细地解释这三个概念的各个方面,并通过丰富的示例帮助初学者理解。
面向对象编程(OOP)是一种编程范式,它使用"对象"这一概念来组织数据和行为。在Java中:
- 对象是类的实例,包含状态(属性)和行为(方法)
- 类是对象的蓝图或模板,定义了对象将拥有的属性和方法
让我们开始详细探索Java面向对象编程的三大支柱。
2. 封装
2.1 什么是封装
封装是面向对象编程的一个基本概念,它指的是将数据(属性)和操作这些数据的方法绑定在一起,对外部世界隐藏对象的内部实现细节,只暴露必要的接口。
简单来说,封装就是:
- 将类的属性(变量)设为私有
- 提供公共的getter和setter方法来访问和修改这些属性
封装可以被视为一个保护屏障,防止外部代码直接访问类内部的数据。
2.2 访问修饰符
Java提供了四种访问修饰符,用于控制类、变量、方法和构造函数的访问级别:
修饰符 | 同一个类 | 同一个包 | 子类 | 其他包 |
---|---|---|---|---|
private | ✓ | ✗ | ✗ | ✗ |
default (无修饰符) | ✓ | ✓ | ✗ | ✗ |
protected | ✓ | ✓ | ✓ | ✗ |
public | ✓ | ✓ | ✓ | ✓ |
示例:
public class AccessModifierDemo {private int privateVar; // 只在当前类中可见int defaultVar; // 在同一包中可见protected int protectedVar; // 在同一包和子类中可见public int publicVar; // 在所有地方可见private void privateMethod() {System.out.println("这是私有方法");}void defaultMethod() {System.out.println("这是默认方法");}protected void protectedMethod() {System.out.println("这是受保护方法");}public void publicMethod() {System.out.println("这是公共方法");}
}
2.3 getter和setter方法
getter和setter方法是封装的基本实现方式,它们允许我们控制对类属性的访问:
- getter方法:用于获取属性值,通常命名为
getXxx()
- setter方法:用于设置属性值,通常命名为
setXxx()
通过这些方法,我们可以:
- 验证数据的正确性
- 在修改数据前执行一些必要的操作
- 在不改变公共接口的情况下更改内部实现
示例:
public class Person {private String name;private int age;// getter方法public String getName() {return name;}// setter方法public void setName(String name) {this.name = name;}// getter方法public int getAge() {return age;}// setter方法(带验证)public void setAge(int age) {if (age < 0 || age > 150) {throw new IllegalArgumentException("年龄必须在0到150之间");}this.age = age;}
}
2.4 封装的优点
- 数据隐藏:防止外部直接访问类的属性,保护数据完整性
- 灵活性和可维护性:可以改变类的内部实现而不影响外部代码
- 提高重用性:封装的类更容易在不同的应用程序中重用
- 数据验证:通过setter方法可以验证数据的正确性
- 提高安全性:防止对象处于不一致状态
2.5 封装实践示例
下面是一个银行账户的示例,展示了封装的实际应用:
public class BankAccount {private String accountNumber;private String accountHolderName;private double balance;private String accountType;private boolean active;// 构造函数public BankAccount(String accountNumber, String accountHolderName, double initialBalance, String accountType) {this.accountNumber = accountNumber;this.accountHolderName = accountHolderName;this.balance = initialBalance;this.accountType = accountType;this.active = true;}// getter和setter方法public String getAccountNumber() {return accountNumber;}public String getAccountHolderName() {return accountHolderName;}public void setAccountHolderName(String accountHolderName) {this.accountHolderName = accountHolderName;}public double getBalance() {return balance;}// 余额不提供直接的setter方法,而是通过存款和取款方法修改public String getAccountType() {return accountType;}public boolean isActive() {return active;}public void setActive(boolean active) {this.active = active;}// 业务方法public void deposit(double amount) {if (amount <= 0) {throw new IllegalArgumentException("存款金额必须大于零");}if (!active) {throw new IllegalStateException("账户不活跃,无法存款");}balance += amount;System.out.println("存款成功,当前余额: " + balance);}public void withdraw(double amount) {if (amount <= 0) {throw new IllegalArgumentException("取款金额必须大于零");}if (!active) {throw new IllegalStateException("账户不活跃,无法取款");}if (amount > balance) {throw new IllegalStateException("余额不足,当前余额: " + balance);}balance -= amount;System.out.println("取款成功,当前余额: " + balance);}// 显示账户信息public void displayAccountInfo() {System.out.println("账户信息:");System.out.println("账号: " + accountNumber);System.out.println("账户持有人: " + accountHolderName);System.out.println("余额: " + balance);System.out.println("账户类型: " + accountType);System.out.println("账户状态: " + (active ? "活跃" : "不活跃"));}
}
使用示例:
public class BankDemo {public static void main(String[] args) {BankAccount account = new BankAccount("12345678", "张三", 1000.0, "储蓄账户");// 获取账户信息System.out.println("账户持有人: " + account.getAccountHolderName());System.out.println("当前余额: " + account.getBalance());// 存款try {account.deposit(500.0);} catch (IllegalArgumentException e) {System.out.println("存款失败: " + e.getMessage());}// 取款try {account.withdraw(200.0);} catch (Exception e) {System.out.println("取款失败: " + e.getMessage());}// 尝试取款超过余额try {account.withdraw(2000.0);} catch (Exception e) {System.out.println("取款失败: " + e.getMessage());}// 显示账户信息account.displayAccountInfo();// 设置账户不活跃account.setActive(false);// 尝试在不活跃状态下存款try {account.deposit(100.0);} catch (IllegalStateException e) {System.out.println("存款失败: " + e.getMessage());}}
}
在上面的示例中,BankAccount
类的所有属性都被声明为私有的,只能通过类的方法来访问和修改。特别是余额(balance
)没有直接的setter方法,而是通过deposit
和withdraw
方法来修改,这样可以确保余额的变化符合业务规则。这就是封装的实际应用。
3. 继承
3.1 什么是继承
继承是面向对象编程的一个重要特性,它允许一个类(子类)获取另一个类(父类)的属性和方法。通过继承,子类可以重用父类的代码,同时添加自己特有的功能,这有助于代码的重用和层次结构的建立。
在Java中,继承使用extends
关键字来实现:
public class 子类 extends 父类 {// 子类的属性和方法
}
继承的关键概念:
- 父类/超类(Superclass):被继承的类
- 子类/派生类(Subclass):继承自其他类的类
- 子类继承父类的所有非私有成员(属性和方法)
- Java只支持单继承:一个类只能直接继承一个父类
3.2 继承的类型
Java中的继承主要有以下几种类型:
-
单一继承(Single Inheritance):一个类只继承一个类
class A {} class B extends A {} // B继承A
-
多层继承(Multilevel Inheritance):一个类继承自另一个子类
class A {} class B extends A {} // B继承A class C extends B {} // C继承B,形成多层继承
-
层次继承(Hierarchical Inheritance):多个类继承同一个父类
class A {} class B extends A {} // B继承A class C extends A {} // C也继承A,形成层次继承
Java不直接支持多重继承(Multiple Inheritance),但可以通过接口实现类似功能。
3.3 super关键字
super
关键字用于引用父类的成员(属性和方法):
super.变量名
:访问父类的变量super.方法名()
:调用父类的方法super()
:调用父类的构造函数
示例:
public class Animal {protected String name;public Animal(String name) {this.name = name;}public void eat() {System.out.println(name + "正在吃东西");}
}public class Dog extends Animal {private String breed;public Dog(String name, String breed) {super(name); // 调用父类构造函数this.breed = breed;}@Overridepublic void eat() {super.eat(); // 调用父类的eat方法System.out.println(name + "是一只" + breed + ",它喜欢吃骨头");}public void displayInfo() {System.out.println("名字: " + super.name); // 访问父类的变量System.out.println("品种: " + this.breed);}
}
3.4 方法覆盖
**方法覆盖(Method Overriding)**是指子类提供了父类中已有方法的特定实现。当子类对象调用被覆盖的方法时,执行的是子类中的版本。
方法覆盖的规则:
- 方法的签名(名称和参数列表)必须相同
- 返回类型必须相同或是父类返回类型的子类型
- 访问修饰符不能比父类的更严格
- 不能覆盖被声明为final的方法
- 不能覆盖被声明为static的方法(这种情况下是方法隐藏,不是覆盖)
- 不能覆盖被声明为private的方法
示例:
public class Vehicle {public void run() {System.out.println("车辆在运行");}public void stop() {System.out.println("车辆停止");}public String getInfo() {return "这是一个车辆";}
}public class Car extends Vehicle {private String brand;public Car(String brand) {this.brand = brand;}@Override // 使用@Override注解表明这是一个覆盖方法public void run() {System.out.println(brand + "汽车在公路上行驶");}@Overridepublic String getInfo() {return "这是一辆" + brand + "汽车";}// 添加新方法public void honk() {System.out.println(brand + "汽车鸣笛");}
}
在上面的例子中,Car
类覆盖了Vehicle
类的run()
和getInfo()
方法,同时添加了一个新的honk()
方法。
3.5 构造函数与继承
构造函数不会被继承,但父类的构造函数会在子类对象创建时被调用。理解继承中的构造函数调用链非常重要:
- 如果子类构造函数没有显式调用父类构造函数,Java会自动调用父类的无参构造函数
- 如果父类没有无参构造函数,子类必须显式调用父类的有参构造函数
- 对父类构造函数的调用必须是子类构造函数的第一条语句
示例:
public class Person {private String name;private int age;// 有参构造函数public Person(String name, int age) {this.name = name;this.age = age;}// 获取个人信息public String getInfo() {return "姓名: " + name + ", 年龄: " + age;}
}public class Student extends Person {private String studentId;private String major;// 学生构造函数必须调用父类构造函数public Student(String name, int age, String studentId, String major) {super(name, age); // 调用父类构造函数this.studentId = studentId;this.major = major;}// 覆盖父类的getInfo方法@Overridepublic String getInfo() {return super.getInfo() + ", 学号: " + studentId + ", 专业: " + major;}// 特有方法public void study() {System.out.println("学生正在学习" + major);}
}public class Teacher extends Person {private String employeeId;private String department;private String[] courses;public Teacher(String name, int age, String employeeId, String department, String[] courses) {super(name, age); // 调用父类构造函数this.employeeId = employeeId;this.department = department;this.courses = courses;}@Overridepublic String getInfo() {String courseList = String.join(", ", courses);return super.getInfo() + ", 工号: " + employeeId + ", 部门: " + department + ", 课程: " + courseList;}// 特有方法public void teach() {System.out.println("老师正在教授课程: " + String.join(", ", courses));}
}
使用示例:
public class SchoolDemo {public static void main(String[] args) {// 创建学生对象Student student = new Student("李华", 20, "S20210001", "计算机科学");System.out.println(student.getInfo());student.study();// 创建老师对象String[] courses = {"Java编程", "数据结构"};Teacher teacher = new Teacher("王教授", 45, "T10010", "计算机系", courses);System.out.println(teacher.getInfo());teacher.teach();}
}
3.6 final关键字
final
关键字在继承中有重要作用:
- final类:不能被继承
- final方法:不能被子类覆盖
- final变量:不能被修改(常量)
示例:
public final class FinalClass {// 这个类不能被继承
}public class Parent {public final void finalMethod() {// 这个方法不能被覆盖}
}public class Child extends Parent {// 试图覆盖final方法会导致编译错误// @Override// public void finalMethod() { } // 错误!
}
Java标准库中的String
、Integer
、System
等类都是final
类,不能被继承。
3.7 继承的优缺点
优点:
- 代码重用:子类可以重用父类的代码,减少重复代码
- 建立层次结构:可以建立类的层次结构,反映事物的层次关系
- 多态性:通过继承可以实现多态,提高代码的灵活性
缺点:
- 紧耦合:父类与子类间存在强耦合,父类的变化可能影响子类
- 可能破坏封装:为了让子类能重用,父类可能需要暴露一些实现细节
- 复杂性:过深的继承层次会增加系统复杂性
3.8 继承实践示例
下面通过一个图形类的继承层次来展示继承的实际应用:
// 基类:形状
public abstract class Shape {protected String color;protected boolean filled;// 构造函数public Shape() {this.color = "白色";this.filled = true;}public Shape(String color, boolean filled) {this.color = color;this.filled = filled;}// Getter和Setter方法public String getColor() {return color;}public void setColor(String color) {this.color = color;}public boolean isFilled() {return filled;}public void setFilled(boolean filled) {this.filled = filled;}// 抽象方法public abstract double getArea();public abstract double getPerimeter();@Overridepublic String toString() {return "形状[颜色=" + color + ", 填充=" + filled + "]";}
}// 圆形
public class Circle extends Shape {private double radius;public Circle() {super();this.radius = 1.0;}public Circle(double radius) {super();this.radius = radius;}public Circle(double radius, String color, boolean filled) {super(color, filled);this.radius = radius;}public double getRadius() {return radius;}public void setRadius(double radius) {this.radius = radius;}@Overridepublic double getArea() {return Math.PI * radius * radius;}@Overridepublic double getPerimeter() {return 2 * Math.PI * radius;}@Overridepublic String toString() {return "圆形[" + super.toString() + ", 半径=" + radius + "]";}
}// 矩形
public class Rectangle extends Shape {private double width;private double height;public Rectangle() {super();this.width = 1.0;this.height = 1.0;}public Rectangle(double width, double height) {super();this.width = width;this.height = height;}public Rectangle(double width, double height, String color, boolean filled) {super(color, filled);this.width = width;this.height = height;}public double getWidth() {return width;}public void setWidth(double width) {this.width = width;}public double getHeight() {return height;}public void setHeight(double height) {this.height = height;}@Overridepublic double getArea() {return width * height;}@Overridepublic double getPerimeter() {return 2 * (width + height);}@Overridepublic String toString() {return "矩形[" + super.toString() + ", 宽=" + width + ", 高=" + height + "]";}
}// 正方形(矩形的特例)
public class Square extends Rectangle {public Square() {super(1.0, 1.0);}public Square(double side) {super(side, side);}public Square(double side, String color, boolean filled) {super(side, side, color, filled);}// 确保宽和高相等@Overridepublic void setWidth(double side) {super.setWidth(side);super.setHeight(side);}@Overridepublic void setHeight(double side) {super.setWidth(side);super.setHeight(side);}public void setSide(double side) {setWidth(side);}public double getSide() {return getWidth();}@Overridepublic String toString() {return "正方形[" + super.toString().replace("矩形", "") + "]";}
}
测试代码:
public class ShapeDemo {public static void main(String[] args) {// 创建圆形Circle circle = new Circle(5.0, "红色", true);System.out.println(circle);System.out.println("面积: " + String.format("%.2f", circle.getArea()));System.out.println("周长: " + String.format("%.2f", circle.getPerimeter()));// 创建矩形Rectangle rectangle = new Rectangle(4.0, 6.0, "蓝色", false);System.out.println(rectangle);System.out.println("面积: " + String.format("%.2f", rectangle.getArea()));System.out.println("周长: " + String.format("%.2f", rectangle.getPerimeter()));// 创建正方形Square square = new Square(5.0, "绿色", true);System.out.println(square);System.out.println("面积: " + String.format("%.2f", square.getArea()));System.out.println("周长: " + String.format("%.2f", square.getPerimeter()));// 修改正方形的边长square.setSide(10.0);System.out.println("修改后的正方形: " + square);System.out.println("新面积: " + String.format("%.2f", square.getArea()));}
}
在这个示例中,Shape
类是一个抽象基类,定义了所有形状共有的属性和方法。Circle
、Rectangle
和Square
是子类,它们继承了Shape
类的属性和方法,并提供了各自的特定实现。
特别注意Square
类继承自Rectangle
类,并覆盖了setWidth
和setHeight
方法,确保正方形的宽和高始终相等。这展示了继承和方法覆盖的实际应用。
4. 多态
4.1 什么是多态
**多态(Polymorphism)**是面向对象编程的第三个核心特性,它允许使用父类类型的引用指向子类的对象,并且在运行时能够根据实际对象类型调用正确的方法。简单来说,多态使得同一个行为在不同的对象上有不同的表现形式。
多态的核心概念:
- 一个引用变量可以指向不同类型的对象
- 方法调用取决于引用变量所指向的实际对象类型
- 在运行时确定调用的方法
多态通常基于以下两个原则:
- 继承:子类继承父类的方法
- 方法覆盖:子类覆盖父类的方法
4.2 运行时多态
运行时多态,也称为动态绑定或后期绑定,是在程序运行时确定要调用的方法。这主要通过方法覆盖和向上转型(upcasting)实现。
示例:
public class Animal {public void makeSound() {System.out.println("动物发出声音");}
}public class Dog extends Animal {@Overridepublic void makeSound() {System.out.println("狗在汪汪叫");}public void fetch() {System.out.println("狗在捡东西");}
}public class Cat extends Animal {@Overridepublic void makeSound() {System.out.println("猫在喵喵叫");}public void scratch() {System.out.println("猫在抓东西");}
}public class PolymorphismDemo {public static void main(String[] args) {// 创建Animal类型的引用变量Animal myAnimal;// 指向Dog对象myAnimal = new Dog();myAnimal.makeSound(); // 输出:狗在汪汪叫// 指向Cat对象myAnimal = new Cat();myAnimal.makeSound(); // 输出:猫在喵喵叫// 注意:通过Animal引用,不能调用Dog或Cat特有的方法// myAnimal.fetch(); // 编译错误// myAnimal.scratch(); // 编译错误// 如果需要调用特有方法,需要进行类型转换Dog myDog = new Dog();Animal animal = myDog; // 向上转型if (animal instanceof Dog) {Dog dog = (Dog) animal; // 向下转型dog.fetch(); // 现在可以调用Dog特有的方法}}
}
在上面的例子中,myAnimal
是Animal
类型的引用变量,但它可以指向Dog
或Cat
的对象。当调用makeSound()
方法时,JVM会根据myAnimal
实际指向的对象类型来决定调用哪个版本的方法,这就是运行时多态。
4.3 编译时多态
编译时多态,也称为静态绑定或早期绑定,是在编译时确定要调用的方法。这主要通过方法重载(Method Overloading)实现。
方法重载是指在同一个类中定义多个同名但参数列表不同的方法。参数列表的不同可以是参数类型不同、参数数量不同或两者都不同。
示例:
public class Calculator {// 方法重载:两个整数相加public int add(int a, int b) {return a + b;}// 方法重载:三个整数相加public int add(int a, int b, int c) {return a + b + c;}// 方法重载:两个浮点数相加public double add(double a, double b) {return a + b;}// 方法重载:整数和浮点数相加public double add(int a, double b) {return a + b;}// 方法重载:浮点数和整数相加public double add(double a, int b) {return a + b;}
}public class OverloadingDemo {public static void main(String[] args) {Calculator calc = new Calculator();System.out.println("2 + 3 = " + calc.add(2, 3));System.out.println("2 + 3 + 4 = " + calc.add(2, 3, 4));System.out.println("2.5 + 3.7 = " + calc.add(2.5, 3.7));System.out.println("2 + 3.7 = " + calc.add(2, 3.7));System.out.println("2.5 + 3 = " + calc.add(2.5, 3));}
}
在这个例子中,Calculator
类定义了5个不同的add
方法,它们具有相同的名称但参数列表不同。编译器会根据调用时提供的参数类型和数量来决定调用哪个方法。
4.4 抽象类
抽象类是一种不能被实例化的类,它通常包含一个或多个抽象方法(没有实现的方法)。抽象类的主要目的是为其子类提供一个通用的模板,强制子类实现某些方法。
抽象类的特点:
- 使用
abstract
关键字声明 - 可以包含抽象方法和非抽象方法
- 不能直接实例化
- 子类必须实现所有抽象方法,除非子类也是抽象类
示例:
public abstract class Vehicle {private String brand;private String model;public Vehicle(String brand, String model) {this.brand = brand;this.model = model;}// 获取品牌和型号public String getBrand() {return brand;}public String getModel() {return model;}// 抽象方法 - 启动public abstract void start();// 抽象方法 - 停止public abstract void stop();// 非抽象方法 - 显示信息public void displayInfo() {System.out.println("车辆信息:" + brand + " " + model);}
}public class Car extends Vehicle {private int numOfDoors;public Car(String brand, String model, int numOfDoors) {super(brand, model);this.numOfDoors = numOfDoors;}// 实现抽象方法@Overridepublic void start() {System.out.println(getBrand() + " " + getModel() + " 汽车启动,发动引擎");}@Overridepublic void stop() {System.out.println(getBrand() + " " + getModel() + " 汽车停止,熄火");}// 特有方法public void openTrunk() {System.out.println(getBrand() + " " + getModel() + " 打开后备箱");}
}public class Motorcycle extends Vehicle {private boolean hasSidecar;public Motorcycle(String brand, String model, boolean hasSidecar) {super(brand, model);this.hasSidecar = hasSidecar;}// 实现抽象方法@Overridepublic void start() {System.out.println(getBrand() + " " + getModel() + " 摩托车启动,踢启动杆");}@Overridepublic void stop() {System.out.println(getBrand() + " " + getModel() + " 摩托车停止,关闭引擎");}// 特有方法public void wheelie() {if(!hasSidecar) {System.out.println(getBrand() + " " + getModel() + " 摩托车前轮离地");} else {System.out.println(getBrand() + " " + getModel() + " 有边车的摩托车不能前轮离地");}}
}
测试抽象类:
public class VehicleDemo {public static void main(String[] args) {// Vehicle vehicle = new Vehicle("Unknown", "Unknown"); // 错误:抽象类不能实例化// 创建Car对象Car car = new Car("丰田", "卡罗拉", 4);car.displayInfo();car.start();car.stop();car.openTrunk();// 创建Motorcycle对象Motorcycle motorcycle = new Motorcycle("本田", "CBR", false);motorcycle.displayInfo();motorcycle.start();motorcycle.stop();motorcycle.wheelie();// 使用多态System.out.println("\n使用多态演示:");Vehicle vehicle;vehicle = car;vehicle.displayInfo();vehicle.start(); // 调用Car的start方法vehicle = motorcycle;vehicle.displayInfo();vehicle.start(); // 调用Motorcycle的start方法}
}
4.5 接口
接口(Interface)是Java中实现多态的另一种重要机制,它定义了一组方法签名(方法的名称、参数类型和返回类型),但不提供方法的实现。接口的主要目的是定义一个类应该做什么,而不关心它如何做。
接口的特点:
- 使用
interface
关键字声明 - 方法默认是
public abstract
的 - 字段默认是
public static final
的 - 一个类可以实现多个接口
- 接口之间可以继承
从Java 8开始,接口可以包含默认方法(有实现的方法)和静态方法。
示例:
// 定义一个接口
public interface Flyable {// 默认是public abstractvoid fly();// 检查是否可以飞行boolean canFly();
}// 定义另一个接口
public interface Swimmable {// 游泳方法void swim();// 检查是否可以游泳boolean canSwim();
}// 实现一个接口的类
public class Bird implements Flyable {private String name;private boolean canFly;public Bird(String name, boolean canFly) {this.name = name;this.canFly = canFly;}@Overridepublic void fly() {if (canFly) {System.out.println(name + "在天空中飞翔");} else {System.out.println(name + "不能飞行");}}@Overridepublic boolean canFly() {return canFly;}
}// 实现多个接口的类
public class Duck implements Flyable, Swimmable {private String name;public Duck(String name) {this.name = name;}@Overridepublic void fly() {System.out.println(name + "在低空飞行");}@Overridepublic boolean canFly() {return true;}@Overridepublic void swim() {System.out.println(name + "在水中游泳");}@Overridepublic boolean canSwim() {return true;}// Duck特有的方法public void quack() {System.out.println(name + "嘎嘎叫");}
}// 使用接口类型的变量
public class InterfaceDemo {public static void main(String[] args) {// 创建Bird对象Bird sparrow = new Bird("麻雀", true);Bird penguin = new Bird("企鹅", false);// 创建Duck对象Duck mallard = new Duck("绿头鸭");// 使用接口类型的变量Flyable flyingCreature;flyingCreature = sparrow;testFlight(flyingCreature); // 麻雀在天空中飞翔flyingCreature = penguin;testFlight(flyingCreature); // 企鹅不能飞行flyingCreature = mallard;testFlight(flyingCreature); // 绿头鸭在低空飞行// 测试多个接口if (mallard instanceof Swimmable) {Swimmable swimmingCreature = mallard;swimmingCreature.swim(); // 绿头鸭在水中游泳}// 直接调用Duck的方法mallard.quack(); // 绿头鸭嘎嘎叫}// 测试飞行的方法public static void testFlight(Flyable creature) {if (creature.canFly()) {creature.fly();} else {System.out.println("这个生物不能飞行");}}
}
在Java 8及以后,接口可以包含:
- 默认方法:使用
default
关键字声明,提供默认实现 - 静态方法:使用
static
关键字声明,属于接口本身
示例:
public interface Movable {// 抽象方法void move();// 默认方法default void startMoving() {System.out.println("开始移动");move(); // 调用抽象方法}// 静态方法static void showInfo() {System.out.println("这是Movable接口,定义了移动的能力");}
}public class Robot implements Movable {private String name;public Robot(String name) {this.name = name;}@Overridepublic void move() {System.out.println(name + "机器人在移动");}
}public class DefaultMethodDemo {public static void main(String[] args) {// 调用接口的静态方法Movable.showInfo();// 使用实现类Robot robot = new Robot("R2D2");robot.move(); // 调用实现的方法robot.startMoving(); // 调用默认方法}
}
4.6 多态实践示例
下面通过一个支付系统的示例来展示多态的实际应用:
// 支付接口
public interface PaymentMethod {boolean processPayment(double amount);void getPaymentDetails();
}// 信用卡支付
public class CreditCardPayment implements PaymentMethod {private String cardNumber;private String cardHolderName;private String expiryDate;private String cvv;public CreditCardPayment(String cardNumber, String cardHolderName, String expiryDate, String cvv) {this.cardNumber = maskCardNumber(cardNumber);this.cardHolderName = cardHolderName;this.expiryDate = expiryDate;this.cvv = cvv;}private String maskCardNumber(String cardNumber) {// 只显示最后4位数字String lastFourDigits = cardNumber.substring(cardNumber.length() - 4);return "xxxx-xxxx-xxxx-" + lastFourDigits;}@Overridepublic boolean processPayment(double amount) {// 模拟信用卡支付处理System.out.println("使用信用卡 " + cardNumber + " 支付 " + amount + " 元");// 假设支付成功return true;}@Overridepublic void getPaymentDetails() {System.out.println("支付方式: 信用卡");System.out.println("卡号: " + cardNumber);System.out.println("持卡人: " + cardHolderName);System.out.println("到期日: " + expiryDate);}
}// 支付宝支付
public class AlipayPayment implements PaymentMethod {private String alipayId;private String userName;public AlipayPayment(String alipayId, String userName) {this.alipayId = alipayId;this.userName = userName;}@Overridepublic boolean processPayment(double amount) {// 模拟支付宝支付处理System.out.println("使用支付宝账号 " + alipayId + " 支付 " + amount + " 元");// 假设支付成功return true;}@Overridepublic void getPaymentDetails() {System.out.println("支付方式: 支付宝");System.out.println("账号: " + alipayId);System.out.println("用户名: " + userName);}
}// 微信支付
public class WechatPayment implements PaymentMethod {private String wechatId;private String phoneNumber;public WechatPayment(String wechatId, String phoneNumber) {this.wechatId = wechatId;this.phoneNumber = phoneNumber;}@Overridepublic boolean processPayment(double amount) {// 模拟微信支付处理System.out.println("使用微信账号 " + wechatId + " 支付 " + amount + " 元");// 假设支付成功return true;}@Overridepublic void getPaymentDetails() {System.out.println("支付方式: 微信支付");System.out.println("微信号: " + wechatId);System.out.println("手机号: " + phoneNumber);}
}// 支付处理类
public class PaymentProcessor {public static void processOrder(double amount, PaymentMethod paymentMethod) {System.out.println("开始处理 " + amount + " 元的订单");// 显示支付详情paymentMethod.getPaymentDetails();// 处理支付boolean success = paymentMethod.processPayment(amount);if (success) {System.out.println("支付成功,订单处理完成");} else {System.out.println("支付失败,请重试");}System.out.println("--------------------------");}
}// 测试支付系统
public class PaymentDemo {public static void main(String[] args) {// 创建不同的支付方式PaymentMethod creditCard = new CreditCardPayment("1234567890123456", "张三", "12/25", "123");PaymentMethod alipay = new AlipayPayment("zhangsan@example.com", "张三");PaymentMethod wechat = new WechatPayment("wxid_123456", "13812345678");// 处理不同的支付PaymentProcessor.processOrder(100.50, creditCard);PaymentProcessor.processOrder(200.75, alipay);PaymentProcessor.processOrder(150.25, wechat);}
}
在这个示例中,PaymentMethod
接口定义了所有支付方式必须实现的方法。CreditCardPayment
、AlipayPayment
和WechatPayment
类实现了这个接口,提供了各自的支付处理逻辑。
PaymentProcessor
类使用多态来处理不同的支付方式,它接受PaymentMethod
类型的参数,根据实际传入的对象类型调用相应的方法。这展示了多态的核心优势:代码的可扩展性和灵活性。如果将来需要添加新的支付方式,只需创建一个新类实现PaymentMethod
接口,而不需要修改PaymentProcessor
类的代码。
5. 封装、继承与多态的关系
Java面向对象编程的三大特性——封装、继承和多态是相互关联、相互支持的,它们共同构成了面向对象编程的基础。
5.1 三者之间的联系
-
封装是基础:封装通过将数据和方法绑定在一起,并控制对它们的访问,为继承和多态奠定了基础。一个设计良好的封装类可以更方便地被继承和扩展。
-
继承建立在封装之上:继承依赖于封装来确保父类的实现细节被适当地隐藏或暴露。子类只能继承父类中非private的成员,这正是封装原则在起作用。
-
多态依赖于继承:多态主要通过继承和方法覆盖来实现。没有继承,就无法建立类的层次结构,也就无法实现对象的多态性。
-
循环支持:三者形成了一个循环支持的关系——封装使继承更安全,继承使多态成为可能,而多态又使封装的类更加灵活和可扩展。
5.2 三者的协同工作
以下是一个综合示例,展示三大特性如何协同工作:
// 产品类 - 展示封装
public abstract class Product {private String id;private String name;private double price;private int stock;// 构造函数public Product(String id, String name, double price, int stock) {this.id = id;this.name = name;setPrice(price); // 使用setter进行验证setStock(stock); // 使用setter进行验证}// getter和setter方法public String getId() {return id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public double getPrice() {return price;}public void setPrice(double price) {if (price < 0) {throw new IllegalArgumentException("价格不能为负数");}this.price = price;}public int getStock() {return stock;}public void setStock(int stock) {if (stock < 0) {throw new IllegalArgumentException("库存不能为负数");}this.stock = stock;}// 更新库存public void updateStock(int quantity) {int newStock = this.stock - quantity;setStock(newStock);}// 抽象方法 - 计算折扣价格public abstract double getDiscountPrice();// 显示产品信息public void displayInfo() {System.out.println("产品ID: " + id);System.out.println("名称: " + name);System.out.println("价格: " + price);System.out.println("折扣价: " + getDiscountPrice());System.out.println("库存: " + stock);}
}// 电子产品类 - 展示继承
public class Electronics extends Product {private String brand;private String model;private int warrantyPeriod; // 保修期(月)public Electronics(String id, String name, double price, int stock, String brand, String model, int warrantyPeriod) {super(id, name, price, stock);this.brand = brand;this.model = model;this.warrantyPeriod = warrantyPeriod;}// getter和setterpublic String getBrand() {return brand;}public String getModel() {return model;}public int getWarrantyPeriod() {return warrantyPeriod;}// 实现抽象方法 - 电子产品折扣5%@Overridepublic double getDiscountPrice() {return getPrice() * 0.95;}// 覆盖父类方法@Overridepublic void displayInfo() {super.displayInfo();System.out.println("品牌: " + brand);System.out.println("型号: " + model);System.out.println("保修期: " + warrantyPeriod + "个月");}// 特有方法public void extendWarranty(int months) {this.warrantyPeriod += months;System.out.println("保修期已延长" + months + "个月,现在总保修期为" + warrantyPeriod + "个月");}
}// 服装类 - 展示继承
public class Clothing extends Product {private String size;private String color;private String material;public Clothing(String id, String name, double price, int stock,String size, String color, String material) {super(id, name, price, stock);this.size = size;this.color = color;this.material = material;}// getter和setterpublic String getSize() {return size;}public String getColor() {return color;}public String getMaterial() {return material;}// 实现抽象方法 - 服装折扣10%@Overridepublic double getDiscountPrice() {return getPrice() * 0.9;}// 覆盖父类方法@Overridepublic void displayInfo() {super.displayInfo();System.out.println("尺码: " + size);System.out.println("颜色: " + color);System.out.println("材质: " + material);}// 特有方法public boolean isAvailableInSize(String size) {return this.size.equals(size);}
}// 产品优惠接口 - 用于多态
public interface Promotable {double applyPromotion(double price);String getPromotionInfo();
}// 实现产品优惠的电子产品 - 展示多态
public class PromotionalElectronics extends Electronics implements Promotable {private double promotionRate;private String promotionDesc;public PromotionalElectronics(String id, String name, double price, int stock,String brand, String model, int warrantyPeriod,double promotionRate, String promotionDesc) {super(id, name, price, stock, brand, model, warrantyPeriod);this.promotionRate = promotionRate;this.promotionDesc = promotionDesc;}// 实现接口方法@Overridepublic double applyPromotion(double price) {return price * (1 - promotionRate);}@Overridepublic String getPromotionInfo() {return promotionDesc + ",优惠" + (promotionRate * 100) + "%";}// 覆盖父类的折扣方法,考虑促销@Overridepublic double getDiscountPrice() {double regularDiscountPrice = super.getDiscountPrice();return applyPromotion(regularDiscountPrice);}
}// 产品管理类 - 使用多态
public class ProductManager {// 使用多态处理不同类型的产品public void processProduct(Product product) {System.out.println("处理产品:" + product.getName());product.displayInfo();// 处理促销产品if (product instanceof Promotable) {Promotable promotableProduct = (Promotable) product;System.out.println("促销信息:" + promotableProduct.getPromotionInfo());System.out.println("最终价格:" + product.getDiscountPrice());}System.out.println("------------------------");}
}// 测试三大特性的综合应用
public class OOPFeaturesDemo {public static void main(String[] args) {// 创建产品Electronics laptop = new Electronics("E001", "笔记本电脑", 5999.0, 10, "联想", "ThinkPad", 12);Clothing tShirt = new Clothing("C001", "T恤", 99.0, 100, "L", "蓝色", "棉");PromotionalElectronics smartphone = new PromotionalElectronics("E002", "智能手机", 2999.0, 20, "小米", "Note 10", 12, 0.15, "618大促");// 创建产品管理器ProductManager manager = new ProductManager();// 处理不同类型的产品(展示多态)manager.processProduct(laptop);manager.processProduct(tShirt);manager.processProduct(smartphone);// 演示封装 - 通过方法访问和修改私有属性System.out.println("演示封装:");System.out.println("原始库存:" + laptop.getStock());laptop.updateStock(2);System.out.println("更新后库存:" + laptop.getStock());try {laptop.setPrice(-100); // 应该抛出异常} catch (IllegalArgumentException e) {System.out.println("捕获异常:" + e.getMessage());}// 演示继承 - 调用特有方法System.out.println("\n演示继承:");laptop.extendWarranty(6);System.out.println("T恤是否有L码:" + tShirt.isAvailableInSize("L"));}
}
在这个综合示例中:
- 封装体现在
Product
类中的私有属性和对外提供的方法,以及在setPrice
和setStock
方法中对数据的验证。 - 继承体现在
Electronics
和Clothing
类继承自Product
类,并覆盖和扩展了父类的方法。 - 多态体现在
ProductManager.processProduct()
方法接受任何Product
类型的对象,以及对Promotable
接口的使用。
这个例子展示了三大特性如何协同工作,创建出灵活、可维护的代码。
6. 面向对象设计的最佳实践
6.1 封装的最佳实践
-
使字段私有:除非有充分理由,否则始终将类的字段声明为私有的。
// 好的做法 private int count;// 避免这样做 public int count;
-
提供访问器方法:为需要访问的私有字段提供getter和setter方法。
private String name;public String getName() {return name; }public void setName(String name) {this.name = name; }
-
在setter方法中验证数据:确保对象始终处于有效状态。
public void setAge(int age) {if (age < 0 || age > 150) {throw new IllegalArgumentException("年龄必须在0到150之间");}this.age = age; }
-
不要暴露可变对象:返回可变对象的副本而不是引用。
private Date birthDate;// 好的做法 public Date getBirthDate() {return (Date) birthDate.clone(); // 返回副本 }// 避免这样做 public Date getBirthDate() {return birthDate; // 返回引用 }
-
使用不可变类:当对象状态不应改变时,考虑使类不可变。
public final class ImmutablePerson {private final String name;private final int age;public ImmutablePerson(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;}// 没有setter方法 }
6.2 继承的最佳实践
-
遵循里氏替换原则:子类对象应该能够替换父类对象使用,而不改变程序的正确性。
// 不好的设计 - 违反里氏替换原则 class Rectangle {private int width;private int height;public void setWidth(int width) {this.width = width;}public void setHeight(int height) {this.height = height;}public int getArea() {return width * height;} }class Square extends Rectangle {@Overridepublic void setWidth(int width) {super.setWidth(width);super.setHeight(width); // 修改了父类的行为}@Overridepublic void setHeight(int height) {super.setWidth(height); // 修改了父类的行为super.setHeight(height);} }
-
组合优于继承:当可能时,优先使用组合而不是继承。
// 使用继承 class Car extends Vehicle { ... }// 使用组合(通常更好) class Car {private Engine engine;private Chassis chassis;... }
-
设计用于继承或声明为final:如果类不是设计用于继承的,应声明为final。
// 不设计用于继承的类 public final class Utility {// 工具方法 }// 设计用于继承的类 public abstract class AbstractTemplate {// 模板方法public final void templateMethod() {step1();step2();step3();}// 由子类实现的步骤protected abstract void step1();protected abstract void step2();protected void step3() {// 默认实现} }
-
不要过度使用继承:继承层次不要太深,一般不超过3层。
-
覆盖方法时记得调用super:特别是构造函数、
equals()
、hashCode()
和clone()
方法。@Override public boolean equals(Object obj) {if (!super.equals(obj)) {return false;}// 比较当前类的字段 }
6.3 多态的最佳实践
-
依赖接口,而不是具体实现:编程时针对接口或抽象类,而不是具体类。
// 好的做法 List<String> list = new ArrayList<>();// 不推荐 ArrayList<String> list = new ArrayList<>();
-
使用抽象类定义类型层次结构:当类型之间有明确的层次关系时使用抽象类。
public abstract class Animal { ... } public class Mammal extends Animal { ... } public class Bird extends Animal { ... }
-
使用接口定义能力:当类型之间没有明确的层次关系,但共享某种能力时使用接口。
public interface Flyable { ... } public interface Swimmable { ... }
-
避免向下转型:尽量避免使用类型转换,尝试在设计层面解决问题。
// 避免这样做 if (animal instanceof Dog) {Dog dog = (Dog) animal;dog.bark(); }// 更好的做法 interface Soundable {void makeSound(); }class Dog implements Soundable {@Overridepublic void makeSound() {bark();}private void bark() {System.out.println("汪汪");} }// 使用 soundable.makeSound(); // 多态调用
-
使用工厂模式创建对象:使用工厂来隐藏对象创建的细节。
public interface Shape {void draw(); }public class ShapeFactory {public static Shape createShape(String type) {if ("circle".equalsIgnoreCase(type)) {return new Circle();} else if ("rectangle".equalsIgnoreCase(type)) {return new Rectangle();} else {throw new IllegalArgumentException("未知形状类型");}} }// 使用 Shape shape = ShapeFactory.createShape("circle"); shape.draw(); // 多态调用
7. 常见面试问题
以下是一些关于Java封装、继承和多态的常见面试问题及其答案:
7.1 封装相关问题
Q1: 什么是封装?为什么它是重要的?
A: 封装是一种将数据(属性)和操作这些数据的方法绑定在一起,并对外部世界隐藏内部实现细节的机制。它的重要性在于:
- 提高安全性,防止对象数据被随意修改
- 提供灵活性,允许更改内部实现而不影响外部代码
- 降低耦合度,促进模块化设计
- 允许对数据进行验证,确保对象状态始终有效
Q2: Java中的访问修饰符有哪些,它们各自的作用是什么?
A: Java中有四种访问修饰符:
private
: 只在当前类中可见default
(无修饰符): 在同一包中可见protected
: 在同一包和所有子类中可见public
: 在所有地方可见
它们的作用是控制类、字段和方法的可见性,是实现封装的关键机制。
Q3: getter和setter方法的作用是什么?
A: getter和setter方法用于访问和修改类的私有字段:
- getter方法(如
getName()
)用于获取字段值 - setter方法(如
setName()
)用于设置字段值
它们的主要作用包括:
- 控制对类字段的访问
- 允许在修改字段前进行数据验证
- 允许计算或转换字段值
- 允许在不改变公共接口的情况下更改内部实现
7.2 继承相关问题
Q1: Java为什么不支持多重继承?如何在Java中模拟多重继承?
A: Java不支持类的多重继承主要是为了避免"菱形问题"(也称为"钻石问题"),即当一个类从两个实现同一方法的父类继承时可能产生的歧义。
可以通过以下方式在Java中模拟多重继承:
- 使用接口:一个类可以实现多个接口
- 使用组合:在类中包含其他类的对象
- 使用默认方法:Java 8引入的接口默认方法
Q2: 在Java中,super关键字的作用是什么?
A: super
关键字用于引用父类:
super.方法名()
:调用父类的方法super.变量名
:访问父类的变量super()
:调用父类的构造函数
它主要用于:
- 当子类和父类有同名成员时,访问父类成员
- 调用父类的构造函数
- 在方法覆盖时调用父类的方法
Q3: final关键字用于类、方法和变量时分别有什么作用?
A: final关键字的作用:
- 用于类:类不能被继承
- 用于方法:方法不能被子类覆盖
- 用于变量:变量成为常量,一旦赋值不能改变
7.3 多态相关问题
Q1: 什么是多态?Java中如何实现多态?
A: 多态是指同一个操作可以在不同的对象上有不同的行为。Java中通过以下方式实现多态:
- 方法覆盖(运行时多态):子类重写父类的方法
- 方法重载(编译时多态):同一个类中定义多个同名但参数不同的方法
运行时多态是多态的核心,它允许父类引用指向子类对象,并在运行时根据实际对象类型调用相应的方法。
Q2: 方法重载和方法覆盖有什么区别?
A:
-
方法重载(Overloading):
- 发生在同一个类中
- 方法名相同但参数列表不同(类型、数量或顺序)
- 返回类型可以相同也可以不同
- 编译时绑定(静态绑定)
-
方法覆盖(Overriding):
- 发生在子类和父类之间
- 方法签名(名称和参数列表)完全相同
- 返回类型必须相同或是父类方法返回类型的子类型
- 运行时绑定(动态绑定)
Q3: 抽象类和接口有什么区别?什么时候应该使用抽象类,什么时候应该使用接口?
A: 主要区别:
特性 | 抽象类 | 接口 |
---|---|---|
实现方法 | 可以有具体方法实现 | 只能有默认方法、静态方法和私有方法(Java 8+) |
构造函数 | 可以有构造函数 | 不能有构造函数 |
字段 | 可以有任何类型的字段 | 只能有public static final字段 |
继承 | 单继承(一个类只能继承一个抽象类) | 多实现(一个类可以实现多个接口) |
访问修饰符 | 可以有所有访问修饰符 | 方法默认是public |
使用抽象类的情况:
- 在相关类之间共享代码
- 需要使用非public和非static的字段
- 需要定义共同的行为,同时允许子类重写部分方法
- 有默认实现的方法
使用接口的情况:
- 不相关的类需要实现共同的方法
- 需要指定行为但不关心具体实现
- 需要"多重继承"能力
- 定义一个角色或能力,不关心谁实现它
8. 总结
在本文中,我们详细探讨了Java面向对象编程的三大核心特性:封装、继承和多态。
封装使我们能够隐藏实现细节,只暴露必要的接口,从而提高代码的安全性和灵活性。通过使用私有字段和公共访问方法,我们可以控制对象数据的访问,并确保对象始终处于有效状态。
继承允许我们建立类的层次结构,实现代码重用。通过继承,子类可以获得父类的非私有成员,同时添加自己的特定功能或重写父类的行为。重要的是要遵循里氏替换原则,确保子类可以无缝替代父类。
多态提供了代码的灵活性和可扩展性。通过运行时多态,同一方法调用可以根据实际对象类型有不同的行为;通过编译时多态(方法重载),同一方法名可以处理不同类型的参数。
这三大特性相互关联、相互支持,共同构成了面向对象编程的基础。良好的面向对象设计应该:
- 适当封装数据和行为
- 谨慎使用继承,遵循"组合优于继承"的原则
- 依赖抽象,而不是具体实现,充分利用多态
理解和掌握这些核心概念,对于编写高质量、可维护的Java代码至关重要。面向对象不仅是一种编程范式,更是一种思考和解决问题的方式,它帮助我们创建更模块化、更灵活、更易于理解的系统。