第九课:面向对象(补充)
在 第二课 中,我们已经介绍过 类与对象的常用用法,这里对几个重要的内容加以补充
(一)接口
接口是一个抽象类型,包含类要实现的方法的声明(方法签名),而不提供具体实现。
接口可以看作是一个特殊的抽象类,无法直接实例化。
和普通的类一样 , 接口文件使用 .java 扩展名, 编译后会生成 .class 文件。
1. 接口的声明和实现
声明的语法格式:
[可见性修饰符] interface 接口名称 [extends 其他接口名] {
// 常量
// 抽象方法
}
实现的语法格式:
class 类名 implements 接口名 [, 接口名2, 接口名3...] {
// 实现接口中定义的方法
}
示例:
// 定义接口
public interface Animal {
void eat();
void travel();
}
// 类实现接口
public class MammalInt implements Animal {
@Override
public void eat() {
System.out.println("Mammal eats");
}
@Override
public void travel() {
System.out.println("Mammal travels");
}
public static void main(String[] args) {
MammalInt mammal = new MammalInt();
mammal.eat(); // 输出:Mammal eats
mammal.travel(); // 输出:Mammal travels
}
}
注意点:
- 如果一个类实现了接口,则必须实现接口中定义的 所有方法(除非该类本身是抽象类)
@Override注解用来明确表示方法是接口方法的实现
2. 接口的特点
-
方法默认是
public abstract -
变量默认是
public static final(必须是常量,且为静态变量)interface Example { int VALUE = 10; // 等价于 public static final int VALUE = 10; } -
Java 的类不支持继承多个父类,但接口支持多继承。
语法:
public interface NewInterface extends InterfaceA, InterfaceB { // 新方法定义 }示例:
public interface Sports { void setHomeTeam(String name); void setVisitingTeam(String name); } public interface Football extends Sports { void homeTeamScored(int points); void visitingTeamScored(int points); void endOfQuarter(int quarter); } public interface Hockey extends Sports { void homeGoalScored(); void visitingGoalScored(); void overtimePeriod(int ot); }
接口与类的不同点:
特性 接口 类 构造方法 没有 可以有构造方法 成员变量 只能是 static和final的常量可以包含普通成员变量 方法 默认是 public abstract;从 Java 8 起可包含定义的默认方法和静态方法包含普通方法和静态方法,可以有实现 实现方式 通过 implements关键字实现子类通过 extends关键字继承父类多继承 支持多继承 不支持多继承 是否能实例化 不能实例化 可以实例化对象
3. 接口中允许的方法
默认方法 (default)
语法:
interface Example {
default void defaultMethod() {
System.out.println("This is a default method");
}
}
实现类可选择重写默认方法:
public class MyClass implements Example {
@Override
public void defaultMethod() {
System.out.println("Overridden default method");
}
}
静态方法 (static)
-
接口可以包含静态方法,供直接通过接口名调用。
-
语法:
interface Example { static void staticMethod() { System.out.println("This is a static method in an interface"); } } -
调用:
Example.staticMethod(); // 不需要实例化
私有方法
interface Example {
private void helperMethod() {
System.out.println("Helper method");
}
}
4. 标记接口
标记接口是没有任何方法和字段的接口,仅用于给类 "打标记",从而赋予类某种特权或能力。
常见标记接口:
java.io.Serializable:标记可以序列化的类。java.util.EventListener:标记事件监听器类。
示例:
public class MyClass implements java.io.Serializable {
private static final long serialVersionUID = 1L;
// 该类可以被序列化
}
(二)抽象类
抽象类在面向对象编程中用于定义具有通用特性的类,但由于其抽象性,这种类不能直接实例化,只能被其他子类继承并实现其抽象方法。抽象类定义了一个框架,具体的实现由子类完成。
1. 定义抽象类
在 Java 中使用 abstract class 定义抽象类
包含 抽象方法(没有方法体的方法)以及 普通方法(有方法体的方法)
public abstract class Animal {
String name; // 普通属性
int age;
Animal(String name, int age) { // 构造方法
this.name = name;
this.age = age;
}
public void eat() { // 普通方法
System.out.println(name + " 正在吃饭");
}
public abstract void sound(); // 抽象方法
}
2. 定义和使用子类
通过继承抽象类并实现抽象方法,完成具体实现:
public class Dog extends Animal {
Dog(String name, int age) {
super(name, age);
}
@Override
public void sound() { // 重写抽象方法
System.out.println(name + " 汪汪叫");
}
}
public class Cat extends Animal {
Cat(String name, int age) {
super(name, age);
}
@Override
public void sound() { // 重写抽象方法
System.out.println(name + " 喵喵叫");
}
}
使用子类:
public class TestAbstractClass {
public static void main(String[] args) {
Animal dog = new Dog("小狗", 3);
dog.eat(); // 输出:小狗 正在吃饭
dog.sound(); // 输出:小狗 汪汪叫
Animal cat = new Cat("小猫", 2);
cat.eat(); // 输出:小猫 正在吃饭
cat.sound(); // 输出:小猫 喵喵叫
}
}
3. 抽象类规则总结
- 抽象类不能直接实例化: 只能通过子类实例化。
- 如果子类是抽象类,则可以选择不实现该抽象方法,但最终必须由具体的子类完成。
- 如果子类是普通类,则必须实现 所有 从抽象类继承的抽象方法。
- 抽象类可以没有抽象方法: 但是一旦有抽象方法,类必须声明为抽象类。
- 抽象方法不能有方法体: 仅提供方法签名。
- 构造方法不能是抽象的: 抽象方法用于行为定义,构造方法是用于实例化的。
4. 抽象类和接口的对比
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 继承关键字 | extends | implements |
| 继承数量 | 一个类只能继承一个抽象类 | 一个类可以实现多个接 口 |
| 方法实现 | 可以包含具体方法实现 | 方法没有实现(JDK 8+ 可以有默认方法) |
| 构造方法 | 可以有构造方法 | 不能有构造方法 |
| 成员变量 | 可以有普通成员变量 | 只能有 static 和 final 修饰的常量 |
| 方法 | 默认 public abstract,可以有默认和静态方法 | 普通方法和抽象方法均支持 |
| 使用场景 | 用于实现行为规范 | 用于描述类的共有属性和方法,实现代码复用 |
默认方法 (
default) 静态方法 (static) 私有方法 (private)
(二)继承
1. 接口多继承
一个子类只能继承一个父类,但支持接口的多继承(implements)。
示例:
public interface A { void eat(); }
public interface B { void sleep(); }
public class C implements A, B {
public void eat() { System.out.println("C 正在吃"); }
public void sleep() { System.out.println("C 正在睡"); }
}
特点
- 子类继承父类非
private的属性和方法。 - 子类可以重写(override)父类方法。
- 继承提高了类的耦合性,耦合度过高时可能降低代码独立性。
2. super 关键字
-
super关键字:- 用于访问父类的属性或方法。
- 调用父类构造方法时必须放在子类构造器的第一行。
示例:
class Animal { void eat() { System.out.println("Animal 正在吃"); } } class Dog extends Animal { @Override void eat() { System.out.println("Dog 正在吃"); } void test() { this.eat(); // 调用子类的 eat 方法 super.eat(); // 调用父类的 eat 方法 } }运行结果:
Dog 正在吃
Animal 正在吃
3. 构造方法的继承
- 子类不继承父类的构造方法,但会调用父类的构造方法。
-
显式调用:
- 使用
super调用父类的带参构造方法。
class Animal { public Animal(String name) { System.out.println("父类构造:" + name); } } class Dog extends Animal { public Dog(String name) { super(name); } } - 使用
-
隐式调用:
- 如果父类有无参构造,系统自动调用:
class Animal { public Animal() { System.out.println("父类无参构造"); }} class Dog extends Animal {}
示例带构造器的继承:
class SuperClass {
public SuperClass() { System.out.println("SuperClass 无参构造"); }
public SuperClass(int n) { System.out.println("SuperClass 带参构造:" + n); }
}
class SubClass extends SuperClass {
public SubClass() { super(); System.out.println("SubClass 无参构造"); }
public SubClass(int n) { super(n); System.out.println("SubClass 带参构造:" + n); }
}
public class Test {
public static void main(String[] args) {
new SubClass();
new SubClass(100);
}
}
输出结果:
SuperClass 无参构造
SubClass 无参构造
SuperClass 带参构造:100
SubClass 带参构造:100
(三)重写和重载
重写 和 重载 是 Java 面向对象编程中 方法多态性 的两种重要体现。
1. 重写 (Override)
重写是指 子类 中定义了与 父类 中具有相同名称、参数列表和返回类型的方法,并提供了自己的实现,从而覆盖父类的方法。
规则:
-
参数列表必须与被重写的方法完全相同。
-
返回类型必须与被重写方法的返回类型相同 ,或者是其 子类。
-
子类的访问控制修饰符,不能比父类的更严格。
- 例如:父类方法是
public,子类方法不能是protected或private。
- 例如:父类方法是
-
重写的方法可以抛出更少或更具体的异常,但不能抛出新的或更广泛的检查异常。
- 例如:父类方法抛出
IOException,子类方法可以抛出其子类FileNotFoundException,但不能抛出Exception(IOException的父类)。
- 例如:父类方法抛出
-
final和static修饰的方法不能被重写。 -
构造方法不能被重写。
示例
// 父类 Animal
class Animal {
public void move() {
System.out.println("动物可以移动");
}
}
// 子类 Dog
class Dog extends Animal {
@Override
public void move() { // 重写父类的 move 方法
System.out.println("狗可以跑和走");
}
}
public class TestOverride {
public static void main(String[] args) {
Animal a = new Animal(); // 父类对象
Animal b = new Dog(); // 子类对象
a.move(); // 输出:动物可以移动
b.move(); // 输出:狗可以跑和走(运行时多态,调用子类的 move 方法)
}
}
重写时可以使用
super关键字调用父类的相同方法class Animal { public void move() { System.out.println("动物可以移动"); } } class Dog extends Animal { @Override public void move() { super.move(); // 调用父类的方法 System.out.println("狗可以跑和走"); } } public class TestSuper { public static void main(String[] args) { Animal b = new Dog(); // 创建 Dog 对象 b.move(); } }输出:
动物可以移动
狗可以跑和走
2. 重载 (Overload)
重载是指在同一个类中定义多个 方法,这些方法具有相同的方法名,但参数的类型、数量或顺序不同。
规则:
-
参数列表必须改变,具体体现在
参数数量参数类型参数的顺序 -
返回类型可以相同,也可以不同。
-
访问修饰符可以不同(如
public、private)。 -
静态方法同样可以被重载。
-
**无法仅通过返回值判断重载:**仅改变返回值类型不能成为区分重载方法的标准。
示例
public class Overloading {
// 无参方法
public void display() {
System.out.println("无参数方法");
}
// 一个参数
public void display(String name) {
System.out.println("参数是 String 类型:" + name);
}
// 两个参数 - 顺序不同
public void display(int age, String name) {
System.out.println("年龄:" + age + ", 姓名:" + name);
}
// 两个参数 - 顺序不同
public void display(String name, int age) {
System.out.println("姓名:" + name + ", 年龄:" + age);
}
public static void main(String[] args) {
Overloading overload = new Overloading();
// 调用不同的重载方法
overload.display();
overload.display("Alice");
overload.display(18, "Bob");
overload.display("Charlie", 20);
}
}
输出:
无参数方法
参数是 String 类型:Alice
年龄:18, 姓名:Bob
姓名:Charlie, 年龄:20
3. 重写与重载的区别
| 特性 | 重写 (Override) | 重载 (Overload) |
|---|---|---|
| 定义位置 | 子类与父类之间 | 同一类中 |
| 方法名 | 必须相同 | 必须相同 |
| 参数列表 | 必须相同 | 必须不同 |
| 返回类型 | 必须相同或父类的子类(协变返回类型) | 可以相同,也可以不同 |
| 访问修饰符 | 子类不能降低父类的访问权限 | 可以随意设置 |
| 异常 | 只能抛出相同或更少范围的异常(不能扩大检查异常类型) | 可以抛出任意异常 |
| 关键字影响 | final 和 static 的方法不能重写 | 与关键字无关 |
| 典型场景 | 改写父类行为 | 一个方法需要多个版本来处理不同的参数 |
| 多态性 | 支持运行时多态 | 不支持运行时多态(编译时决定) |
(四)多态
多态是指 同一个方法 在不同的对象中有不同的实现。。简单来说,多态实现了 “一个接口,多种行为” 的能力。
在 Java 中,多态通常通过 继承 和 接口 实现,是 动态绑定 的一种体现。
1. 父类引用指向子类对象 实现多态
class Animal {
void eat() {
System.out.println("动物吃饭");
}
}
class Cat extends Animal {
@Override
void eat() {
System.out.println("猫吃鱼");
}
}
class Dog extends Animal {
@Override
void eat() {
System.out.println("狗吃骨头");
}
}
public class Test {
public static void main(String[] args) {
Animal a1 = new Cat(); // 父类引用指向子类 Cat
a1.eat(); // 输出:猫吃鱼
Animal a2 = new Dog(); // 父类引用指向子类 Dog
a2.eat(); // 输出:狗吃骨头
}
}
解释:
子类重写了父类的 方法。
Animal a1 = new Cat(); 将父类引用指向子类对象
在运行时,JVM 决定调用真正属于子类的方法,而不是父类的方法,
在 Java 中,方法调用有两种绑定方式:
动态绑定 , 如
Animal a1 = new Cat();, 在运行时决定调用哪个方法,动态绑定是多态实现的基础。静态绑定 , 如
Cat a1 = new Cat();,在编译时决定调用哪个方法
注意: 父类的引用类型只能调用父类中 声明 的方法。
2. 使用接口 实现多态
通过实现接口的多态,实现统一标准下的不同行为。
interface Animal {
void makeSound();
}
class Dog implements Animal {
public void makeSound() {
System.out.println("汪汪汪");
}
}
class Cat implements Animal {
public void makeSound() {
System.out.println("喵喵喵");
}
}
public class TestInterface {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
dog.makeSound(); // 输出:汪汪汪
cat.makeSound(); // 输出:喵喵喵
}
}
3. 向上转型与向下转型
向上转型 (Upcasting):
- 子类对象赋给父类引用,这种转换是隐式的。
- 示例:
Animal animal = new Cat(); // 子类 Cat 转为父类 Animal animal.eat(); // 调用 Cat 的 eat() 方法
向下转型 (Downcasting):
- 父类引用转为子类对象,这种转换需要显式声明。
- 示例:
Animal animal = new Cat(); Cat cat = (Cat) animal; // 将 Animal 转为 Cat cat.eat(); // 调用 Cat 的 eat() 方法
向下转型示例 + 类型检查
在进行向下转型时,通常需要先通过 instanceof 检查。
public class Test {
public static void show(Animal a) {
a.eat();
if (a instanceof Cat) {
Cat c = (Cat) a; // 向下转型
c.work(); // 调用 Cat 特有的方法
} else if (a instanceof Dog) {
Dog d = (Dog) a; // 向下转型
d.work(); // 调用 Dog 特有的方法
}
}
public static void main(String[] args) {
show(new Cat()); // 输出:猫吃鱼 -> 猫抓老鼠
show(new Dog()); // 输出:狗吃骨头 -> 狗看家
}
}
class Animal {
void eat() {}
}
class Cat extends Animal {
@Override
void eat() { System.out.println("猫吃鱼"); }
void work() { System.out.println("猫抓老鼠"); }
}
class Dog extends Animal {
@Override
void eat() { System.out.println("狗吃骨头"); }
void work() { System.out.println("狗看家"); }
}
输出结果:
猫吃鱼
猫抓老鼠
狗吃骨头
狗看家
4. 虚函数与动态绑定
虚函数在 Java 中的特点
- 在 Java 中,所有非
static方法默认是虚函数。 - Java 默认采用 动态绑定(
Dynamic Binding),也就是说在运行时按照对象的实际类型调用方法,而非引用类型。
class Employee {
void mailCheck() {
System.out.println("Employee 邮寄支票");
}
}
class Salary extends Employee {
@Override
void mailCheck() {
System.out.println("Salary 邮寄支票");
}
}
public class VirtualFunctionDemo {
public static void main(String[] args) {
Employee e = new Salary(); // 父类引用指向子类对象
e.mailCheck(); // 动态绑定,调用 Salary 的 mailCheck 方法
}
}
输出:
Salary 邮寄支票