【Java基础知识 19】继承
文章目录
- 一、继承的概念
- 1. 核心定义
- 2. 实现方式
- 3. 代码示例
- 二、权限修饰符
- 1.定义
- 2.代码示例
- 3.实际开发中的使用规范
- 三、继承的特点
- 1.单继承
- 2.祖宗类(Object)
- 3.就近原则
- 四、方法重写(Override)
- 1. 基本概念
- 2. 重写的核心规则
- (1)方法签名必须完全一致
- (2)访问权限不能降低
- (3)返回值类型限制
- (4)特殊方法不能重写
- 3. 常见应用场景
- 4. 重写的意义
- 五、子类构造器的特点
- 1. 子类构造器调用父类构造器
- (1)默认调用:隐式调用父类无参构造器
- (2)特殊情况:显式调用父类有参构造器
- 2. 核心原则
- 3. 实际应用:一次性初始化所有数据
- 4.总结
- 5. 补充:this(...)调用兄弟构造器
一、继承的概念
继承是面向对象编程(OOP)的三大核心特性之一,它允许一个类(称为子类)继承另一个类(父类)的属性和方法,从而实现代码的复用和扩展,是构建类层次结构的重要手段。
1. 核心定义
- 父类:被继承的类,也称为基类或超类。它通常集中了多个子类共有的属性和方法,是对一类事物的抽象定义。
- 子类:继承父类的类。它可以继承父类的所有非私有成员(成员变量、成员方法),同时还能添加自己特有的属性和方法,或重写父类的方法。
- 继承后子类的对象的创建由子类和父类共同完成,因此子类对象包含了完整的属性和方法集。
2. 实现方式
在 Java 中,使用extends
关键字实现继承,语法结构如下:
// 父类
class 父类名 {// 父类的属性和方法
}// 子类,继承父类
class 子类名 extends 父类名 {// 子类特有的属性和方法// 也可以重写父类的方法
}
3. 代码示例
场景:创建一个表示 “人” 的父类,以及继承自 “人” 的 “教师” 和 “咨询师” 两个子类,展示继承的实际应用。
package demo;//父类:人类(提取共性属性)
public class People {// 私有属性(子类无法直接访问,通过公共方法访问)private String name; // 姓名private char sex; // 性别// 公共getter/setter方法(提供属性访问接口)public String getName() {return name;}public void setName(String name) {this.name = name;}public char getSex() {return sex;}public void setSex(char sex) {this.sex = sex;}
}
package demo;//咨询师类:子类(继承自People类)
public class ZiXunShi {private int number;// 特有属性的getter/setterpublic int getNumber() {return number;}public void setNumber(int number) {this.number = number;}
}
package demo;// 教师类:子类(继承自People类)
public class Teacher extends People {private String subject; // 教授科目(特有属性)// 特有属性的getter/setterpublic String getSubject() {return subject;}public void setSubject(String subject) {this.subject = subject;}
}
package demo;public class TestInheritance {public static void main(String[] args) {// 测试教师类Teacher teacher = new Teacher();// 调用继承自父类的方法teacher.setName("张教授");teacher.setSex('男');// 调用子类特有方法teacher.setSubject("计算机科学");System.out.println("教师信息:");System.out.println("姓名:" + teacher.getName() + ",性别:" + teacher.getSex() + ",教授科目:" + teacher.getSubject());// 测试咨询师类ZiXunShi consultant = new ZiXunShi();// 调用继承自父类的方法consultant.setName("李咨询师");consultant.setSex('女');// 调用子类特有方法consultant.setConsultNumber(2023001);System.out.println("\n咨询师信息:");System.out.println("姓名:" + consultant.getName() + ",性别:" + consultant.getSex() + ",咨询编号:" + consultant.getConsultNumber());}
}
二、权限修饰符
1.定义
权限修饰符是用于限制类中成员(包括成员变量、成员方法、构造器等)访问范围的关键字,通过控制访问权限实现面向对象的封装特性。
private
:只能在当前类中直接访问无访问修饰符(缺省)
:当前包中访问protected
:当前包及子类、子孙类中访public
:任意类中访问(每个类中只能有一个被public
修饰的类,即public修饰的类名要成为文件名)
2.代码示例
以下通过多个类的交互示例,直观展示不同权限修饰符的访问规则:
父类定义(包:demo)
package demo;public class Fu {//priavate:仅当前类中访问private void privateMethod(){System.out.println("privateMethod");}// 无修饰符(缺省):当前包中访问void Method(){System.out.println("Method");}//protected:当前包及子类、孙子类中访问protected void protectedMethod(){System.out.println("protectedMethod");}//public:任意类中访问public void publicMethod(){System.out.println("publicMethod");}public static void main(String[] args) {Fu fu = new Fu();fu.privateMethod(); // 允许:当前类内部fu.defaultMethod(); // 允许:当前类内部fu.protectedMethod(); // 允许:当前类内部fu.publicMethod(); // 允许:当前类内部}
}
同包测试类(包:demo)
package demo;public class Test {public static void main(String[] args) {Fu fu = new Fu();// fu.privateMethod(); // 错误:private仅限当前类访问fu.defaultMethod(); // 允许:同包访问fu.protectedMethod(); // 允许:同包访问fu.publicMethod(); // 允许:任意访问}
}
不同包普通类(包:demo1)
package demo1;import demo.Fu;public class Demo1 {public static void main(String[] args) {Fu fu = new Fu();// fu.privateMethod(); // 错误:private仅限当前类// fu.defaultMethod(); // 错误:缺省权限仅限同包// fu.protectedMethod();// 错误:protected在不同包非子类中不可访问fu.publicMethod(); // 允许:public权限无限制}
}
不同包子类(包:demo1)
package demo1;import demo.Fu;// 子类继承自不同包的父类Fu
public class Zi extends Fu{//在其他包的子类中访问public void show(){// privateMethod(); // 错误:子类无法访问父类private成员// defaultMethod(); // 错误:缺省权限仅限同包protectedMethod(); // 允许:子类可访问父类protected成员publicMethod(); // 允许:public权限无限制}
}
3.实际开发中的使用规范
在日常开发中,权限修饰符的使用应遵循 "最小权限原则"(尽可能限制访问范围),常用规范如下:
- 成员变量:通常使用 private 修饰,通过公共的 getter/setter 方法控制访问,实现数据封装
- 成员方法:根据需要选择权限,对外提供的接口方法用 public,仅内部使用的辅助方法用 private 或 protected
- 构造器:根据实例化需求设置权限,如需限制外部创建对象可使用 private(如单例模式)
- 缺省权限:尽量避免使用,除非明确需要同包内访问但不允许其他包访问的场景
合理使用权限修饰符可以提高代码的安全性、可维护性,同时明确类的对外接口和内部实现的边界。
三、继承的特点
在 Java 中,继承作为面向对象编程的核心特性,具有以下几个显著特点:
1.单继承
- Java 中一个类只能直接继承一个父类(使用
extends
关键字),即不支持多继承。 - 但支持多层继承(传递性):子类可以被其他类继承,形成类的层次结构。
例如:A → B → C
(C 继承 B,B 继承 A,因此 C 间接继承 A 的非私有成员)。
package demo;public class Test {public static void main(String[] args) {}
}//Java的类只能是单继承的,不能多继承,支持多层继承class A{}class B extends A{}class C extends B{}
单继承机制避免了多继承可能导致的方法冲突问题,使类层次结构更清晰。
2.祖宗类(Object)
- Java 中所有类默认直接或间接继承
java.lang.Object
类,Object
是所有类的顶级父类。 Object
类的方法(如toString()
、equals()
等)可被所有类继承和重写。
package demo;public class Test {public static void main(String[] args) {A a = new A();B b = new B();C c = new C();// 以下方法均继承自 Object 类System.out.println(a.toString());System.out.println(b.equals(c));System.out.println(c.hashCode());}
}//一个类要么直接继承Object,要么默认继承Object,要么间接继承Object//直接继承Objectclass A extends Object{}//默认继承Object(未显式声明 extends)class B{}//间接继承Object(通过继承 B)class C extends B{}
Object
类为所有 Java 对象提供了统一的方法基础,是 Java 面向对象体系的根基。
3.就近原则
子类方法中访问成员(成员变量、成员方法)时,遵循就近原则:
- 先在当前方法的局部范围查找
- 若未找到,在子类的成员范围查找
- 若仍未找到,在父类的成员范围查找
- 若最终未找到,则编译报错
当子父类中出现同名成员时,会优先使用子类的成员。若需在子类中明确访问父类的成员,可使用`super`关键字(代表父类对象的引用):
super.父类成员变量
:访问父类的成员变量super.方法名()
:调用父类的成员方法
package demo;public class Test {public static void main(String[] args) {Zi zi = new Zi();zi.show();}
}//就近原则
class Fu{String name = "父类的name";public void run(){System.out.println("父类的run方法");}
}class Zi extends Fu{String name ="子类的name";public void show(){String name ="show方法的局部name";// 访问变量的就近原则演示System.out.println(name); // 局部变量:show方法的局部nameSystem.out.println(this.name); // 子类成员变量:子类的nameSystem.out.println(super.name); // 父类成员变量:父类的name// 访问方法的就近原则演示run(); // 子类方法:子类的run方法(就近)super.run(); // 父类方法:父类的run方法(通过super指定)}public void run(){System.out.println("子类的run方法");}
}
就近原则确保了子类可以优先使用自身的实现,而super
关键字则为访问父类成员提供了明确途径,二者结合使继承体系中的成员访问更加灵活可控。
四、方法重写(Override)
方法重写是继承体系中实现多态的核心机制,指子类根据自身需求,重新实现父类中已有的方法,以覆盖父类的原始实现。
1. 基本概念
当子类认为子类认为父类的某个方法实现不符合自身需求时,可以定义一个与父类方法名、参数列表完全相同的方法,从而覆盖父类的该方法,这就是方法重写(也称为方法覆盖)。
package demo;public class Test {public static void main(String[] args) {Animal animal = new Animal();animal.cry(); // 调用父类方法Cat cat = new Cat();cat.cry(); // 调用重写后的子类方法}
}// 父类:动物
class Animal{// 父类的方法public void cry(){System.out.println("动物会叫~~~");}
}// 子类:猫
class Cat extends Animal{//方法重写:覆盖父类的cry()方法,方法名称、形参列表必须一致@Override//方法重写的校验注解(标志注解),校验是否符合重写规则,不符合会编译报错public void cry(){System.out.println("🐱小猫会喵喵喵~~~");}
}
2. 重写的核心规则
(声明不变,重新实现)
(1)方法签名必须完全一致
- 方法名、参数列表(参数类型、个数、顺序)必须与父类被重写方法完全相同
- 仅返回值不同或仅参数名不同,都不属于重写(会被视为重载或错误定义)
(2)访问权限不能降低
- 子类重写方法的访问权限必须大于或等于父类方法的权限
- 权限等级:
public
>protected
>缺省(默认)
>private
- 示例:父类方法为
protected
,子类重写时只能是protected
或public
(3)返回值类型限制
子类重写方法的返回值类型限制必须等于父类或小于父类
(4)特殊方法不能重写
private
修饰的方法:父类私有方法对子类不可见,无法重写static
修饰的方法:属于类级别的方法,重写会被视为子类定义了新的静态方法(不是重写)final
修饰的方法:final
方法是 “不可修改” 的,禁止重写
3. 常见应用场景
经典场景:子类重写Object
类的toString()
方法,以便返回对象的内容
package demo;public class Test {public static void main(String[] args) {//子类重写Object类的toString()方法Student s = new Student("张三", 18, '男');System.out.println(s);//直接输出对象,默认会调用Object的toString()方法,返回对象的地址信息// System.out.println(s.toString());//输出对象的地址实际没有意义,希望输出的是对象的内容信息,所以子类需要重写toString()方法// 以便以后输出对象时默认就近调用子类的toString()方法返回对象的内容信息}
}class Student{private String name;private int age;private char 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()方法 //也可以使用快捷键alt+ins,选择toString()@Overridepublic String toString() {return "Student{name="+name+",age="+age+",sex="+sex+"}";}
}
4. 重写的意义
- 实现多态:通过父类引用指向子类对象时,能调用子类的重写方法,体现 “一个接口,多种实现”
- 功能扩展:在保留父类方法定义的同时,实现子类特有的功能逻辑
- 符合开闭原则:无需修改父类代码,通过重写扩展功能,提高代码稳定性
方法重写是 Java 实现动态绑定的基础,也是面向对象编程中 “继承 - 扩展” 思想的具体体现,合理使用重写可以使代码既保持一致性又具备灵活性。
五、子类构造器的特点
在 Java 继承里,创建子类对象时,必须先初始化父类的部分,再初始化子类自己的部分—— 也就是说,子类的所有构造器,都会先调用父类的构造器,再执行自己的代码。
1. 子类构造器调用父类构造器
(1)默认调用:隐式调用父类无参构造器
如果子类构造器里没写调用父类的代码,Java 会自动在子类构造器的第一行加一句super()
,这行代码会调用父类的 “无参构造器”。
package demo;public class Test {public static void main(String[] args) {// 造子类对象时,先执行父类构造器,再执行子类构造器Zi zi = new Zi();}
}// 父类
class Fu {// 父类无参构造器public Fu() {System.out.println("父类无参构造器执行(初始化父类内容)");}
}// 子类
class Zi extends Fu {// 子类无参构造器(没写调用父类的代码)public Zi() {// 这里会自动加:super() → 调用父类无参构造器System.out.println("子类无参构造器执行(初始化子类内容)");}
}
(2)特殊情况:显式调用父类有参构造器
如果父类没有无参构造器(比如父类只写了带参数的构造器),那子类里自动加的super()
就会报错 —— 因为找不到父类的无参构造器。
这时候必须手动在子类构造器的第一行写super(参数)
,指定调用父类的 “有参构造器”。
package demo;public class Test {public static void main(String[] args) {Zi zi = new Zi();}
}// 父类(只有有参构造器,没有无参构造器)
class Fu {public Fu(String info) {System.out.println("父类有参构造器执行:" + info);}
}// 子类
class Zi extends Fu {public Zi() {// 手动写super(参数),调用父类有参构造器(必须在第一行)super("给父类传的参数");System.out.println("子类构造器执行");}
}
2. 核心原则
super()
或super(参数)
必须放在子类构造器的第一行—— 先初始化父类,再初始化子类,顺序不能乱。
3. 实际应用:一次性初始化所有数据
子类构造器调用父类构造器,能把 “继承自父类的属性”(比如姓名、年龄)和 “子类自己的属性”(比如技能)一次性初始化,不用分两次赋值。
package demo;// 父类:人类(有姓名、年龄两个属性)
class People {private String name;private int age;// 父类有参构造器(初始化姓名、年龄)public People(String name, int age) {this.name = name;this.age = age;}// getter方法(让子类能获取属性值)public String getName() { return name; }public int getAge() { return age; }
}// 子类:教师(多了“技能”属性)
class Teacher extends People {private String skill;// 子类有参构造器:初始化父类属性+子类属性public Teacher(String name, int age, String skill) {// 1. 调用父类构造器,先初始化姓名、年龄super(name, age);// 2. 再初始化子类自己的技能this.skill = skill;}// getter方法public String getSkill() { return skill; }
}// 测试
public class Test1 {public static void main(String[] args) {// 造教师对象时,一次传完所有参数Teacher t = new Teacher("李四", 36, "Java");// 直接获取所有属性值System.out.println("姓名:" + t.getName());System.out.println("年龄:" + t.getAge());System.out.println("技能:" + t.getSkill());}
}
4.总结
子类构造器的核心逻辑:
1.先通过super()
(自动)或super(参数)
(手动)调用父类构造器,初始化父类内容;
2. 再执行子类自己的构造器代码,初始化子类内容;
3. 最终得到一个完整的子类对象。
5. 补充:this(…)调用兄弟构造器
在一个类中,有时会定义多个构造器(重载的构造器),它们之间可以通过this(…)相互调用,减少重复代码。
核心规则:
this(...)
用于在当前构造器中调用同类的其他构造器(兄弟构造器)this(...)
必须放在构造器的第一行this(...)
和super(...)
不能同时使用(两者都要占第一行位置)
package demo2;public class Student {private String name;private int age;private char sex;private String SchoolName;@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", sex=" + sex +", SchoolName='" + SchoolName + '\'' +'}';}public Student(String name, int age, char sex) {
// this.name = name;
// this.age = age;
// this.sex = sex;
// this.SchoolName = "清华";//this调兄弟构造器(初始化,将学校固定为清华)//注意:super(...) this(...)都必须写在构造器的第一行,且两者不能同时出现this(name, age, sex, "清华");}public Student() {}public Student(String name, int age, char sex, String schoolName) {this.name = name;this.age = age;this.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) {SchoolName = schoolName;}
}
package demo2;public class Test {public static void main(String[] args) {//this(...)调用兄弟构造器//创建对象,存储一个学生的数据Student s = new Student("张三", 18, '男', "蓝翔");Student s1 = new Student("小红", 20, '女', "清华");Student s2 = new Student("小蓝",25, '男');System.out.println(s);System.out.println(s1);System.out.println(s2);}
}
作用:
- 减少重复代码:把相同的初始化逻辑写在一个构造器中,其他构造器通过this(…)调用,避免代码冗余
- 统一初始化入口:核心初始化逻辑集中管理,便于维护和修改