十、Java面向对象编程入门指南:继承与多态
面向对象编程(OOP)有三大核心特性:封装、继承和多态。
本节主要讲解的是继承与多态,其中继承和多态是实现代码复用和灵活性的关键。
补充:其封装知识点博客在——九、Java类核心语法:构造器、this、封装与static详解
目录
一、继承(Inheritance)
1. 什么是继承?
2. 为什么需要继承?
3. 基本语法
4. 继承示例
5. 继承的权限修饰符
6. 继承后的特点
7. 继承的方法重写(Override)及应用场景
8. 子类构造器的特点及应用场景
9. 继承中构造器的 `this()` 调用(兄弟构造器)
二、多态(Polymorphism)
1. 认识多态
2. 多态的好处和存在的问题
3. 多态的类型转换问题
总结
一、继承(Inheritance)
1. 什么是继承?
- 继承是一种类与类之间的关系,允许一个类(子类)"继承"另一个类(父类)的属性和方法,同时可以添加自己的独特功能。
2. 为什么需要继承?
- 减少重复代码:多个类有共同属性时,只需在父类定义一次
- 建立类的层级关系:使代码结构更清晰
- 为多态奠定基础
3. 基本语法
使用 `extends` 关键字实现继承:
// 父类:定义共性属性和方法
class 父类名 {属性;方法;
}// 子类:继承父类,并可扩展新功能
class 子类名 extends 父类名 {// 新增属性和方法(扩展)// 重写父类方法(修改)
}
4. 继承示例
// 父类:动物(共性)
class Animal {String name;void eat() {System.out.println(name + "在吃东西");}
}// 子类:狗(继承共性,添加个性)
class Dog extends Animal {void bark() { // 新增方法System.out.println(name + "汪汪叫");}
}// 使用
public class Test {public static void main(String[] args) {Dog dog = new Dog();dog.name = "旺财"; // 继承父类的属性dog.eat(); // 继承父类的方法dog.bark(); // 子类自己的方法}
}
5. 继承的权限修饰符
用来限制类中的成员(成员变量,成员方法,构造器)能够被访问的范围:
public
:任意位置protected
:本类,同一个包中的类,子孙类中- 默认(缺省):本类,同一个包中的类
private
:只能本类
关键说明:
- `private`:父类私有成员,子类完全不可见(即使同包),需通过父类的 `public/protected` 方法间接访问。
- `protected`:专为继承设计,允许不同包的子类访问(体现“继承可见性”)。
示例:
class Parent {private int a = 1; // 子类不可见int b = 2; // 同包子类可见,不同包不可见protected int c = 3; // 所有子类可见public int d = 4; // 所有类可见
}class Child extends Parent {void test() {// System.out.println(a); // 错误:private不可见System.out.println(b); // 同包时可见System.out.println(c); // 所有子类可见System.out.println(d); // 可见}
}
6. 继承后的特点
1)单继承限制:Java 只允许一个子类有一个直接父类,但支持多层继承(如 `A → B → C`,C 是 B 的子类,B 是 A 的子类),其Object类是Java中所有类的祖宗类。
public class Test {public static void main(String[] args) {//目标:继承的注意事项和特点。A a=new A();}
}//1、java的类只能是单继承,不支持多继承,但支持多层继承。
//2、一个类要么直接继承Object,要么默认继承Object。
class A extends Object{
}
class B extends A{
}
class C extends B{
}
2)子类拥有父类非私有成员:子类可直接使用父类的 `public/protected/默认(同包)` 成员,但不能修改父类的私有成员。
3)就近原则:子类没有找子类,子类没有找父类,父类没有就报错。
public class Test2 {//目标2:继承后子类的访问特点:就近原则。public static void main(String[] args) {Zi zi=new Zi();zi.show();}
}class Fu{String name="Fu的name";public void run(){System.out.println("Fu的run()");}
}class Zi extends Fu{String name="Zi的name";public void show(){String name="show()的name";System.out.println(name);//show()的name(就近原则)System.out.println(this.name);//Zi的name(当前类的对象)System.out.println(super.name);//Fu的name(父类的对象)run();//子类的run ()super.run();//父类的run()}public void run(){System.out.println("Zi的run()");}
}
7. 继承的方法重写(Override)及应用场景
- 定义:子类对父类的方法进行重新实现(方法名、参数列表、返回值完全相同),以满足子类的特有需求。
重写规则:
- 方法名、参数列表(类型、顺序、数量)必须与父类完全一致。
- 返回值类型:必须与被重写方法的返回值类型一样,或范围更小。
- 访问权限:子类方法权限不能低于父类(如父类是 `protected`,子类可改为 `public`)。
- 异常:子类方法抛出的异常不能多于/严于父类。
- 注解:建议添加 `@Override`(编译期检查是否符合重写规则,避免拼写错误)。
- 私有方法和静态方法不能被重写。
示例:
public class Test {public static void main(String[] args) {//目标:认识方法重写和应用场景。Cat cat = new Cat();cat.cry();}
}
class Cat extends Animal{//方法重写:方法名称,形参列表必须相同时才可以进行方法重写。//方法重写规范:声明不变,重新实现。//方法重写的校验注解(标志):@Override//要求方法名称和形参列表必须与被重写的方法一致,否则报错!//更安全,可读性、可维护性更高@Overridepublic void cry() {System.out.println("小猫喵喵叫");}
}
class Animal {public void cry() {System.out.println("动物叫");}
}
应用场景:
- 当父类的方法实现不满足子类需求时,通过重写定制子类行为。
- 子类重写Object类的toString()方法,以便返回对象的内容。
示例:
public class Test2 {public static void main(String[] args) {//目标:方法重写的常见应用场景://子类重写Object的toString方法,以便获取对象信息Student s=new Student("彭于晏",18,'男');//System.out.println(s.toString());默认打印对象所在地址System.out.println(s);//注意1:直接输出对象,默认会自动调用toString方法(可省不写),返回对象地址信息。//注意2:输出对象的实际地址无意义,要输出对象信息,需要重写Object的toString方法。}
}class Student{private String name;private int age;private char sex;@Overridepublic String toString() {return "Stuent{" +"name='" + name + '\'' +", age=" + age +", sex=" + sex +'}';}public Student() {}public Student(String name, int age, char sex) {this.name = name;this.age = age;this.sex = sex;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public char getSex() {return sex;}public void setSex(char sex) {this.sex = sex;}
}
小窍门:快速生成toString()方法
1)右键单击鼠标->生成
2)点击toString()
3)选中所有添加的->点击确定即可
`@Override`(编译期检查是否符合重写规则,避免拼写错误)
8. 子类构造器的特点及应用场景
- 核心特点:子类构造器必须先调用父类构造器,再执行自己的逻辑。
- 构造器执行顺序:父类构造器 → 子类构造器(确保父类成员先初始化,子类可安全使用)。
- 默认调用:若子类构造器中未显式调用父类构造器,编译器会自动添加 `super()`(调用父类无参构造)。
- 显式调用:若父类没有无参构造器(如父类只定义了有参构造),子类必须显式用 `super(参数)` 调用父类的有参构造,且 `super(参数)` 必须放在子类构造器的第一行。
示例:
public class Test {public static void main(String[] args) {//目标:认识子类构造器的特点和应用场景。//子类构造器都会必须先调用父类构造器,再继续执行子类构造器的代码。Zi zi=new Zi();}
}class Zi extends Fu {public Zi(){super();//默认存在的(父类无参构造器)//super(666);指定调用父类有参构造器System.out.println("子类无参构造器");}
}class Fu {public Fu(){System.out.println("父类无参构造器");}public Fu(int a){System.out.println("父类有参构造器");}
}
子类构造器调用父类构造器的应用场景:
//子类
public class Teacher extends People{private String skill;public Teacher() {}public Teacher(String name, int age, char sex, String skill) {//子类构造器调用父类构造器应用场景。//可以把子类继承父类这部分的数据也完成初始化赋值。super(name, age, sex);this.skill = skill;}public String getSkill() {return skill;}public void setSkill(String skill) {this.skill = skill;}
}
9. 继承中构造器的 `this()` 调用(兄弟构造器)
`this()` 用于在同一个类的构造器中调用其他构造器(兄弟构造器),目的是减少代码重复。
规则:
- `this()` 必须放在构造器的第一行。
- `this()` 与 `super()` 不能同时使用(两者都需在第一行)。
示例:
public class Student {private String name;private int age;private char sex;private String schoolName;public Student() {}public Student(String name, int age, char sex) {//this调用兄弟构造器。//注意:super()和this()不能同时使用,并且都需要写在构造器的第一行。this(name, age, sex, "清华大学");this.name = name;this.age = age;this.sex = sex;}public Student(String name, int age, char sex, String schoolName) {super();//必须写在构造方法第一行this.name = name;this.age = age;this.sex = sex;this.schoolName = schoolName;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", sex=" + sex +", schoolName='" + schoolName + '\'' +'}';}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public char getSex() {return sex;}public void setSex(char sex) {this.sex = sex;}public String getSchoolName() {return schoolName;}public void setSchoolName(String schoolName) {this.schoolName = schoolName;}
}
二、多态(Polymorphism)
1. 认识多态
定义:
- 多态指“同一行为,不同实现”:同一方法调用,因对象不同而产生不同结果。
- 多态是在继承/实现情况下的一种现象,表现为:对象多态,行为多态。
实现条件:
- 存在继承关系(子类继承父类)。
- 子类重写父类的方法。
- 向上转型:父类引用指向子类对象(`父类类型 变量 = new 子类类型()`)。
示例:
class Animal {void makeSound() {System.out.println("动物叫");}
}class Dog extends Animal {@Overridevoid makeSound() {System.out.println("汪汪叫");}
}class Cat extends Animal {@Overridevoid makeSound() {System.out.println("喵喵叫");}
}public class Test {public static void main(String[] args) {// 向上转型:父类引用指向不同子类对象Animal a1 = new Dog();Animal a2 = new Cat();a1.makeSound(); // 输出:汪汪叫(实际调用Dog的方法)a2.makeSound(); // 输出:喵喵叫(实际调用Cat的方法)}
}
多态的两种形式:
- 编译时多态:方法重载(Overload):同一类中方法名相同,参数不同(编译期确定调用哪个方法)。
- 运行时多态:方法重写(Override)配合向上转型(运行期确定调用哪个方法,OOP 核心多态)。
2. 多态的好处和存在的问题
好处:
- 代码扩展性:新增子类时,无需修改依赖父类的代码。
- 代码灵活性:用统一的父类引用处理不同子类对象,简化逻辑。
- 解耦合:右边对象是解耦合的。
存在的问题:
- 无法直接调用子类特有方法:父类引用只能调用父类中定义的方法,子类特有方法需转型后才能调用。
- 属性无多态性:多态只针对方法,属性的值由编译时类型(左边)决定。
- 可能抛出转型异常:向下转型时若类型不匹配,会抛出 `ClassCastException`。
示例:
public class Test {public static void main(String[] args) {//目标:认识多态的代码。//1、多态的好处:右边对象是解耦合的。Animal a1 = new Tortoise();a1.run();//方法:编译看左边,运行看右边//a1.shinkHead();//多态下不能调用子类独有功能。(除了强制转换)Wolf w = new Wolf();go(w);Tortoise t = new Tortoise();go(t);}//宠物游戏:所有动物都可以用这个方法开始跑步。//2、多态的好处:父类类型的变量作为参数,可以接受一个子类对象。public static void go(Animal a) {System.out.println("开始了!");a.run();//a1.shinkHead();//报错,多态下不能调用子类独有功能。}
}
3. 多态的类型转换问题
多态中的类型转换分为向上转型和向下转型,目的是在“统一处理”和“使用子类特有功能”之间切换。
1)向上转型(自动转换):
- 语法:`父类类型 变量 = new 子类类型();`
- 特点:安全(子类是父类的一种),但会“丢失”子类特有方法(只能调用父类定义的方法)。
- 示例:`Animal a = new Dog();`(正确,无需显式转换)。
2)向下转型(强制转换):
- 语法:`子类类型 变量 = (子类类型) 父类引用;`
- 特点:需显式转换,用于恢复子类特有方法,但可能因类型不匹配抛出 `ClassCastException`。
- 解决:先用 `instanceof` 判断类型是否匹配(返回 `true` 则安全转换)。
示例:
public class Test {public static void main(String[] args) {//目标:认识多态的代码。//1、多态的好处:右边对象是解耦合的。Animal a1 = new Tortoise();a1.run();//方法:编译看左边,运行看右边//a1.shinkHead();//多态下不能调用子类独有功能。(除了强制类型转换)//强制类型转换:可以解决多态下调用子类独有功能。Tortoise t = (Tortoise)a1;t.shrinkHead();//有继承关系就可以强制类型转换,编译阶段不会报错!//运行时会出现类型转换异常:ClassCastException//Wolf w = (Wolf)a1;// Wolf w = new Wolf();
// go(w);
//
// Tortoise t = new Tortoise();
// go(t);}//宠物游戏:所有动物都可以用这个方法开始跑步。//2、多态的好处:父类类型的变量作为参数,可以接受一个子类对象。public static void go(Animal a) {System.out.println("开始了!");a.run();//a1.shinkHead();//报错,多态下不能调用子类独有功能。//java建议强制类型转换前,应该判断对象的真实类型:对象 instanceof 类型,再进行强制类型转换。if(a instanceof Tortoise) {Tortoise t = (Tortoise)a;t.shrinkHead();}else if(a instanceof Wolf) {Wolf w = (Wolf)a;w.eatSheep();}}
}
总结
- 继承:通过 `extends` 实现代码复用,需注意权限修饰符和构造器调用规则。
- 多态:基于继承和重写,通过向上转型实现“同一行为不同表现”,提升代码扩展性,但需注意类型转换问题。