继承(extends)
基本介绍
为什么需要继承?
当不同的类中有相同的属性或者方法的时候可以避免重复定义,提高代码的阅读性和复用性
关系结构
(1) 父类(基类,超类)
(2) 子类(派生类)
(3) 子类继承父类的属性和方法,子类又可以当作父类继续继承子类
代码示例
java
// 父类 A
package extend;
public class A {
String name;
int age;
double height;
// 声明无参构造器
public A() {
}
// 构造器的声明
public A(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
// 设置属性值
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setHeight(double height) {
this.height = height;
}
public void printinfo() {
System.out.println("A类的print函数被调用");
System.out.print("姓名:" + name + "\n年龄:" + age + "\n身高:" + height);
}
}
// 子类 B 继承 父类 A
package extend;
public class B extends A {
// B 继承 A类, 此时 B 拥有 A 的所有属性和方法,不同的地方重新声明即可,
// 共有的部分:直接调用即可
@Override
public void printinfo() {
System.out.println("B类的print函数被调用");
System.out.print("姓名:" + name + "\n年龄:" + age + "\n身高:" + height);
}
}
// 主函数中调用
package extend;
public class test {
public static void main(String[] args) {
A a = new A();
a.setName("jackson");
a.setAge(18);
a.setHeight(1.68);
a.printinfo();
System.out.println("\n");
B b = new B();
b.setName("bob");
b.setAge(22);
b.setHeight(1.73);
b.printinfo();
}
}
// 输出结果
A类的print函数被调用
姓名:jackson
年龄:18
身高:1.68
B类的print函数被调用
姓名:bob
年龄:22
身高:1.73代码分析
(1)使用了继承避免了在 B 类中重复定义相同的属性和方法
(2)B 类继承 A 类后, B 类拥有 A 类的所有属性和方法,是共用的
(3)B 类中修改了 print 函数的提示信息,实则上如果没有提示信息,B 类中可以不写任何内容
小结
子类继承父类后,子类拥有父类的所有属性和方法,如果子类有特定的属性或者方法,在子类中声明即可
使用细节
(1) 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法不能在子类直接访问,要通过父类提供的公共方法,然后访问
(2) 子类必须调用父类的构造器,完成父类的初始化,之后才会调用子类的构造器完成子类的初始化
(3) 当创建子类对象时,无论调用子类的哪个构造器,默认情况下总会调用父类的无参构造器。如果父类没有提供无参构造器,子类必须在构造器中显式的用 super 指定调用父类的哪个构造器来完成父类的初始化工作,否则编译报错
(4) 如果希望指定去调用父类的某个构造器,则显式的调用:super(参数列表)
(5) super 在使用时,必须放在构造器第一行,super 只能在构造器中使用
(6) super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
(7) java 所有类都是 Object 类的子类,Object 是所有类的父类
(8)调用父类构造器不止于直接父类,如果父类还有父类,则会一直向上追溯,直到 Object 类(顶级父类)
(9)子类最多只能继承一个父类(指直接继承),即 Java 中是单继承机制
注意:不能滥用继承,子类和父类之间必须满足 is - a 的逻辑关系
(10)可以通过间接的方式实现多继承
例如:A 继承了 B,B 继承了 C,就可以间接的实现 A 继承 B 和 C
代码示例
java
// 父类 A
package extend1;
public class A {
String name;
private int age;
public A(){
}
public A(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public double getage(){
return this.age;
}
public void printinfo() {
System.out.println("A类的print函数被调用");
System.out.print("姓名:" + name + "\n年龄:" + age);
}
}
// 子类 B 继承 父类 A
package extend1;
public class B extends A{
// age 是私有的,这个时候使用 getage方法访问 age
public void printinfo() {
System.out.println("B类的print函数被调用");
System.out.print("姓名:" + name + "\n年龄:" + getage());
}
}
// 主函数
package extend1;
public class test {
public static void main(String[] args) {
A a = new A();
a.setName("jackson");
a.setAge(18);
a.printinfo();
System.out.println("\n");
B b = new B();
b.setName("bob");
b.setAge(22);
b.printinfo();
}
}
// 输出结果
A类的print函数被调用
姓名:jackson
年龄:18
B类的print函数被调用
姓名:bob
年龄:22.0代码解析
(1)子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法不能在子类直接访问,要通过父类提供的公共方法去访问
(2)age 是 private 的,在子类 B 中无法访问,通过在父类中写入 getage 方法,通过继承来调用 getage 方法实现对 private age 属性的访问
继承的过程分析
对象创建好之后会建立查找关系
(1)首先看子类是否有该属性
(2)如果子类有这个属性,并且可以访问,则返回信息
(3)如果子类没有这个属性,就看父类有没有这个属性(如父类有该属性,并且可以访问,就返回信息)
(5) 如果父类没有就按(3)的规则,继续找上级父类,直到 Object…
java
public class pra {
public static void main(String[] args) {
Son son = new Son(); // 创建 Son 类的对象
System.out.println("son.name:" + son.name);
System.out.println("son.age:" + son.age);
System.out.println("son.hobby:" + son.hobby);
}
}
class GrandPa { // 爷爷类
String name = "大头爷爷";
String hobby = "钓鱼";
}
class Father extends GrandPa { // 父亲类继承爷爷类
String name = "大头爸爸";
int age = 39;
}
class Son extends Father { // 儿子类继承父亲类
String name = "大头儿子";
}
// 输出结果
son.name:大头儿子
son.age:39
son.hobby:钓鱼代码分析
son 没有 age 和 hobby 这两个属性,但是访问结果输出了,验证了继承中会寻找父类的属性,如果没有就找到上级父类,直到 object
super()
调用默认构造器
创建子类时,子类的构造器默认会先调用父类的无参构造器,之后调用自身的构造器
java
// 父类A
package extend1;
public class A {
public A(){
System.out.print("A类的无参构造器被调用\n");
}
}
// 子类B 继承 父类A
package extend1;
public class B extends A{
public B(){
// super默认存在,优先调用父类的构造器,之后才执行子类的构造器
super(); // 默认调用父类的无参构造器,如果没有,需要指定使用父类的哪个构造器
System.out.print("子类B的无参构造器被调用\n");
}
}
// 主类
package extend1;
public class test {
public static void main(String[] args) {
B b = new B();
}
}
// 输出结果
A类的构造器被调用
子类B的无参构造器被调用代码解析
创建子类 B 对象时,优先调用父类的无参构造器,输出后,再调用子类的构造器,如果父类的无参构造器被覆盖或者没有显示声明需要用 super 指定调用父类的构造器
调用指定构造器
java
// 父类A
package extend1;
public class A {
String name;
int age;
public A(){
System.out.println("A类的无参构造器被调用");
}
public A(String name) {
this.name = name;
System.out.println("A类中public A(String name)构造器被调用");
}
public A(int age) {
this.age = age;
System.out.println("A类中public A(int age)构造器被调用");
}
public A(String name, int age) {
this.name = name;
this.age = age;
System.out.println("A类中带有全部属性初始化的构造器被调用");
}
public void printinfo() {
System.out.println("A类的print函数被调用");
System.out.print("姓名:" + name + "\n年龄:" + age);
System.out.println("\n");
}
}
// 子类B
package extend1;
public class B extends A{
public B(){
}
public B(int age){
super(age);
System.out.println("子类B的无参构造器被调用");
}
public void printinfo() {
System.out.println("B类的print函数被调用");
System.out.print("姓名:" + name + "\n年龄:" + age);
System.out.println("\n");
}
}
// 主类
package extend1;
public class test {
public static void main(String[] args) {
/*
共有的属性
int age;
String name
对于 父类A 可以初始化 age/name/age,name
对于 子类B 可以初始化 age (使用super指定了)
*/
A a = new A("jackson",18);
a.printinfo();
// 测试 子类B 中的无参构造器
B b = new B();
b.printinfo();
// 测试 子类B 中用super调用主类中指定的构造器
B b1 = new B(18);
b1.printinfo();
}
}
// 输出结果
A类中带有全部属性初始化的构造器被调用
A类的print函数被调用
姓名:jackson
年龄:18
A类的无参构造器被调用
B类的print函数被调用
姓名:null
年龄:0
A类中public A(int age)构造器被调用
子类B的无参构造器被调用
B类的print函数被调用
姓名:null
年龄:18