1. Java面向对象编程的核心概念回顾
在进入第五天的学习之前,让我们先快速回顾一下Java面向对象编程(OOP)的四大核心概念,这些概念构成了Java编程的基石。
1.1 封装(Encapsulation)
封装是将数据(属性)和操作数据的方法(行为)捆绑在一起的机制。在Java中,我们通过类(class)来实现封装。一个良好的封装实践应该:
- 将类的属性声明为private
- 提供public的getter和setter方法来访问和修改属性
- 在方法中加入必要的验证逻辑
java复制public class Student {
private String name; // 私有属性
// public getter方法
public String getName() {
return name;
}
// public setter方法
public void setName(String name) {
if(name != null && !name.isEmpty()) {
this.name = name;
}
}
}
1.2 继承(Inheritance)
继承允许我们基于现有类创建新类,新类继承了父类的属性和方法,并可以添加自己的特性。Java使用extends关键字实现继承。
java复制class Animal {
void eat() {
System.out.println("动物在吃东西");
}
}
class Dog extends Animal {
void bark() {
System.out.println("狗在叫");
}
}
注意:Java不支持多重继承,一个类只能直接继承一个父类,但可以通过接口实现类似多重继承的效果。
1.3 多态(Polymorphism)
多态是指同一个行为具有多个不同表现形式的能力。在Java中主要通过方法重载(overloading)和方法重写(overriding)实现。
java复制class Calculator {
// 方法重载
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
}
class AdvancedCalculator extends Calculator {
// 方法重写
@Override
int add(int a, int b) {
System.out.println("使用高级计算器");
return super.add(a, b);
}
}
1.4 抽象(Abstraction)
抽象是通过隐藏实现细节来展示功能的过程。在Java中可以通过抽象类和接口实现。
java复制// 抽象类
abstract class Shape {
abstract void draw(); // 抽象方法
void display() {
System.out.println("显示形状");
}
}
// 接口
interface Drawable {
void draw(); // 接口方法默认是public abstract
}
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Java面向对象高级特性
2.1 静态成员与实例成员
理解静态(static)成员和实例成员的区别对于掌握Java面向对象编程至关重要。
java复制class Counter {
static int count = 0; // 静态变量
int instanceCount = 0; // 实例变量
Counter() {
count++;
instanceCount++;
}
static void displayCount() { // 静态方法
System.out.println("总计数: " + count);
}
void displayInstanceCount() { // 实例方法
System.out.println("实例计数: " + instanceCount);
}
}
关键区别:
- 静态成员属于类,所有实例共享
- 实例成员属于对象,每个对象有自己的副本
- 静态方法只能访问静态成员
- 实例方法可以访问静态成员和实例成员
2.2 final关键字
final关键字在Java中有三种用法:
- final变量:一旦赋值就不能改变
- final方法:不能被子类重写
- final类:不能被继承
java复制final class FinalClass { // final类
final int MAX_VALUE = 100; // final变量
final void display() { // final方法
System.out.println("最大值: " + MAX_VALUE);
}
}
2.3 访问修饰符
Java提供了四种访问级别修饰符:
| 修饰符 | 类内 | 包内 | 子类 | 任意位置 |
|---|---|---|---|---|
| private | ✓ | ✗ | ✗ | ✗ |
| (default) | ✓ | ✓ | ✗ | ✗ |
| protected | ✓ | ✓ | ✓ | ✗ |
| public | ✓ | ✓ | ✓ | ✓ |
提示:良好的封装实践建议尽可能使用最严格的访问级别。通常属性设为private,方法设为public,只有在需要子类访问时才使用protected。
3. 接口与抽象类
3.1 抽象类详解
抽象类是不能被实例化的类,用于作为其他类的基类。它可以包含:
- 抽象方法(没有实现)
- 具体方法(有实现)
- 构造方法
- 成员变量
java复制abstract class Animal {
private String name;
Animal(String name) {
this.name = name;
}
abstract void makeSound(); // 抽象方法
void sleep() { // 具体方法
System.out.println(name + "正在睡觉");
}
}
class Dog extends Animal {
Dog(String name) {
super(name);
}
@Override
void makeSound() {
System.out.println("汪汪!");
}
}
3.2 接口详解
接口是Java中实现抽象的另一机制。与抽象类相比:
- 接口中的所有方法默认是public abstract
- 接口中的所有变量默认是public static final
- 一个类可以实现多个接口
- Java 8以后,接口可以包含默认方法和静态方法
java复制interface Swimmable {
void swim(); // 抽象方法
default void floatOnWater() { // Java 8默认方法
System.out.println("浮在水面上");
}
static void showAbility() { // Java 8静态方法
System.out.println("这是一个会游泳的生物");
}
}
class Duck implements Swimmable {
@Override
public void swim() {
System.out.println("鸭子在游泳");
}
}
3.3 接口与抽象类的选择
选择使用接口还是抽象类取决于设计需求:
| 考虑因素 | 抽象类 | 接口 |
|---|---|---|
| 多重继承 | 不支持 | 支持 |
| 方法实现 | 可以有 | Java 8前不能有 |
| 状态 | 可以维护 | 不能维护 |
| 使用场景 | 共享代码 | 定义契约 |
经验法则:
- 如果需要定义子类的共同行为,使用抽象类
- 如果需要定义多个类共有的能力,使用接口
- 从Java 8开始,两者区别变得模糊,可以根据团队习惯选择
4. 面向对象设计原则
4.1 SOLID原则
SOLID是面向对象设计的五个基本原则:
- 单一职责原则(SRP):一个类应该只有一个引起它变化的原因
- 开闭原则(OCP):软件实体应该对扩展开放,对修改关闭
- 里氏替换原则(LSP):子类应该能够替换它们的基类而不影响程序正确性
- 接口隔离原则(ISP):客户端不应该被迫依赖它们不使用的接口
- 依赖倒置原则(DIP):高层模块不应该依赖低层模块,两者都应该依赖抽象
4.2 组合优于继承
在面向对象设计中,组合(composition)通常比继承(inheritance)更灵活:
java复制// 使用继承
class Engine {
void start() {
System.out.println("引擎启动");
}
}
class Car extends Engine {
void drive() {
start();
System.out.println("汽车行驶");
}
}
// 使用组合
class BetterCar {
private Engine engine;
BetterCar(Engine engine) {
this.engine = engine;
}
void drive() {
engine.start();
System.out.println("汽车行驶");
}
}
组合的优点:
- 更灵活,可以在运行时改变行为
- 避免继承层次过深
- 遵循单一职责原则
4.3 设计模式简介
设计模式是解决常见软件设计问题的可重用方案。常见的有:
-
创建型模式:处理对象创建机制
- 单例模式(Singleton)
- 工厂模式(Factory)
- 建造者模式(Builder)
-
结构型模式:处理类和对象的组合
- 适配器模式(Adapter)
- 装饰器模式(Decorator)
- 代理模式(Proxy)
-
行为型模式:处理对象间的通信
- 观察者模式(Observer)
- 策略模式(Strategy)
- 命令模式(Command)
java复制// 单例模式示例
class Singleton {
private static Singleton instance;
private Singleton() {} // 私有构造方法
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
5. Java 8+面向对象新特性
5.1 默认方法(Default Methods)
Java 8允许接口包含具有实现的方法,称为默认方法:
java复制interface Vehicle {
void start();
default void stop() {
System.out.println("车辆停止");
}
}
class Car implements Vehicle {
@Override
public void start() {
System.out.println("汽车启动");
}
// 可以选择不实现stop方法
}
5.2 静态方法
Java 8也允许接口包含静态方法:
java复制interface MathOperations {
static int add(int a, int b) {
return a + b;
}
}
// 使用方式
int sum = MathOperations.add(5, 3);
5.3 函数式接口与Lambda表达式
函数式接口是只包含一个抽象方法的接口,可以用Lambda表达式实现:
java复制@FunctionalInterface
interface Greeting {
void sayHello(String name);
}
// 使用Lambda表达式
Greeting greeting = name -> System.out.println("Hello, " + name);
greeting.sayHello("World");
5.4 方法引用
方法引用是Lambda表达式的一种简写形式:
java复制List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Lambda表达式
names.forEach(name -> System.out.println(name));
// 方法引用
names.forEach(System.out::println);
6. 常见问题与最佳实践
6.1 何时使用抽象类 vs 接口
在实际项目中,选择抽象类还是接口常常令人困惑。以下是一些指导原则:
- 当你需要定义子类的共同行为时,使用抽象类
- 当你需要定义多个类共有的能力时,使用接口
- 如果需要维护状态,使用抽象类
- 如果需要多重继承,使用接口
- 从Java 8开始,两者区别变得模糊,可以根据团队习惯选择
6.2 避免过度设计
面向对象设计容易陷入过度设计的陷阱:
- 不要为每个简单的类都创建接口
- 不要过早考虑所有可能的扩展点
- 保持简单,只在必要时引入抽象
- 遵循YAGNI(You Aren't Gonna Need It)原则
6.3 性能考虑
面向对象设计可能带来一些性能开销:
- 大量小对象会增加内存开销
- 深层次的继承层次会影响方法调用性能
- 虚方法调用比静态方法调用慢
- 在性能关键路径上,可能需要权衡设计优雅性和性能
6.4 测试驱动开发(TDD)
面向对象设计与TDD相辅相成:
- 先写测试,定义接口/抽象类的行为
- 实现最小功能使测试通过
- 重构代码,改善设计
- 重复这个过程
这种方法可以产生更清晰、更可测试的设计。
7. 实战练习
7.1 设计一个图形系统
让我们设计一个简单的图形系统来实践面向对象概念:
java复制abstract class Shape {
private String color;
Shape(String color) {
this.color = color;
}
abstract double area();
abstract double perimeter();
String getColor() {
return color;
}
}
class Circle extends Shape {
private double radius;
Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
double area() {
return Math.PI * radius * radius;
}
@Override
double perimeter() {
return 2 * Math.PI * radius;
}
}
class Rectangle extends Shape {
private double length;
private double width;
Rectangle(String color, double length, double width) {
super(color);
this.length = length;
this.width = width;
}
@Override
double area() {
return length * width;
}
@Override
double perimeter() {
return 2 * (length + width);
}
}
7.2 实现一个简单的银行系统
java复制interface Account {
void deposit(double amount);
void withdraw(double amount);
double getBalance();
}
abstract class BankAccount implements Account {
protected double balance;
private String accountNumber;
BankAccount(String accountNumber) {
this.accountNumber = accountNumber;
this.balance = 0;
}
@Override
public void deposit(double amount) {
if(amount > 0) {
balance += amount;
}
}
@Override
public double getBalance() {
return balance;
}
abstract void applyInterest();
}
class SavingsAccount extends BankAccount {
private double interestRate;
SavingsAccount(String accountNumber, double interestRate) {
super(accountNumber);
this.interestRate = interestRate;
}
@Override
public void withdraw(double amount) {
if(amount > 0 && amount <= balance) {
balance -= amount;
}
}
@Override
void applyInterest() {
balance += balance * interestRate;
}
}
class CheckingAccount extends BankAccount {
private double overdraftLimit;
CheckingAccount(String accountNumber, double overdraftLimit) {
super(accountNumber);
this.overdraftLimit = overdraftLimit;
}
@Override
public void withdraw(double amount) {
if(amount > 0 && (balance - amount) >= -overdraftLimit) {
balance -= amount;
}
}
@Override
void applyInterest() {
// 支票账户通常不计算利息
}
}
7.3 使用设计模式改进设计
让我们用工厂模式改进银行账户创建:
java复制interface AccountFactory {
Account createAccount(String accountNumber, double param);
}
class SavingsAccountFactory implements AccountFactory {
@Override
public Account createAccount(String accountNumber, double interestRate) {
return new SavingsAccount(accountNumber, interestRate);
}
}
class CheckingAccountFactory implements AccountFactory {
@Override
public Account createAccount(String accountNumber, double overdraftLimit) {
return new CheckingAccount(accountNumber, overdraftLimit);
}
}
class Bank {
private Map<String, AccountFactory> factories = new HashMap<>();
Bank() {
factories.put("SAVINGS", new SavingsAccountFactory());
factories.put("CHECKING", new CheckingAccountFactory());
}
Account openAccount(String type, String accountNumber, double param) {
AccountFactory factory = factories.get(type.toUpperCase());
if(factory == null) {
throw new IllegalArgumentException("未知账户类型");
}
return factory.createAccount(accountNumber, param);
}
}
8. 进阶主题与资源推荐
8.1 Java内存模型与面向对象
理解Java内存模型有助于编写更好的面向对象代码:
- 堆内存存储对象实例
- 栈内存存储局部变量和方法调用
- 方法区存储类信息、常量、静态变量
- 对象引用与对象本身的关系
- 垃圾回收机制如何影响对象生命周期
8.2 反射与面向对象
Java反射API允许在运行时检查类、接口、字段和方法:
java复制Class<?> clazz = Class.forName("java.lang.String");
Method[] methods = clazz.getDeclaredMethods();
for(Method method : methods) {
System.out.println(method.getName());
}
反射的强大功能:
- 动态创建对象
- 动态调用方法
- 检查注解信息
- 实现通用框架
8.3 序列化与面向对象
Java对象序列化机制允许将对象转换为字节流:
java复制class Person implements Serializable {
private String name;
private transient int age; // 不会被序列化
// 构造方法、getter、setter等
}
// 序列化
Person person = new Person("Alice", 25);
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
oos.writeObject(person);
}
// 反序列化
try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
Person deserialized = (Person) ois.readObject();
}
8.4 推荐学习资源
-
书籍:
- 《Effective Java》Joshua Bloch
- 《Head First设计模式》Eric Freeman
- 《Java编程思想》Bruce Eckel
-
在线课程:
- Coursera: Object Oriented Programming in Java
- Udemy: Java Programming Masterclass
-
实践平台:
- LeetCode Java OOP相关问题
- CodingBat Java练习
-
开源项目:
- Apache Commons
- Spring Framework
- Guava
面向对象编程是Java的核心,掌握这些概念和技巧将帮助你编写更清晰、更可维护的代码。记住,好的面向对象设计不是一蹴而就的,需要通过不断实践和反思来提升。
