第三章:面向对象编程
在前两章中,我们学习了Java的基础知识和核心语法。现在,我们将深入探讨Java最重要的特性之一:面向对象编程(Object-Oriented Programming,简称OOP)。面向对象编程是一种编程范式,它使用"对象"这一概念来组织和构建程序。本章将通过生活中的例子,帮助你理解面向对象编程的核心概念。
1. 类与对象
1.1 类与对象的基本概念
在面向对象编程中,"类"和"对象"是两个最基本的概念:
- 类(Class):类是对象的模板或蓝图,定义了对象的属性(数据)和行为(方法)。
- 对象(Object):对象是类的实例,是类的具体表现。
想象一下,如果"汽车"是一个类,那么你的丰田卡罗拉和你朋友的特斯拉Model 3就是这个类的两个不同对象。它们都有品牌、型号、颜色等属性,也都能启动、停止、加速等行为,但具体的属性值和行为表现可能不同。
基本语法
// 定义类
public class 类名 {
// 属性(成员变量)
数据类型 属性名1;
数据类型 属性名2;
// 更多属性...
// 方法
返回类型 方法名1(参数列表) {
// 方法体
}
返回类型 方法名2(参数列表) {
// 方法体
}
// 更多方法...
}
// 创建对象
类名 对象名 = new 类名();
生活例子:汽车类
public class Car {
// 属性
String brand; // 品牌
String model; // 型号
String color; // 颜色
int year; // 年份
double mileage; // 里程数
boolean isRunning; // 是否在运行
// 方法
void start() {
if (!isRunning) {
isRunning = true;
System.out.println(brand + " " + model + " 启动了。引擎声:轰轰轰...");
} else {
System.out.println("车已经在运行中!");
}
}
void stop() {
if (isRunning) {
isRunning = false;
System.out.println(brand + " " + model + " 停止了。");
} else {
System.out.println("车已经停止了!");
}
}
void accelerate() {
if (isRunning) {
System.out.println(brand + " " + model + " 加速中...");
} else {
System.out.println("请先启动车辆!");
}
}
void displayInfo() {
System.out.println("汽车信息:");
System.out.println("- 品牌:" + brand);
System.out.println("- 型号:" + model);
System.out.println("- 颜色:" + color);
System.out.println("- 年份:" + year);
System.out.println("- 里程数:" + mileage + "公里");
System.out.println("- 运行状态:" + (isRunning ? "运行中" : "已停止"));
}
}
使用汽车类创建对象
public class CarDemo {
public static void main(String[] args) {
// 创建第一个汽车对象
Car myCar = new Car();
myCar.brand = "丰田";
myCar.model = "卡罗拉";
myCar.color = "银色";
myCar.year = 2020;
myCar.mileage = 15000.5;
myCar.isRunning = false;
// 创建第二个汽车对象
Car friendsCar = new Car();
friendsCar.brand = "特斯拉";
friendsCar.model = "Model 3";
friendsCar.color = "红色";
friendsCar.year = 2022;
friendsCar.mileage = 5000.0;
friendsCar.isRunning = false;
// 使用汽车对象
System.out.println("===== 我的车 =====");
myCar.displayInfo();
myCar.start();
myCar.accelerate();
myCar.stop();
System.out.println("\n===== 朋友的车 =====");
friendsCar.displayInfo();
friendsCar.accelerate(); // 尝试在未启动的情况下加速
friendsCar.start();
friendsCar.accelerate();
}
}
1.2 构造方法
构造方法是一种特殊的方法,用于初始化对象。当使用new
关键字创建对象时,构造方法会被自动调用。
基本语法
public class 类名 {
// 无参构造方法
public 类名() {
// 初始化代码
}
// 带参数的构造方法
public 类名(参数列表) {
// 初始化代码
}
}
生活例子:改进的汽车类
public class Car {
// 属性
String brand;
String model;
String color;
int year;
double mileage;
boolean isRunning;
// 无参构造方法
public Car() {
System.out.println("创建了一个新的汽车对象!");
// 设置默认值
brand = "未知品牌";
model = "未知型号";
color = "白色";
year = 2023;
mileage = 0.0;
isRunning = false;
}
// 带参数的构造方法
public Car(String brand, String model, String color, int year) {
this.brand = brand;
this.model = model;
this.color = color;
this.year = year;
this.mileage = 0.0;
this.isRunning = false;
System.out.println("创建了一个" + color + "的" + brand + " " + model + "!");
}
// 方法(与前面相同)
void start() {
if (!isRunning) {
isRunning = true;
System.out.println(brand + " " + model + " 启动了。引擎声:轰轰轰...");
} else {
System.out.println("车已经在运行中!");
}
}
void stop() {
if (isRunning) {
isRunning = false;
System.out.println(brand + " " + model + " 停止了。");
} else {
System.out.println("车已经停止了!");
}
}
void accelerate() {
if (isRunning) {
System.out.println(brand + " " + model + " 加速中...");
} else {
System.out.println("请先启动车辆!");
}
}
void displayInfo() {
System.out.println("汽车信息:");
System.out.println("- 品牌:" + brand);
System.out.println("- 型号:" + model);
System.out.println("- 颜色:" + color);
System.out.println("- 年份:" + year);
System.out.println("- 里程数:" + mileage + "公里");
System.out.println("- 运行状态:" + (isRunning ? "运行中" : "已停止"));
}
}
使用构造方法创建对象
public class CarDemoWithConstructor {
public static void main(String[] args) {
// 使用无参构造方法创建对象
Car defaultCar = new Car();
System.out.println("默认汽车信息:");
defaultCar.displayInfo();
System.out.println("\n-----------------------\n");
// 使用带参数的构造方法创建对象
Car myCar = new Car("丰田", "卡罗拉", "银色", 2020);
myCar.mileage = 15000.5; // 设置里程数
System.out.println("我的汽车信息:");
myCar.displayInfo();
System.out.println("\n-----------------------\n");
Car friendsCar = new Car("特斯拉", "Model 3", "红色", 2022);
System.out.println("朋友的汽车信息:");
friendsCar.displayInfo();
}
}
1.3 this关键字
this
关键字指代当前对象,常用于区分局部变量和成员变量,特别是当它们同名时。
在上面的带参数构造方法中,我们已经使用了this
关键字:
public Car(String brand, String model, String color, int year) {
this.brand = brand; // this.brand是成员变量,brand是参数
this.model = model; // this.model是成员变量,model是参数
this.color = color; // this.color是成员变量,color是参数
this.year = year; // this.year是成员变量,year是参数
this.mileage = 0.0;
this.isRunning = false;
}
2. 封装
封装是面向对象编程的四大特性之一,它指的是将数据(属性)和操作数据的方法捆绑在一起,对外部隐藏实现细节,只暴露必要的接口。
2.1 访问修饰符
Java提供了四种访问修饰符,用于控制类、变量、方法和构造方法的访问权限:
- public:公共的,可以被任何其他类访问
- protected:受保护的,可以被同一包内的类和所有子类访问
- 默认(无修饰符):包私有的,只能被同一包内的类访问
- private:私有的,只能在定义它的类内部访问
2.2 封装的实现
封装的典型实现方式是:
- 将类的属性设为私有(private)
- 提供公共的getter和setter方法来访问和修改这些属性
生活例子:银行账户
public class BankAccount {
// 私有属性
private String accountNumber;
private String accountHolder;
private double balance;
private String password;
// 构造方法
public BankAccount(String accountNumber, String accountHolder, String password) {
this.accountNumber = accountNumber;
this.accountHolder = accountHolder;
this.balance = 0.0;
this.password = password;
}
// Getter方法
public String getAccountNumber() {
return accountNumber;
}
public String getAccountHolder() {
return accountHolder;
}
public double getBalance() {
return balance;
}
// 注意:没有提供getPassword方法,因为密码是敏感信息
// Setter方法
public void setAccountHolder(String accountHolder) {
this.accountHolder = accountHolder;
}
// 注意:没有提供setBalance方法,余额只能通过存款和取款方法修改
// 业务方法
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println("存款成功!存入:" + amount + "元,当前余额:" + balance + "元");
} else {
System.out.println("存款金额必须大于0!");
}
}
public boolean withdraw(double amount, String inputPassword) {
// 验证密码
if (!password.equals(inputPassword)) {
System.out.println("密码错误,取款失败!");
return false;
}
// 验证余额
if (amount <= 0) {
System.out.println("取款金额必须大于0!");
return false;
}
if (amount > balance) {
System.out.println("余额不足,取款失败!当前余额:" + balance + "元");
return false;
}
// 执行取款
balance -= amount;
System.out.println("取款成功!取出:" + amount + "元,当前余额:" + balance + "元");
return true;
}
public void displayInfo() {
System.out.println("账户信息:");
System.out.println("- 账号:" + accountNumber);
System.out.println("- 户主:" + accountHolder);
System.out.println("- 余额:" + balance + "元");
}
}
使用银行账户类
public class BankDemo {
public static void main(String[] args) {
// 创建银行账户
BankAccount account = new BankAccount("6225 8888 8888 8888", "张三", "123456");
// 显示账户信息
account.displayInfo();
// 存款
account.deposit(1000);
account.deposit(-100); // 尝试存入负数
// 取款
account.withdraw(500, "123456"); // 密码正确
account.withdraw(600, "654321"); // 密码错误
account.withdraw(1000, "123456"); // 余额不足
// 尝试直接访问私有属性(这会导致编译错误)
// System.out.println(account.balance); // 错误:balance是私有的
// account.balance = 1000000; // 错误:balance是私有的
// 使用公共方法访问私有属性
System.out.println("当前余额:" + account.getBalance() + "元");
// 修改账户持有人
account.setAccountHolder("张三丰");
account.displayInfo();
}
}
2.3 封装的好处
- 数据隐藏:防止外部直接访问和修改对象的内部状态
- 数据验证:可以在setter方法中添加验证逻辑,确保数据的有效性
- 灵活性:可以改变内部实现而不影响外部代码
- 安全性:可以控制对敏感数据的访问
3. 继承
继承是面向对象编程的另一个重要特性,它允许一个类(子类)获取另一个类(父类)的属性和方法。
3.1 继承的基本概念
基本语法
// 父类
public class 父类名 {
// 属性和方法
}
// 子类
public class 子类名 extends 父类名 {
// 子类特有的属性和方法
}
生活例子:交通工具继承体系
// 父类:交通工具
public class Vehicle {
// 共有属性
protected String brand;
protected String model;
protected int year;
protected double speed;
protected boolean isMoving;
// 构造方法
public Vehicle(String brand, String model, int year) {
this.brand = brand;
this.model = model;
this.year = year;
this.speed = 0.0;
this.isMoving = false;
}
// 共有方法
public void start() {
if (!isMoving) {
isMoving = true;
System.out.println(brand + " " + model + " 启动了。");
} else {
System.out.println("已经在运行中!");
}
}
public void stop() {
if (isMoving) {
isMoving = false;
speed = 0.0;
System.out.println(brand + " " + model + " 停止了。");
} else {
System.out.println("已经停止了!");
}
}
public void accelerate(double amount) {
if (isMoving) {
speed += amount;
System.out.println(brand + " " + model + " 加速到 " + speed + " km/h。");
} else {
System.out.println("请先启动!");
}
}
public void displayInfo() {
System.out.println("交通工具信息:");
System.out.println("- 品牌:" + brand);
System.out.println("- 型号:" + model);
System.out.println("- 年份:" + year);
System.out.println("- 当前速度:" + speed + " km/h");
System.out.println("- 运行状态:" + (isMoving ? "运行中" : "已停止"));
}
}
// 子类:汽车
public class Car extends Vehicle {
// 特有属性
private int numberOfDoors;
private boolean airConditionerOn;
// 构造方法
public Car(String brand, String model, int year, int numberOfDoors) {
super(brand, model, year); // 调用父类构造方法
this.numberOfDoors = numberOfDoors;
this.airConditionerOn = false;
}
// 特有方法
public void turnOnAirConditioner() {
if (!airConditionerOn) {
airConditionerOn = true;
System.out.println("空调已开启。");
} else {
System.out.println("空调已经开启了!");
}
}
public void turnOffAirConditioner() {
if (airConditionerOn) {
airConditionerOn = false;
System.out.println("空调已关闭。");
} else {
System.out.println("空调已经关闭了!");
}
}
// 重写父类方法
@Override
public void displayInfo() {
System.out.println("汽车信息:");
System.out.println("- 品牌:" + brand);
System.out.println("- 型号:" + model);
System.out.println("- 年份:" + year);
System.out.println("- 门数:" + numberOfDoors);
System.out.println("- 当前速度:" + speed + " km/h");
System.out.println("- 运行状态:" + (isMoving ? "运行中" : "已停止"));
System.out.println("- 空调状态:" + (airConditionerOn ? "开启" : "关闭"));
}
}
// 子类:自行车
public class Bicycle extends Vehicle {
// 特有属性
private int numberOfGears;
private boolean hasBasket;
// 构造方法
public Bicycle(String brand, String model, int year, int numberOfGears, boolean hasBasket) {
super(brand, model, year); // 调用父类构造方法
this.numberOfGears = numberOfGears;
this.hasBasket = hasBasket;
}
// 特有方法
public void ringBell() {
System.out.println("叮铃铃!自行车铃声响起。");
}
// 重写父类方法
@Override
public void start() {
if (!isMoving) {
isMoving = true;
System.out.println(brand + " " + model + " 自行车开始骑行。");
} else {
System.out.println("已经在骑行中!");
}
}
@Override
public void accelerate(double amount) {
if (isMoving) {
// 自行车速度增加较慢,所以只增加一半的量
speed += amount / 2;
System.out.println(brand + " " + model + " 自行车加速到 " + speed + " km/h。");
} else {
System.out.println("请先开始骑行!");
}
}
@Override
public void displayInfo() {
System.out.println("自行车信息:");
System.out.println("- 品牌:" + brand);
System.out.println("- 型号:" + model);
System.out.println("- 年份:" + year);
System.out.println("- 档位数:" + numberOfGears);
System.out.println("- 是否有篮子:" + (hasBasket ? "是" : "否"));
System.out.println("- 当前速度:" + speed + " km/h");
System.out.println("- 运行状态:" + (isMoving ? "骑行中" : "已停止"));
}
}
使用继承类
public class VehicleDemo {
public static void main(String[] args) {
// 创建汽车对象
Car myCar = new Car("丰田", "卡罗拉", 2020, 4);
System.out.println("===== 我的汽车 =====");
myCar.displayInfo();
myCar.start();
myCar.accelerate(60);
myCar.turnOnAirConditioner();
myCar.displayInfo();
System.out.println("\n===== 我的自行车 =====");
// 创建自行车对象
Bicycle myBicycle = new Bicycle("捷安特", "ATX 620", 2021, 21, true);
myBicycle.displayInfo();
myBicycle.start();
myBicycle.accelerate(10);
myBicycle.ringBell();
myBicycle.displayInfo();
// 使用父类引用指向子类对象(多态,将在下一节讨论)
System.out.println("\n===== 交通工具数组 =====");
Vehicle[] vehicles = new Vehicle[2];
vehicles[0] = myCar; // 汽车是一种交通工具
vehicles[1] = myBicycle; // 自行车也是一种交通工具
for (int i = 0; i < vehicles.length; i++) {
System.out.println("\n交通工具 #" + (i + 1) + ":");
vehicles[i].displayInfo(); // 调用的是各自重写的displayInfo方法
}
}
}
3.2 super关键字
super
关键字用于引用父类的成员(属性和方法)。在上面的例子中,我们已经使用了super
来调用父类的构造方法:
public Car(String brand, String model, int year, int numberOfDoors) {
super(brand, model, year); // 调用父类构造方法
this.numberOfDoors = numberOfDoors;
this.airConditionerOn = false;
}
super
也可以用来调用父类的方法:
@Override
public void displayInfo() {
super.displayInfo(); // 调用父类的displayInfo方法
System.out.println("- 门数:" + numberOfDoors);
System.out.println("- 空调状态:" + (airConditionerOn ? "开启" : "关闭"));
}
3.3 方法重写(Override)
方法重写是指子类提供一个与父类方法签名(名称、参数列表)相同但实现不同的方法。在上面的例子中,Car
和Bicycle
类都重写了displayInfo()
方法。
重写方法时,可以使用@Override
注解,这不是必须的,但它可以帮助编译器检查是否正确重写了父类方法。
4. 多态
多态是面向对象编程的第三个重要特性,它允许使用父类类型的引用指向子类对象,并且调用方法时会执行子类的实现。
4.1 多态的基本概念
多态有两种主要形式:
- 编译时多态(静态多态):通过方法重载实现
- 运行时多态(动态多态):通过方法重写和继承实现
重载:
参数列表必须不同(数量、类型或顺序)。
返回类型可以不同,但仅返回类型不同不构成重载。
重写:
参数列表和返回类型必须与父类方法相同(或返回类型为父类返回类型的子类型)。
访问修饰符不能比父类更严格(如父类为protected,子类不能为private)
在这里,我们主要讨论运行时多态。
生活例子:动物声音
// 父类:动物
public class Animal {
protected String name;
protected int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void makeSound() {
System.out.println("动物发出声音");
}
public void displayInfo() {
System.out.println("动物名称:" + name);
System.out.println("年龄:" + age + "岁");
}
}
// 子类:狗
public class Dog extends Animal {
private String breed; // 品种
public Dog(String name, int age, String breed) {
super(name, age);
this.breed = breed;
}
@Override
public void makeSound() {
System.out.println(name + "汪汪汪!");
}
@Override
public void displayInfo() {
System.out.println("狗的信息:");
super.displayInfo();
System.out.println("品种:" + breed);
}
// 特有方法
public void fetch() {
System.out.println(name + "在捡球。");
}
}
// 子类:猫
public class Cat extends Animal {
private boolean isIndoor; // 是否室内猫
public Cat(String name, int age, boolean isIndoor) {
super(name, age);
this.isIndoor = isIndoor;
}
@Override
public void makeSound() {
System.out.println(name + "喵喵喵!");
}
@Override
public void displayInfo() {
System.out.println("猫的信息:");
super.displayInfo();
System.out.println("是否室内猫:" + (isIndoor ? "是" : "否"));
}
// 特有方法
public void climb() {
System.out.println(name + "在爬树。");
}
}
// 子类:鸟
public class Bird extends Animal {
private boolean canFly; // 是否会飞
public Bird(String name, int age, boolean canFly) {
super(name, age);
this.canFly = canFly;
}
@Override
public void makeSound() {
System.out.println(name + "叽叽喳喳!");
}
@Override
public void displayInfo() {
System.out.println("鸟的信息:");
super.displayInfo();
System.out.println("是否会飞:" + (canFly ? "是" : "否"));
}
// 特有方法
public void fly() {
if (canFly) {
System.out.println(name + "在飞翔。");
} else {
System.out.println(name + "不会飞。");
}
}
}
// 使用多态
public class AnimalDemo {
public static void main(String[] args) {
// 创建动物对象
Animal dog = new Dog("旺财", 3, "金毛");
Animal cat = new Cat("咪咪", 2, true);
Animal bird = new Bird("小黄", 1, true);
// 创建动物数组
Animal[] animals = {dog, cat, bird};
// 使用多态调用方法
for (Animal animal : animals) {
System.out.println("\n-----------------------");
animal.displayInfo();
animal.makeSound();
// 特有方法需要类型转换
if (animal instanceof Dog) {
((Dog) animal).fetch();
} else if (animal instanceof Cat) {
((Cat) animal).climb();
} else if (animal instanceof Bird) {
((Bird) animal).fly();
}
}
}
}
4.2 多态的好处
- 代码灵活性:可以使用父类类型的变量引用不同的子类对象,使代码更加灵活
- 可扩展性:可以方便地添加新的子类,而不需要修改使用父类引用的代码
- 接口统一:可以统一处理不同类型的对象,简化代码
4.3 向上转型与向下转型
-
向上转型(Upcasting):将子类引用赋值给父类变量,这是自动的
Animal dog = new Dog("旺财", 3, "金毛"); // 向上转型
-
向下转型(Downcasting):将父类引用转换为子类类型,这需要显式转换,并且在运行时可能会失败
Animal animal = new Dog("旺财", 3, "金毛"); Dog dog = (Dog) animal; // 向下转型,正确 Animal anotherAnimal = new Cat("咪咪", 2, true); Dog anotherDog = (Dog) anotherAnimal; // 运行时会抛出ClassCastException
为了避免向下转型失败,可以使用instanceof
运算符进行类型检查:
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 安全的向下转型
dog.fetch();
}
5. 抽象类
抽象类是一种不能被实例化的类,它可以包含抽象方法(没有实现的方法)和具体方法(有实现的方法)。抽象类主要用于定义子类的通用特性和行为。
5.1 抽象类的基本概念
基本语法
// 抽象类
public abstract class 抽象类名 {
// 属性
// 构造方法
// 抽象方法(没有方法体)
public abstract 返回类型 方法名(参数列表);
// 具体方法(有方法体)
public 返回类型 方法名(参数列表) {
// 方法体
}
}
生活例子:交通工具抽象类
// 抽象类:交通工具
public abstract class Vehicle {
// 属性
protected String brand;
protected String model;
protected double speed;
// 构造方法
public Vehicle(String brand, String model) {
this.brand = brand;
this.model = model;
this.speed = 0.0;
}
// 抽象方法(子类必须实现)
public abstract void start();
public abstract void stop();
// 具体方法(子类可以继承或重写)
public void accelerate(double amount) {
speed += amount;
System.out.println(brand + " " + model + " 加速到 " + speed + " km/h。");
}
public void brake(double amount) {
if (speed >= amount) {
speed -= amount;
} else {
speed = 0;
}
System.out.println(brand + " " + model + " 减速到 " + speed + " km/h。");
}
public void displayInfo() {
System.out.println("交通工具信息:");
System.out.println("- 品牌:" + brand);
System.out.println("- 型号:" + model);
System.out.println("- 当前速度:" + speed + " km/h");
}
}
// 具体子类:汽车
public class Car extends Vehicle {
private boolean engineRunning;
public Car(String brand, String model) {
super(brand, model);
this.engineRunning = false;
}
// 实现抽象方法
@Override
public void start() {
engineRunning = true;
System.out.println(brand + " " + model + " 汽车启动,引擎运转中。");
}
@Override
public void stop() {
engineRunning = false;
speed = 0;
System.out.println(brand + " " + model + " 汽车停止,引擎已关闭。");
}
// 特有方法
public void honk() {
System.out.println(brand + " " + model + " 汽车鸣笛:嘟嘟!");
}
}
// 具体子类:自行车
public class Bicycle extends Vehicle {
private boolean isMoving;
public Bicycle(String brand, String model) {
super(brand, model);
this.isMoving = false;
}
// 实现抽象方法
@Override
public void start() {
isMoving = true;
System.out.println(brand + " " + model + " 自行车开始骑行。");
}
@Override
public void stop() {
isMoving = false;
speed = 0;
System.out.println(brand + " " + model + " 自行车停止骑行。");
}
// 重写具体方法
@Override
public void accelerate(double amount) {
// 自行车加速较慢
super.accelerate(amount / 2);
}
// 特有方法
public void ringBell() {
System.out.println(brand + " " + model + " 自行车铃声:叮铃铃!");
}
}
// 使用抽象类
public class VehicleDemo {
public static void main(String[] args) {
// Vehicle vehicle = new Vehicle("未知", "未知"); // 错误:抽象类不能被实例化
Vehicle car = new Car("丰田", "卡罗拉");
Vehicle bicycle = new Bicycle("捷安特", "ATX 620");
System.out.println("===== 汽车 =====");
car.start();
car.accelerate(50);
car.brake(20);
car.displayInfo();
((Car) car).honk(); // 需要向下转型才能调用特有方法
car.stop();
System.out.println("\n===== 自行车 =====");
bicycle.start();
bicycle.accelerate(20); // 自行车加速较慢,实际只增加10
bicycle.displayInfo();
((Bicycle) bicycle).ringBell(); // 需要向下转型才能调用特有方法
bicycle.stop();
}
}
5.2 抽象类的特点
- 不能被实例化:抽象类不能直接创建对象
- 可以包含抽象方法:抽象方法没有方法体,只有声明
- 子类必须实现所有抽象方法:除非子类也是抽象类
- 可以包含具体方法:有完整实现的方法
- 可以包含构造方法:虽然不能直接实例化,但构造方法可以被子类调用
- 可以包含成员变量:可以是任何访问修饰符
5.3 抽象类的应用场景
- 定义通用特性:当多个类共享某些特性和行为,但具体实现不同时
- 强制子类实现某些方法:通过抽象方法确保子类提供特定功能
- 提供部分实现:通过具体方法提供通用实现,减少代码重复
- 作为框架的基础:许多框架使用抽象类定义基本组件
6. 接口
接口是一种完全抽象的类型,它只定义方法的签名,不提供实现。接口主要用于定义对象的行为规范,而不关心对象的具体类型。
6.1 接口的基本概念
接口可以看作是一种特殊的抽象类,它比抽象类的抽象程度更高。在现实生活中,接口就像是设备之间的连接标准,例如USB接口、HDMI接口等,它们定义了设备之间如何交互,但不关心设备的具体实现。
基本语法
// 接口
public interface 接口名 {
// 常量(默认是public static final)
数据类型 常量名 = 值;
// 抽象方法(默认是public abstract)
返回类型 方法名(参数列表);
// 默认方法(Java 8及以上)
default 返回类型 方法名(参数列表) {
// 方法体
}
// 静态方法(Java 8及以上)
static 返回类型 方法名(参数列表) {
// 方法体
}
// 私有方法(Java 9及以上)
private 返回类型 方法名(参数列表) {
// 方法体
}
}
生活例子:遥控器接口
// 接口:遥控器
public interface RemoteControl {
// 常量
int MAX_VOLUME = 100;
int MIN_VOLUME = 0;
// 抽象方法
void powerOn();
void powerOff();
void setVolume(int volume);
void channelUp();
void channelDown();
// 默认方法
default void mute() {
System.out.println("将音量设置为0(静音)");
setVolume(MIN_VOLUME);
}
// 静态方法
static void printInstructions() {
System.out.println("遥控器使用说明:");
System.out.println("- 按电源键开关设备");
System.out.println("- 使用音量键调节音量");
System.out.println("- 使用频道键切换频道");
}
}
// 实现接口:电视遥控器
public class TVRemote implements RemoteControl {
private boolean isPoweredOn;
private int volume;
private int channel;
public TVRemote() {
this.isPoweredOn = false;
this.volume = 30; // 默认音量
this.channel = 1; // 默认频道
}
@Override
public void powerOn() {
isPoweredOn = true;
System.out.println("电视已开机");
}
@Override
public void powerOff() {
isPoweredOn = false;
System.out.println("电视已关机");
}
@Override
public void setVolume(int volume) {
if (!isPoweredOn) {
System.out.println("电视未开机,无法调节音量");
return;
}
if (volume > MAX_VOLUME) {
this.volume = MAX_VOLUME;
} else if (volume < MIN_VOLUME) {
this.volume = MIN_VOLUME;
} else {
this.volume = volume;
}
System.out.println("电视音量设置为:" + this.volume);
}
@Override
public void channelUp() {
if (!isPoweredOn) {
System.out.println("电视未开机,无法切换频道");
return;
}
channel++;
System.out.println("电视频道切换为:" + channel);
}
@Override
public void channelDown() {
if (!isPoweredOn) {
System.out.println("电视未开机,无法切换频道");
return;
}
if (channel > 1) {
channel--;
}
System.out.println("电视频道切换为:" + channel);
}
// 特有方法
public void showInfo() {
System.out.println("电视状态:" + (isPoweredOn ? "开机" : "关机"));
if (isPoweredOn) {
System.out.println("当前频道:" + channel);
System.out.println("当前音量:" + volume);
}
}
}
// 实现接口:音响遥控器
public class StereoRemote implements RemoteControl {
private boolean isPoweredOn;
private int volume;
private String mode; // FM, AM, AUX, Bluetooth等
public StereoRemote() {
this.isPoweredOn = false;
this.volume = 20; // 默认音量
this.mode = "FM"; // 默认模式
}
@Override
public void powerOn() {
isPoweredOn = true;
System.out.println("音响已开机");
}
@Override
public void powerOff() {
isPoweredOn = false;
System.out.println("音响已关机");
}
@Override
public void setVolume(int volume) {
if (!isPoweredOn) {
System.out.println("音响未开机,无法调节音量");
return;
}
if (volume > MAX_VOLUME) {
this.volume = MAX_VOLUME;
} else if (volume < MIN_VOLUME) {
this.volume = MIN_VOLUME;
} else {
this.volume = volume;
}
System.out.println("音响音量设置为:" + this.volume);
}
@Override
public void channelUp() {
if (!isPoweredOn) {
System.out.println("音响未开机,无法切换频道");
return;
}
System.out.println("音响频率上调");
}
@Override
public void channelDown() {
if (!isPoweredOn) {
System.out.println("音响未开机,无法切换频道");
return;
}
System.out.println("音响频率下调");
}
// 重写默认方法
@Override
public void mute() {
if (!isPoweredOn) {
System.out.println("音响未开机,无法静音");
return;
}
System.out.println("音响已静音");
setVolume(MIN_VOLUME);
}
// 特有方法
public void changeMode(String mode) {
if (!isPoweredOn) {
System.out.println("音响未开机,无法切换模式");
return;
}
this.mode = mode;
System.out.println("音响模式切换为:" + mode);
}
public void showInfo() {
System.out.println("音响状态:" + (isPoweredOn ? "开机" : "关机"));
if (isPoweredOn) {
System.out.println("当前模式:" + mode);
System.out.println("当前音量:" + volume);
}
}
}
// 使用接口
public class RemoteControlDemo {
public static void main(String[] args) {
// 打印使用说明
RemoteControl.printInstructions();
System.out.println("\n===== 电视遥控器 =====");
TVRemote tvRemote = new TVRemote();
tvRemote.powerOn();
tvRemote.setVolume(50);
tvRemote.channelUp();
tvRemote.channelUp();
tvRemote.showInfo();
tvRemote.mute(); // 使用默认方法
tvRemote.showInfo();
tvRemote.powerOff();
System.out.println("\n===== 音响遥控器 =====");
StereoRemote stereoRemote = new StereoRemote();
stereoRemote.powerOn();
stereoRemote.setVolume(70);
stereoRemote.changeMode("Bluetooth");
stereoRemote.showInfo();
stereoRemote.mute();
stereoRemote.showInfo();
stereoRemote.powerOff();
// 使用接口类型引用
System.out.println("\n===== 使用接口类型引用 =====");
RemoteControl remote = new TVRemote(); // 接口引用指向实现类对象
remote.powerOn();
remote.setVolume(40);
remote.powerOff();
}
}
6.2 接口的特点
- 只能包含抽象方法、默认方法、静态方法和私有方法:接口中的方法默认是
public abstract
的 - 只能包含常量:接口中的变量默认是
public static final
的 - 不能被实例化:接口不能直接创建对象
- 类可以实现多个接口:这是Java实现多重继承的方式
- 接口可以继承其他接口:使用
extends
关键字
6.3 接口与抽象类的区别
特性 | 接口 | 抽象类 |
---|---|---|
方法 | 只能有抽象方法、默认方法、静态方法和私有方法 | 可以有抽象方法和具体方法 |
变量 | 只能有常量(public static final) | 可以有任何类型的变量 |
构造方法 | 不能有构造方法 | 可以有构造方法 |
继承 | 类可以实现多个接口 | 类只能继承一个抽象类 |
关键字 | implements | extends |
设计目的 | 定义行为规范 | 提供基础实现 |
6.4 接口的应用场景
- 定义通用行为:当多个不相关的类需要实现相同的行为时
- 实现多重继承:Java不支持类的多重继承,但可以通过接口实现
- 解耦合:接口可以降低代码的耦合度,提高灵活性
- 回调机制:通过接口实现回调功能
6.5 生活中的接口例子
充电接口
在现实生活中,充电接口就是一个很好的例子。不同的设备(手机、平板、笔记本电脑等)可能使用不同的充电器,但它们都需要实现相同的充电接口标准(如USB-C、Lightning等)。
// 充电接口
public interface ChargingPort {
void connect();
void disconnect();
void charge();
int getBatteryLevel();
}
// USB-C接口实现
public class USBCPort implements ChargingPort {
private boolean isConnected;
private int batteryLevel;
public USBCPort() {
this.isConnected = false;
this.batteryLevel = 20; // 初始电量
}
@Override
public void connect() {
isConnected = true;
System.out.println("USB-C接口已连接");
}
@Override
public void disconnect() {
isConnected = false;
System.out.println("USB-C接口已断开");
}
@Override
public void charge() {
if (isConnected) {
batteryLevel += 10;
if (batteryLevel > 100) {
batteryLevel = 100;
}
System.out.println("通过USB-C接口充电中,当前电量:" + batteryLevel + "%");
} else {
System.out.println("请先连接充电器");
}
}
@Override
public int getBatteryLevel() {
return batteryLevel;
}
}
// Lightning接口实现
public class LightningPort implements ChargingPort {
private boolean isConnected;
private int batteryLevel;
public LightningPort() {
this.isConnected = false;
this.batteryLevel = 30; // 初始电量
}
@Override
public void connect() {
isConnected = true;
System.out.println("Lightning接口已连接");
}
@Override
public void disconnect() {
isConnected = false;
System.out.println("Lightning接口已断开");
}
@Override
public void charge() {
if (isConnected) {
batteryLevel += 8;
if (batteryLevel > 100) {
batteryLevel = 100;
}
System.out.println("通过Lightning接口充电中,当前电量:" + batteryLevel + "%");
} else {
System.out.println("请先连接充电器");
}
}
@Override
public int getBatteryLevel() {
return batteryLevel;
}
}
// 使用充电接口
public class ChargingDemo {
public static void main(String[] args) {
System.out.println("===== 安卓手机(USB-C接口)=====");
ChargingPort androidPhone = new USBCPort();
System.out.println("初始电量:" + androidPhone.getBatteryLevel() + "%");
androidPhone.charge(); // 尝试在未连接时充电
androidPhone.connect();
for (int i = 0; i < 5; i++) {
androidPhone.charge();
}
androidPhone.disconnect();
System.out.println("\n===== 苹果手机(Lightning接口)=====");
ChargingPort iPhone = new LightningPort();
System.out.println("初始电量:" + iPhone.getBatteryLevel() + "%");
iPhone.connect();
for (int i = 0; i < 5; i++) {
iPhone.charge();
}
iPhone.disconnect();
}
}
7. 内部类
内部类是定义在另一个类内部的类。Java支持多种类型的内部类,每种类型都有其特定的用途和特点。
7.1 内部类的基本概念
内部类可以看作是外部类的一个成员,它可以访问外部类的所有成员(包括私有成员),同时也可以对外隐藏。在现实生活中,内部类就像是一个组件或零件,它是某个大型设备的一部分,但也有自己的功能和特性。
7.2 内部类的类型
7.2.1 成员内部类
成员内部类是最常见的内部类,它作为外部类的一个成员存在,可以访问外部类的所有成员。
// 外部类:汽车
public class Car {
private String brand;
private String model;
private Engine engine; // 引用内部类
public Car(String brand, String model, int horsePower) {
this.brand = brand;
this.model = model;
this.engine = new Engine(horsePower);
}
public void start() {
System.out.println(brand + " " + model + " 启动中...");
engine.start();
}
public void stop() {
System.out.println(brand + " " + model + " 停止中...");
engine.stop();
}
public void showInfo() {
System.out.println("汽车信息:");
System.out.println("- 品牌:" + brand);
System.out.println("- 型号:" + model);
System.out.println("- 引擎马力:" + engine.horsePower + "HP");
System.out.println("- 引擎状态:" + (engine.isRunning ? "运行中" : "已停止"));
}
// 成员内部类:引擎
private class Engine {
private int horsePower;
private boolean isRunning;
public Engine(int horsePower) {
this.horsePower = horsePower;
this.isRunning = false;
}
public void start() {
isRunning = true;
System.out.println("引擎启动,马力:" + horsePower + "HP");
}
public void stop() {
isRunning = false;
System.out.println("引擎停止");
}
}
}
// 使用带有成员内部类的外部类
public class CarDemo {
public static void main(String[] args) {
Car myCar = new Car("丰田", "卡罗拉", 140);
myCar.showInfo();
myCar.start();
myCar.showInfo();
myCar.stop();
myCar.showInfo();
// 注意:不能直接创建内部类的实例
// Car.Engine engine = new Car.Engine(100); // 错误:Engine是私有的
// 即使Engine不是私有的,也需要通过外部类实例创建
// Car.Engine engine = myCar.new Engine(100);
}
}
7.2.2 静态内部类
静态内部类是使用static
关键字修饰的内部类,它不依赖于外部类的实例,但可以访问外部类的静态成员。
// 外部类:数学工具
public class MathUtils {
// 静态变量
private static final double PI = 3.14159;
// 静态方法
public static double calculateCircleArea(double radius) {
return PI * radius * radius;
}
// 静态内部类:几何计算器
public static class GeometryCalculator {
// 计算矩形面积
public double calculateRectangleArea(double length, double width) {
return length * width;
}
// 计算三角形面积
public double calculateTriangleArea(double base, double height) {
return 0.5 * base * height;
}
// 使用外部类的静态方法
public double calculateCircleArea(double radius) {
return MathUtils.calculateCircleArea(radius);
}
}
}
// 使用静态内部类
public class MathDemo {
public static void main(String[] args) {
// 直接创建静态内部类的实例,不需要外部类的实例
MathUtils.GeometryCalculator calculator = new MathUtils.GeometryCalculator();
// 使用静态内部类的方法
double rectangleArea = calculator.calculateRectangleArea(5, 10);
double triangleArea = calculator.calculateTriangleArea(4, 6);
double circleArea = calculator.calculateCircleArea(3);
System.out.println("矩形面积:" + rectangleArea);
System.out.println("三角形面积:" + triangleArea);
System.out.println("圆形面积:" + circleArea);
// 直接使用外部类的静态方法
double anotherCircleArea = MathUtils.calculateCircleArea(5);
System.out.println("另一个圆形面积:" + anotherCircleArea);
}
}
7.2.3 局部内部类
局部内部类是定义在方法或代码块内的类,它只在定义它的方法或代码块内可见。
// 外部类:玩具工厂
public class ToyFactory {
private String factoryName;
public ToyFactory(String factoryName) {
this.factoryName = factoryName;
}
// 方法中定义局部内部类
public void produceToy(String toyType) {
// 局部变量
final String productionDate = java.time.LocalDate.now().toString();
// 局部内部类:玩具
class Toy {
private String type;
private String manufacturer;
public Toy(String type) {
this.type = type;
this.manufacturer = factoryName; // 访问外部类的成员
}
public void showInfo() {
System.out.println("玩具信息:");
System.out.println("- 类型:" + type);
System.out.println("- 制造商:" + manufacturer);
System.out.println("- 生产日期:" + productionDate); // 访问局部变量
}
}
// 在方法内创建局部内部类的实例
Toy toy = new Toy(toyType);
System.out.println(factoryName + " 工厂生产了一个新玩具:");
toy.showInfo();
}
}
// 使用带有局部内部类的类
public class ToyFactoryDemo {
public static void main(String[] args) {
ToyFactory factory = new ToyFactory("快乐玩具厂");
factory.produceToy("泰迪熊");
factory.produceToy("遥控车");
// 注意:不能在方法外部访问局部内部类
// Toy toy = new Toy("积木"); // 错误:Toy类在方法外不可见
}
}
7.2.4 匿名内部类
匿名内部类是没有名字的内部类,它通常用于创建接口或抽象类的实例。
// 接口:按钮监听器
public interface ButtonListener {
void onClick();
void onLongPress();
}
// 外部类:按钮
public class Button {
private String label;
private ButtonListener listener;
public Button(String label) {
this.label = label;
}
public void setLabel(String label) {
this.label = label;
}
public void setListener(ButtonListener listener) {
this.listener = listener;
}
public void click() {
System.out.println("按钮 '" + label + "' 被点击");
if (listener != null) {
listener.onClick();
}
}
public void longPress() {
System.out.println("按钮 '" + label + "' 被长按");
if (listener != null) {
listener.onLongPress();
}
}
}
// 使用匿名内部类
public class ButtonDemo {
public static void main(String[] args) {
// 创建按钮
Button loginButton = new Button("登录");
Button registerButton = new Button("注册");
// 使用匿名内部类设置按钮监听器
loginButton.setListener(new ButtonListener() {
@Override
public void onClick() {
System.out.println("执行登录操作");
}
@Override
public void onLongPress() {
System.out.println("显示登录帮助");
}
});
registerButton.setListener(new ButtonListener() {
@Override
public void onClick() {
System.out.println("执行注册操作");
}
@Override
public void onLongPress() {
System.out.println("显示注册协议");
}
});
// 模拟按钮操作
loginButton.click();
registerButton.longPress();
}
}
7.3 内部类的优点
- 封装性:内部类可以对外部隐藏,提高封装性
- 访问外部类成员:内部类可以访问外部类的所有成员,包括私有成员
- 提高代码可读性:将相关的类放在一起,使代码结构更清晰
- 实现回调:匿名内部类常用于实现回调功能
7.4 内部类的应用场景
- 组件关系:当一个类是另一个类的组件时,可以使用成员内部类
- 辅助功能:当一个类只为另一个类提供服务时,可以使用内部类
- 实现接口:使用匿名内部类快速实现接口
- 事件处理:在GUI编程中,使用匿名内部类处理事件
7.5 生活中的内部类例子
嵌套玩具
在现实生活中,俄罗斯套娃是一个很好的内部类例子。每个套娃都可以包含一个更小的套娃,形成嵌套结构。
// 俄罗斯套娃类
public class RussianDoll {
private String color;
private int size;
private RussianDoll innerDoll; // 内部的套娃
public RussianDoll(String color, int size) {
this.color = color;
this.size = size;
this.innerDoll = null;
}
// 添加内部套娃
public void addInnerDoll(String color, int size) {
if (size >= this.size) {
System.out.println("错误:内部套娃必须比外部套娃小!");
return;
}
if (innerDoll == null) {
innerDoll = new RussianDoll(color, size);
} else {
innerDoll.addInnerDoll(color, size);
}
}
// 显示所有套娃信息
public void displayDolls(String prefix) {
System.out.println(prefix + "套娃 - 颜色:" + color + ",尺寸:" + size);
if (innerDoll != null) {
innerDoll.displayDolls(prefix + " ");
}
}
}
// 使用俄罗斯套娃
public class RussianDollDemo {
public static void main(String[] args) {
RussianDoll outerDoll = new RussianDoll("红色", 10);
outerDoll.addInnerDoll("蓝色", 8);
outerDoll.addInnerDoll("绿色", 6);
outerDoll.addInnerDoll("黄色", 4);
outerDoll.addInnerDoll("紫色", 2);
System.out.println("俄罗斯套娃结构:");
outerDoll.displayDolls("");
}
}
总结
在本章中,我们学习了面向对象编程的核心概念:类与对象、封装、继承、多态、抽象类、接口和内部类。这些概念是Java编程的基础,掌握它们将帮助你设计出更加灵活、可维护的程序。
通过生活中的例子,我们看到了面向对象编程如何模拟现实世界的对象和关系。在接下来的章节中,我们将学习更多Java的高级特性,如异常处理、泛型编程和集合框架等。