面向对象特征之继承

思维导图

代码练习

public class ExtendTest {
    public static void main(String[] args) {
        // 子类对象可以赋值给父类变量,所以说子类是一种特殊的父类
//        Super s = new Base();

        // 每个类的根父类都是Object类,都可以调用Object类中的方法,如toString(),equals等
//        Super s1 = new Super();
//        System.out.println(s1.toString());
//        System.out.println(s1.equals(s));
//
//        Base b = new Base();
//        System.out.println(b.toString());
//        System.out.println(b.equals(b));

        // 子类构造器总会先调用父类构造器,所以先输出Super构造器打印,再输出Base构造器打印
        Base b = new Base();//Super Constructor//Base Constructor
        // 子类同名实例变量会隐藏继承自父类的实例变量
        // 所以子类对象访问同名实例变量,会输出子类定义的实例变量值
        System.out.println(b.desc);//Base {}
        // 子类中可以在实例方法中通过super关键字调用继承自父类的同名实例变量
        b.getSuperSameNameVar();//Super {}
        // 子类可以在实例方法中通过super关键字调用被重写的父类方法
        b.getSuperOverrideMet();//Super study Java

    }
}

// Java中如果一个类没有显式继承其他类,则默认继承自java.lang.Object类
class Super{
    private String name = "Super";
    public String desc = "Super";
    public final String FINAL_STR = "Super final var";

    {
        desc = "Super {}";
    }

    Super(){
        System.out.println("Super Constructor");
    }

    Super(String name,String desc){
        this.name = name;
        this.desc = desc;
    }

    public void study(){
        System.out.println("Super study Java");
    }

    // 父类的final方法无法被子类重写
    public final void test(){
        System.out.println("Super final method");
    }
}

// Java中类的继承是单继承(继承使用关键字extends),即一个类只有一个父类
// Java中类虽然只能由一个直接父类,但是可以由多个间接父类,比如Base还是Object类的子类
// 所以每个类的根父类都是Object类,都可以调用Object类中的方法,如toString(),equals等
class Base extends Super{
    // 子类可以继承父类的成员变量和方法,包括私有的。但是不能继承父类的构造器和初始化块

    public String desc = "Base";

    {
        desc = "Base {}";
    }

    Base(){
        // 如果没有在子类构造器中显式通过super调用父类构造器,则系统自动隐式调用super(),即隐式调用父类无参构造器
        System.out.println("Base Constructor");
    }

    Base(String name,String desc){
        // 子类构造器中可以通过super限定该构造器调用父类构造器逻辑完成继承自父类实例变量的初始化。
        // 子类构造器中通过super调用父类构造器必须定义再第一行
        super(name,desc);
        // 如果已经在子类构造器中使用super调用父类构造器,则不能再通过this调用本类重载构造器,否则编译报错
        // Call to 'this()' must be first statement in constructor body
        //this();
    }

    // 子类中可以在实例方法中通过super关键字调用继承自父类的同名实例变量
    public void getSuperSameNameVar(){
        System.out.println(super.desc);
    }

    // 子类可以再实例方法中通过super关键字调用被重写的父类方法
    public void getSuperOverrideMet(){
        super.study();
    }

    // @Override可以在编译期检测被注解的方法是否是重写方法
    @Override
    // 方法重写要求:
    // 发生在父子类间,
    // 子类重写方法的方法签名要和父类一致,
    // 子类重写方法的访问权限修饰符要比父类大或相等
    // 子类重写方法抛出的异常要比父类小或相等
    public void study(){
        System.out.println("Base study English");
    }

    @Override
    public String toString() {
        return "Base{" +
                "desc='" + desc + '\'' +
                '}';
    }

    // 子类不能重写父类的final方法,否则编译期异常
    // 'test()' cannot override 'test()' in 'extendsTest.Super'; overridden method is final
//    @Override
//    public void test(){
//        System.out.println("Base try to override Super final method");
//    }
}

思考题

1.Java的继承体系都是单继承吗?

不是。

Java的类的继承体系都是单继承。

但是

Java的接口的继承体系时多继承,即一个接口可以继承多个其他接口。

2.Java的类不是单继承吗,比如A继承B,但是B中并没有定义equals,toString等方法,为什么A类对象还是可以调用这些方法?

Java类的继承体系是单继承。即一个类只能由一个父类。

且继承发生后,子类可以继承父类的所有成员变量,成员方法,内部类,枚举类。

但是Java继承还有一个规则:
如果一个类没有显式继承其他类,则默认继承java.lang.Object类

举个例子

A extends B
B extends C
C extends D
D 没有显式继承任何其他类


分析:
由于D没有显式继承其他类,所以D默认继承Object类。所以D获得Object类的所有成员方法(equals,toString等)

而C继承了D,所以C也获得了(equals,toString)

而B继承C,所以B也获得了(equals,toString)

同理A最终也能获得(equals,toString)

总结:Java中类的继承是单继承,只能说明一个类只能由一个直接父类,但是可以由多个间接父类。

3.子类为什么不能继承父类的构造器和初始化块?

我们知道构造器的名字必须和类名相同。

而子类继承自父类的成员的名字不会改变。所以构造器不能被继承,因为继承自父类的构造器和子类类名冲突。

另外,初始化块在本质上是属于构造器的一部分。在代码编译后,初始化块就会消失,初始化块中的代码会融入到构造器中,且在原先构造器执行体代码前。

4.子类构造器中使用super关键字可以调用父类构造器,会导致产生父类对象吗?

不会。

在子类构造器中使用super调用父类构造器,只是为了给子类对象中(继承自父类的实例变量)完成初始化。不会产生父类对象。

5.子类不会继承父类的构造器和初始化块,我们知道构造器可以用super调用,那么父类的初始化块,子类怎么调用?

