- 实验代码
// 祖父类
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,不提供setterpublic 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()方法@Overridepublic 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访问权限,抛出RuntimeExceptionpublic 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,合法)@Overridepublic 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 parentMethodco.callParentMethod(); // 调用父类方法,输出:Parent parentMethod// 静态方法:调用取决于变量类型,非对象类型(非多态)ParentOverride po = new ChildOverride();ParentOverride.staticMethod(); // 输出:Parent staticMethodChildOverride.staticMethod(); // 输出:Child staticMethodpo.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; // 隐藏父类字段@Overridepublic 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=100System.out.println("Parent myValue: " + parent.myValue); // 输出:Parent myValue: 100// 2. 子类对象调用方法和字段ChildPolymorphic child = new ChildPolymorphic();child.printValue(); // 输出:Child.printValue(), myValue=200System.out.println("Child myValue: " + child.myValue); // 输出:Child myValue: 200// 3. 父类引用指向子类对象(多态)ParentPolymorphic poly = new ChildPolymorphic();poly.printValue(); // 方法调用取决于对象真实类型(子类),输出:Child.printValue(), myValue=200System.out.println("Poly myValue: " + poly.myValue); // 字段访问取决于变量类型(父类),输出:Poly myValue: 100// 4. 修改父类字段(通过父类引用)poly.myValue++;System.out.println("After poly.myValue++, poly.myValue: " + poly.myValue); // 输出:101poly.printValue(); // 子类方法访问的是子类字段,输出:Child.printValue(), myValue=200// 5. 强制类型转换后访问子类字段((ChildPolymorphic) poly).myValue++;System.out.println("After cast, Child myValue: " + ((ChildPolymorphic) poly).myValue); // 输出:201poly.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)); // trueSystem.out.println("obj1 instanceof Mammal: " + (obj1 instanceof Mammal)); // trueSystem.out.println("obj2 instanceof Cat: " + (obj2 instanceof Cat)); // trueSystem.out.println("obj2 instanceof Dog: " + (obj2 instanceof Dog)); // falseSystem.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 {@Overridepublic void eat() {System.out.println("狮子吃鲜肉");}
}// 猴子类(继承Animal)
class MonkeyV2 extends Animal {@Overridepublic void eat() {System.out.println("猴子吃香蕉");}
}// 鸽子类(继承Animal)
class PigeonV2 extends Animal {@Overridepublic 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 {@Overridepublic 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;}// 实现父类的抽象方法@Overridepublic 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 finalString 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 {@Overridepublic void cook() {System.out.println("鸭子可以烹饪(如烤鸭)");}@Overridepublic 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面向对象的核心机制,为后续开发中合理设计类结构、灵活运用多态奠定了基础。