继承与多态动手动脑
- 实验代码
// 祖父类
class GrandParent {
public GrandParent() {
System.out.println("GrandParent 无参构造方法");
}
public GrandParent(String msg) {
System.out.println("GrandParent 带参构造方法:" + msg);
}
}
// 父类
class Parent extends GrandParent {
public Parent() {
// 显式调用GrandParent的带参构造方法,必须放在子类构造方法第一句
super("调用GrandParent带参构造");
System.out.println("Parent 无参构造方法");
}
}
// 子类
class Child extends Parent {
public Child() {
// 隐式调用Parent的无参构造方法
System.out.println("Child 无参构造方法");
}
}
// 测试类
public class TestInherits {
public static void main(String[] args) {
new Child();
}
}
3. 实验结果
GrandParent 带参构造方法:调用GrandParent带参构造
Parent 无参构造方法
Child 无参构造方法
4. 结论
- 子类构造方法默认会隐式调用父类的无参构造方法。
- 通过super关键字调用父类构造方法时,该语句必须是子类构造方法中的第一句,否则会编译错误。
- 若父类没有无参构造方法,子类构造方法必须显式通过super调用父类的带参构造方法,否则编译失败。
(二)继承条件下访问权限验证
1. 实验目的
验证public、private、protected、default四种访问权限在继承中的作用范围。
2. 实验代码(分两个包)
包1:com.example.parent
package com.example.parent;
// 父类
public class Parent {
public String publicField = "public字段";
private String privateField = "private字段";
protected String protectedField = "protected字段";
String defaultField = "default字段"; // 默认权限
public void publicMethod() {
System.out.println("Parent public方法");
}
private void privateMethod() {
System.out.println("Parent private方法");
}
protected void protectedMethod() {
System.out.println("Parent protected方法");
}
void defaultMethod() {
System.out.println("Parent default方法");
}
}
包2:com.example.child(同包子类)
package com.example.parent;
// 同包子类
public class SamePackageChild extends Parent {
public void accessParentField() {
System.out.println(publicField); // 可访问
// System.out.println(privateField); // 编译错误,private不可访问
System.out.println(protectedField); // 可访问
System.out.println(defaultField); // 可访问(同包)
}
public void callParentMethod() {
publicMethod(); // 可访问
// privateMethod(); // 编译错误,private不可访问
protectedMethod(); // 可访问
defaultMethod(); // 可访问(同包)
}
}
包3:com.example.other(不同包子类)
package com.example.other;
import com.example.parent.Parent;
// 不同包子类
public class DifferentPackageChild extends Parent {
public void accessParentField() {
System.out.println(publicField); // 可访问
// System.out.println(privateField); // 编译错误
System.out.println(protectedField); // 可访问(不同包子类)
// System.out.println(defaultField); // 编译错误,default不同包不可访问
}
public void callParentMethod() {
publicMethod(); // 可访问
// privateMethod(); // 编译错误
protectedMethod(); // 可访问(不同包子类)
// defaultMethod(); // 编译错误,default不同包不可访问
}
}
3. 实验结论
| 访问权限 | 同一类 | 同一包非子类 | 同一包子类 | 不同包子类 | 不同包非子类 |
|---|---|---|---|---|---|
| public | ✅ | ✅ | ✅ | ✅ | ✅ |
| private | ✅ | ❌ | ❌ | ❌ | ❌ |
| protected | ✅ | ✅ | ✅ | ✅ | ❌ |
| default | ✅ | ✅ | ✅ | ❌ | ❌ |
(三)final类与不可变类验证
1. 实验目的
验证final修饰的类不可继承、final修饰的方法不可覆盖、final修饰的变量不可修改,以及不可变类的特性。
2. 实验代码
// final类,不可被继承
final class FinalClass {
// final方法,不可被覆盖
public final void finalMethod() {
System.out.println("FinalClass final方法");
}
}
// 尝试继承final类,编译错误
// class SubClass extends FinalClass {}
// 不可变类示例(类似String)
class ImmutableClass {
private final String name; // final变量,初始化后不可修改
private final int age;
// 构造方法初始化所有final变量
public ImmutableClass(String name, int age) {
this.name = name;
this.age = age;
}
// 只提供getter,不提供setter
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
// 测试类
public class TestFinal {
public static void main(String[] args) {
// 测试final变量
final int num = 10;
// num = 20; // 编译错误,final变量不可修改
// 测试不可变类
ImmutableClass ic = new ImmutableClass("张三", 20);
System.out.println("姓名:" + ic.getName() + ",年龄:" + ic.getAge());
// 无法修改ic的name和age,因为没有setter且变量为final
}
}
3. 实验结论
- final修饰的类不能被继承,否则编译错误。
- final修饰的方法不能被子类覆盖,否则编译错误。
- final修饰的变量(基本类型)初始化后不可修改;修饰引用类型时,引用地址不可变,但对象内部属性可修改(需通过类设计限制,如不可变类不提供setter)。
- 不可变类(如String、上述ImmutableClass)的对象创建后,属性无法修改,可安全用于多线程环境,无需加锁。
(四)Object类toString()方法重写验证
1. 实验目的
验证默认toString()方法的输出,以及重写后自定义输出的效果。
2. 实验代码
// 未重写toString()的类
class NoOverrideToString {
private String info = "未重写toString()";
}
// 重写toString()的类
class OverrideToString {
private String name;
private int id;
public OverrideToString(String name, int id) {
this.name = name;
this.id = id;
}
// 重写toString()方法
@Override
public String toString() {
return "OverrideToString{name='" + name + "', id=" + id + "}";
}
}
// 测试类
public class TestToString {
public static void main(String[] args) {
// 未重写toString(),输出默认格式:类名@哈希码(十六进制)
NoOverrideToString no = new NoOverrideToString();
System.out.println(no); // 输出:com.example.NoOverrideToString@1c5f743
// 重写toString(),输出自定义格式
OverrideToString ot = new OverrideToString("李四", 1001);
System.out.println(ot); // 输出:OverrideToString{name='李四', id=1001}
// 验证“+”运算中隐式调用toString()
String result = "对象信息:" + ot;
System.out.println(result); // 输出:对象信息:OverrideToString{name='李四', id=1001}
}
}
3. 实验结论
- Object类默认的toString()方法返回格式为:
全类名@哈希码(十六进制),其中哈希码由hashCode()方法(本地方法)生成。 - 子类可重写toString()方法,自定义对象的字符串表示形式,便于调试和输出。
- 在“+”字符串拼接运算中,对象会隐式调用toString()方法,将对象转换为字符串后再拼接。
(五)方法覆盖(Override)语法规则验证
1. 实验目的
验证方法覆盖的语法规则:访问范围、异常抛出、final方法、静态方法的限制。
2. 实验代码
// 父类
class ParentOverride {
// 父类方法:public访问权限,抛出RuntimeException
public void parentMethod() throws RuntimeException {
System.out.println("Parent parentMethod");
}
// final方法,不可被覆盖
public final void finalMethod() {
System.out.println("Parent finalMethod");
}
// 静态方法,不可被覆盖(子类同名方法为隐藏,非覆盖)
public static void staticMethod() {
System.out.println("Parent staticMethod");
}
}
// 子类
class ChildOverride extends ParentOverride {
// 规则1:覆盖方法访问范围不能小于父类(public→public,合法)
@Override
public void parentMethod() throws RuntimeException {
// 规则2:覆盖方法抛出的异常不能比父类多(父类抛RuntimeException,子类不抛或抛其子类,合法)
System.out.println("Child parentMethod");
}
// 尝试覆盖final方法,编译错误
// @Override
// public void finalMethod() {
// System.out.println("Child finalMethod");
// }
// 静态方法:子类同名方法为隐藏,非覆盖(@Override注解会报错)
public static void staticMethod() {
System.out.println("Child staticMethod");
}
// 调用父类被覆盖的方法
public void callParentMethod() {
super.parentMethod(); // 通过super调用父类被覆盖的方法
}
}
// 测试类
public class TestOverride {
public static void main(String[] args) {
ChildOverride co = new ChildOverride();
co.parentMethod(); // 调用子类覆盖后的方法,输出:Child parentMethod
co.callParentMethod(); // 调用父类方法,输出:Parent parentMethod
// 静态方法:调用取决于变量类型,非对象类型(非多态)
ParentOverride po = new ChildOverride();
ParentOverride.staticMethod(); // 输出:Parent staticMethod
ChildOverride.staticMethod(); // 输出:Child staticMethod
po.staticMethod(); // 输出:Parent staticMethod(变量类型为ParentOverride)
}
}
3. 实验结论
- 方法覆盖要求子类与父类方法的签名(方法名、参数列表、返回值类型,其中返回值可为父类返回值的子类)完全一致。
- 覆盖方法的访问权限不能小于父类(如父类为public,子类不能为protected/private)。
- 覆盖方法抛出的异常不能比父类多(异常范围不能更大,可相同或为子类异常)。
- final方法不可被覆盖,静态方法不可被覆盖(子类同名静态方法为“隐藏”,调用取决于变量类型,非对象类型)。
- 子类中可通过super关键字调用父类被覆盖的方法。
二、多态相关实践验证
(一)多态核心特性验证(父类引用指向子类对象)
1. 实验目的
验证多态的核心特性:父类变量引用子类对象时,方法调用取决于对象的真实类型,字段访问取决于变量类型。
2. 实验代码
// 父类
class ParentPolymorphic {
public int myValue = 100;
public void printValue() {
System.out.println("Parent.printValue(), myValue=" + myValue);
}
}
// 子类
class ChildPolymorphic extends ParentPolymorphic {
public int myValue = 200; // 隐藏父类字段
@Override
public void printValue() {
System.out.println("Child.printValue(), myValue=" + myValue);
}
}
// 测试类
public class ParentChildTest {
public static void main(String[] args) {
// 1. 父类对象调用方法和字段
ParentPolymorphic parent = new ParentPolymorphic();
parent.printValue(); // 输出:Parent.printValue(), myValue=100
System.out.println("Parent myValue: " + parent.myValue); // 输出:Parent myValue: 100
// 2. 子类对象调用方法和字段
ChildPolymorphic child = new ChildPolymorphic();
child.printValue(); // 输出:Child.printValue(), myValue=200
System.out.println("Child myValue: " + child.myValue); // 输出:Child myValue: 200
// 3. 父类引用指向子类对象(多态)
ParentPolymorphic poly = new ChildPolymorphic();
poly.printValue(); // 方法调用取决于对象真实类型(子类),输出:Child.printValue(), myValue=200
System.out.println("Poly myValue: " + poly.myValue); // 字段访问取决于变量类型(父类),输出:Poly myValue: 100
// 4. 修改父类字段(通过父类引用)
poly.myValue++;
System.out.println("After poly.myValue++, poly.myValue: " + poly.myValue); // 输出:101
poly.printValue(); // 子类方法访问的是子类字段,输出:Child.printValue(), myValue=200
// 5. 强制类型转换后访问子类字段
((ChildPolymorphic) poly).myValue++;
System.out.println("After cast, Child myValue: " + ((ChildPolymorphic) poly).myValue); // 输出:201
poly.printValue(); // 输出:Child.printValue(), myValue=201
}
}
3. 实验结果
Parent.printValue(), myValue=100
Parent myValue: 100
Child.printValue(), myValue=200
Child myValue: 200
Child.printValue(), myValue=200
Poly myValue: 100
After poly.myValue++, poly.myValue: 101
Child.printValue(), myValue=200
After cast, Child myValue: 201
Child.printValue(), myValue=201
4. 实验结论
- 多态场景下,方法调用遵循“动态绑定”:调用哪个方法由对象的真实类型决定(子类对象调用子类方法,父类对象调用父类方法)。
- 字段访问遵循“静态绑定”:访问哪个字段由变量的声明类型决定(父类变量访问父类字段,子类变量访问子类字段,即使父类变量引用子类对象)。
- 实际开发中应避免子类定义与父类同名的字段,避免字段隐藏导致的逻辑混乱。
(二)类型转换与instanceof验证
1. 实验目的
验证子类与父类对象的类型转换规则,以及instanceof运算符的作用。
2. 实验代码
// 基类
class Mammal {
}
// 子类1
class Dog extends Mammal {
}
// 子类2
class Cat extends Mammal {
}
// 测试类
public class TestCast {
public static void main(String[] args) {
Mammal m = null;
Dog d = new Dog();
Cat c = new Cat();
// 1. 子类对象赋值给父类变量(自动类型转换,合法)
m = d;
System.out.println("m = d: 赋值成功(子类→父类)");
// 2. 父类变量直接赋值给子类变量(编译错误,需强制转换)
// d = m;
// 3. 父类变量强制转换为子类(m此时引用Dog对象,转换成功)
d = (Dog) m;
System.out.println("d = (Dog) m: 强制转换成功(m引用Dog对象)");
// 4. 不同子类对象之间转换(编译错误,Dog和Cat无继承关系)
// d = c;
// 5. 父类变量强制转换为无关子类(运行时错误,ClassCastException)
m = new Mammal(); // m此时引用Mammal对象
try {
c = (Cat) m;
} catch (ClassCastException e) {
System.out.println("c = (Cat) m: 运行时错误," + e.getMessage());
}
// 6. instanceof运算符:判断对象是否可转换为指定类型
Object obj1 = new Dog();
Object obj2 = new Cat();
Object obj3 = new Mammal();
System.out.println("obj1 instanceof Dog: " + (obj1 instanceof Dog)); // true
System.out.println("obj1 instanceof Mammal: " + (obj1 instanceof Mammal)); // true
System.out.println("obj2 instanceof Cat: " + (obj2 instanceof Cat)); // true
System.out.println("obj2 instanceof Dog: " + (obj2 instanceof Dog)); // false
System.out.println("obj3 instanceof Cat: " + (obj3 instanceof Cat)); // false
}
}
3. 实验结果
m = d: 赋值成功(子类→父类)
d = (Dog) m: 强制转换成功(m引用Dog对象)
c = (Cat) m: 运行时错误,class com.example.Mammal cannot be cast to class com.example.Cat
obj1 instanceof Dog: true
obj1 instanceof Mammal: true
obj2 instanceof Cat: true
obj2 instanceof Dog: false
obj3 instanceof Cat: false
4. 实验结论
- 自动类型转换:子类对象可直接赋值给父类变量(向上转型),无需强制转换。
- 强制类型转换:父类变量赋值给子类变量时,必须进行强制转换;若父类变量引用的对象不是目标子类的实例,运行时会抛出
ClassCastException。 - instanceof运算符:用于判断对象是否为指定类(或其子类)的实例,返回boolean值;可在强制转换前使用,避免
ClassCastException。 - 无继承关系的类之间不能相互转换(编译错误)。
(三)多态实际应用:动物园喂食场景重构
1. 实验目的
通过重构动物园喂食程序,验证多态在代码扩展性、可维护性上的优势。
2. 版本一:无多态(硬编码)
// 狮子类
class Lion {
public void eat() {
System.out.println("狮子吃鲜肉");
}
}
// 猴子类
class Monkey {
public void eat() {
System.out.println("猴子吃香蕉");
}
}
// 鸽子类
class Pigeon {
public void eat() {
System.out.println("鸽子吃谷物");
}
}
// 饲养员类(硬编码,每个动物对应一个喂食方法)
class FeederV1 {
private String name;
public FeederV1(String name) {
this.name = name;
}
public void feedLion(Lion lion) {
System.out.print(name + "喂养:");
lion.eat();
}
public void feedMonkey(Monkey monkey) {
System.out.print(name + "喂养:");
monkey.eat();
}
public void feedPigeon(Pigeon pigeon) {
System.out.print(name + "喂养:");
pigeon.eat();
}
}
// 测试类
public class ZooV1 {
public static void main(String[] args) {
FeederV1 feeder = new FeederV1("小李");
// 喂养1只狮子
feeder.feedLion(new Lion());
// 喂养10只猴子
for (int i = 0; i < 10; i++) {
feeder.feedMonkey(new Monkey());
}
// 喂养5只鸽子
for (int i = 0; i < 5; i++) {
feeder.feedPigeon(new Pigeon());
}
}
}
问题:若新增动物(如老虎),需修改Feeder类,新增feedTiger()方法,违反“开闭原则”(对扩展开放,对修改关闭)。
3. 版本二:基于继承的多态(重构后)
// 抽象动物类(基类)
abstract class Animal {
public abstract void eat();
}
// 狮子类(继承Animal)
class LionV2 extends Animal {
@Override
public void eat() {
System.out.println("狮子吃鲜肉");
}
}
// 猴子类(继承Animal)
class MonkeyV2 extends Animal {
@Override
public void eat() {
System.out.println("猴子吃香蕉");
}
}
// 鸽子类(继承Animal)
class PigeonV2 extends Animal {
@Override
public void eat() {
System.out.println("鸽子吃谷物");
}
}
// 饲养员类(多态,一个方法处理所有动物)
class FeederV2 {
private String name;
public FeederV2(String name) {
this.name = name;
}
// 接收Animal类型参数,可处理所有Animal子类对象
public void feedAnimal(Animal animal) {
System.out.print(name + "喂养:");
animal.eat(); // 多态:调用子类的eat()方法
}
// 批量喂养(接收Animal数组)
public void feedAnimals(Animal[] animals) {
for (Animal animal : animals) {
feedAnimal(animal);
}
}
// 批量喂养(接收Vector集合,支持动态增减)
public void feedAnimals(Vector<Animal> animals) {
for (Animal animal : animals) {
feedAnimal(animal);
}
}
}
// 测试类
public class ZooV2 {
public static void main(String[] args) {
FeederV2 feeder = new FeederV2("小李");
// 方式1:单个喂养
feeder.feedAnimal(new LionV2());
// 方式2:数组批量喂养
Animal[] animalArray = new Animal[16];
animalArray[0] = new LionV2();
for (int i = 0; i < 10; i++) {
animalArray[1 + i] = new MonkeyV2();
}
for (int i = 0; i < 5; i++) {
animalArray[11 + i] = new PigeonV2();
}
feeder.feedAnimals(animalArray);
// 方式3:Vector动态喂养(支持新增/删除动物)
Vector<Animal> animalVector = new Vector<>();
animalVector.add(new LionV2()); // 新增狮子
for (int i = 0; i < 10; i++) {
animalVector.add(new MonkeyV2()); // 新增猴子
}
animalVector.remove(0); // 删除第一只狮子(动态调整)
feeder.feedAnimals(animalVector);
// 新增动物(如老虎),无需修改Feeder类
class Tiger extends Animal {
@Override
public void eat() {
System.out.println("老虎吃鲜肉");
}
}
feeder.feedAnimal(new Tiger()); // 直接使用,无需修改Feeder
}
}
4. 实验结论
- 多态通过“父类引用指向子类对象”,使代码具备良好的扩展性:新增子类(如Tiger)时,无需修改已有代码(如Feeder类),只需新增子类并实现抽象方法。
- 多态减少了代码冗余:将多个相似方法(
feedLion()、feedMonkey())合并为一个通用方法(feedAnimal())。 - 结合集合(如Vector)使用时,可动态管理对象(新增/删除),进一步提升代码灵活性。
三、抽象类与接口实践验证
(一)抽象类与抽象方法验证
1. 实验目的
验证抽象类不能实例化、抽象方法必须由子类实现的规则。
2. 实验代码
// 抽象类(包含抽象方法和非抽象方法)
abstract class Person {
private String name;
// 非抽象方法
public Person(String name) {
this.name = name;
}
// 抽象方法(无实现)
public abstract String getDescription();
// 非抽象方法
public String getName() {
return name;
}
}
// 子类实现抽象方法
class Employee extends Person {
private String department;
public Employee(String name, String department) {
super(name); // 调用父类构造方法
this.department = department;
}
// 实现父类的抽象方法
@Override
public String getDescription() {
return "员工" + getName() + ",所属部门:" + department;
}
}
// 子类未完全实现抽象方法(仍为抽象类)
abstract class Student extends Person {
private String studentId;
public Student(String name, String studentId) {
super(name);
this.studentId = studentId;
}
// 未实现getDescription(),因此Student仍为抽象类
public String getStudentId() {
return studentId;
}
}
// 测试类
public class TestAbstract {
public static void main(String[] args) {
// 抽象类不能实例化,编译错误
// Person p = new Person("张三");
// 抽象类变量引用子类对象(多态)
Person emp = new Employee("李四", "研发部");
System.out.println(emp.getDescription()); // 输出:员工李四,所属部门:研发部
// 未完全实现抽象方法的子类不能实例化,编译错误
// Student stu = new Student("王五", "2023001");
}
}
3. 实验结论
- 抽象类(用
abstract修饰)不能直接实例化,只能通过其子类实例化(子类需完全实现抽象方法)。 - 包含抽象方法(用
abstract修饰,无方法体)的类必须是抽象类;抽象类可包含非抽象方法和成员变量。 - 子类继承抽象类后,必须实现父类的所有抽象方法,否则子类仍需声明为抽象类。
- 抽象类的主要作用是作为基类,定义子类的共同行为(抽象方法),实现代码复用和多态。
(二)接口实践与特性验证
1. 实验目的
验证接口的定义、实现、多实现、接口继承等特性,以及接口与抽象类的区别。
2. 实验代码
// 接口1:可食用
interface IFood {
// 接口方法默认public abstract,无需显式声明
void cook();
// 接口常量默认public static final
String FOOD_TYPE = "可食用物品";
}
// 接口2:会游泳(继承IFood接口)
interface ISwim extends IFood {
void swim();
}
// 抽象类:鸟
abstract class Bird {
public void fly() {
System.out.println("鸟会飞");
}
public abstract void layEggs(); // 抽象方法
}
// 鸭子类:继承Bird,实现ISwim接口(多实现)
class Duck implements ISwim {
@Override
public void cook() {
System.out.println("鸭子可以烹饪(如烤鸭)");
}
@Override
public void swim() {
System.out.println("鸭子会游泳");
}
}
// 测试类
public class TestInterface {
public static void main(String[] args) {
// 接口变量引用实现类对象(多态)
IFood food = new Duck();
food.cook(); // 输出:鸭子可以烹饪(如烤鸭)
System.out.println("食物类型:" + IFood.FOOD_TYPE); // 访问接口常量
ISwim swim = new Duck();
swim.swim(); // 输出:鸭子会游泳
swim.cook(); // 实现了父接口IFood,可调用cook()
// 接口与抽象类的区别:鸭子同时是Bird(继承)和IFood/ISwim(实现),实现多特性
Duck duck = new Duck();
duck.fly(); // 继承Bird的方法,输出:鸟会飞
duck.swim(); // 实现ISwim的方法
duck.cook(); // 实现IFood的方法
}
}
3. 实验结论
- 接口(用
interface修饰)是方法声明的集合,方法默认public abstract,常量默认public static final。 - 类实现接口(用
implements)时,必须实现接口的所有方法;一个类可实现多个接口(多实现),解决Java单继承的限制。 - 接口可继承接口(用
extends),子接口包含父接口的所有方法;实现子接口的类需实现父接口和子接口的所有方法。 - 接口与抽象类的区别:
- 抽象类是“不完全的类”,可包含非抽象方法和成员变量;接口仅包含方法声明和常量,无实现细节。
- 一个类只能继承一个抽象类,但可实现多个接口。
- 抽象类用于定义类的继承关系(IS-A),接口用于定义类的行为特性(CAN-DO)。
四、总结
本次实践围绕Java继承与多态的核心知识点,通过代码验证了以下关键结论:
- 继承:子类通过
extends继承父类,自动拥有父类的public/protected成员;构造方法调用需遵循super关键字规则;final类不可继承、final方法不可覆盖、final变量不可修改。 - 多态:核心是“父类引用指向子类对象”,方法调用动态绑定(取决于对象真实类型),字段访问静态绑定(取决于变量类型);多态显著提升代码的扩展性和可维护性。
- 抽象类与接口:抽象类不能实例化,用于定义子类共同行为;接口用于定义类的行为特性,支持多实现,解决单继承限制。
- 访问权限:public、private、protected、default的作用范围决定了继承中成员的可访问性,是封装特性的重要体现。
通过动手实践,深入理解了Java面向对象的核心机制,为后续开发中合理设计类结构、灵活运用多态奠定了基础。
浙公网安备 33010602011771号