首先我们需要知道初始化块是无法被外界调用的,它只在类加载时(静态初始化块),或创建对象时(实例初始化块)执行一次。

另外我们知道  构造器执行前,总会先执行实例初始化块。

所以当我们用super调用父类构造器时,父类构造器会默认先调用实例初始化块。

6.子类是否可以继承父类final修饰的实例变量,和实例方法?

public class Main{
    public static void main(String[] args){
		Base b = new Base();
		b.test();
		System.out.println(b.num);
	}
}

class Super{
	final int num = 10;
	final void test(){
		System.out.println("Super final method");
	}
}

class Base extends Super{}
首先运行上面的程序发现,正常运行,结果是

Super final method
10

所以子类可以继承父类的final方法和变量。

对于final关键字的作用:

1.被final修饰的类无法派生子类
2.被final修饰的变量,不能改变指向
3.被final修饰的方法,不能被子类重写。


所以子类只是不能重写父类final方法,也不能继承自父类的final变量的指向

7.子类是否可以继承父类的私有成员?

更具继承定义来看:

子类可以继承父类(除构造器,初始化块)的所有成员。

没有提到私有成员,所以子类继承了父类的私有成员的。只是子类无法访问继承自父类的私有成员而已。

这里可以将继承分为两种:

显式继承:可以访问从父类继承到的成员。

隐式继承:无法访问从父类继承到的成员。

8.子类可以将父类的静态方法重写为实例方法吗?

不可以

方法重写有一个规则:

父类实例方法只能被子类实例方法重写。
对于父类静态方法,存不存在重写规则,尚存争议。更不要说被子类实例方法重写。

另外,父类静态方法属于父类本身,在内存中只有一份。即使被子类继承,子类也不会像为(继承自父类的实例变量)单独开辟一个内存存储,而是会直接访问父类的静态方法。

9.当子类定义了和父类相同的实例变量时,父类的实例变量会被覆盖吗?

不会。子类只会隐藏继承自父类的同名实例变量。

因为子类继承父类后,创建子类对象时,会开辟两块内存空间:
一块用于存储子类本身的实例变量
一块用于存储继承自父类的实例变量
所以在内存上来看,子类实例变量和继承自父类的实例变量不会发生覆盖现象。

10.子类从父类继承的方法是否可以操作子类自定义的变量?请思考下面代码运行结果(好玩的题目,让我认识到自己对继承的了解还不够深入)

public class Main{
	public static void main(String[] args){
		A a = new A();
		a.printOut();// A.i相加 A:1
		B b = new B();
		b.printOut();// B.i相加 A:0
		b.show();// 2
		a = b;
		a.printOut();// B.i相加 A:0
	}
}

class A {
	int i = 0;
	public void doAdd(){
		i++;
		System.out.println("A.i相加");
	}
	
	public void printOut(){
		this.doAdd();
		System.out.println("A:"+i);
	}
	
}

class B extends A {
	public int i = 0;
	public void doAdd(){
		i+=2;
		System.out.println("B.i相加");
	}
	public void show(){
		System.out.println(i);
	}
}
结论:子类继承的方法,只能操作子类继承的成员变量。

11.super关键字与this关键字的区别是什么,它们之间是什么关系?

主要是用途上的区别

this的用途
1.在实例方法中 代表 调用该实例方法的对象。可以用来调用本类其他实例方法。
2.在构造器中 代表 当前创建对象。 可以用来区别同名变量是形参还是对象的实例变量。
3.在构造器中 调用 其他重载的构造器。 

super的用途
1.作为对象,限定该对象调用它从父类继承得到的实例变量,实例方法
2.作为构造器,限定该构造器初始化的是该对象从父类继承得到的实例变量,而不是自己的实例变量


当this和super在构造器中使用时,他们不能同时出现。
因为它们都要定义在构造器的第一行。

12.为什么说子类构造器总会调用一次父类构造器?

如果子类中只定义了一个构造器,则有三种情况:
class Base{
    Base(){
        // 什么都不写,系统自动隐式调用父类无参构造器
        //super();// super()是显式调用父类无参构造器
        //super(12);// super(12)是显式调用父类有参构造器
    }
}
显然,此时肯定会调用父类构造器


如果子类中定义了多个重载构造器:

那么如果多个重载构造器中没有使用this互相引用,那么每个构造器都是默认调用super,或者显式调用super

如果多个重载构造器间使用this互相引用,那么我们知道,
由于this和super都只能在构造器第一行,所以它们互斥。即有this,就没有super。

那么题目的问题可以换种思路:

总会有一个子类构造器显式或隐式地调用super

再换种思路,即

总会有一个子类构造器不会调用this引用其他重载构造器

那么会不会出现所有的子类构造器都用this引用其他重载构造器呢?
显然是不会的,代码复用不可能是一个环式复用,那不就死循环了吗。所以肯定有一个子类构造器作为最基础的,被其他重载构造器调用的,而不会调用其他重载构造器。

13.请思考下面代码运行结果(发生继承关系时的默认初始化,子类自定义实例变量初始化,子类构造器初始化,继承自父类的实例变量初始化,父类构造器初始化执行顺序):

class X{
    Y b = new Y();
    X(){
        System.out.println("X");
    }
}

class Y{
    Y(){
        System.out.println("Y");
    }
}

public class Z extends X{
    Y y = new Y();
    Z(){
        System.out.println("Z");
    }
    public static void main(String[] args){
        new Z();
    }
}
这道题的输出结果时
Y
X
Y
Z


子类实例变量(包含继承自父类的实例变量)默认初始化 > 父类实例变量初始化 > 父类构造器 > 子类实例变量初始化 > 子类构造器

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员阿甘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值