继承是Java面向对象的一个最为显著的特性。继承是从已有的类中派生出新的类,新的类能够吸收已有类的数据属性和方法,并且能够具有自己的能力。Java中的继承是使用已有类定义新类的一种技术,新类的定义能够增加新的数据以及新的功能。但是Java只能实现单继承。但是,一个类可以实现过个接口。
在Java实现继承的时候,我们使用的是extends关键字,继承一个已有的类,这也被叫做泛化。Java中被继承的类叫做父类,新的类叫做子类。就像父亲与儿子一样,你DNA中继承了来自父亲的染色体,但是你也拥有你自己多于父亲的染色体。
继承的语法:
上面就是大概继承的相关概念。
下面,我们来说一说继承的实例化规则:子类的实例化是先实例化父类,就如没有你的父亲也就没有你一样(所以要学会感恩),然后再实例化子类。再实例化父类的时候,会主动调用父类的构造器,等父类的构造器运行完毕之后才进行子类构造器的调用。因为这样的实例化过程,对于初学者来说,可能会出现一点小的错误。下面,我举一个简单的例子来说明一下:
首先定义一个父类Father:
可以看到,Father存在一个私有属性name,然后有一个普通的方法,打印的是这个属性;然后构造器中调用了这个方法。
然后,我们再定义一个Son类:
Son继承自Father,然后也有一个相同的打印方法,同时构造器也是调用了这个方法。
然后,我们来进行一段小的测试代码:
大家可以猜测一下,最后的结果是什么。或许很多初学者会给出123,998都输出的答案,哈哈,恭喜你,答错了。真实运行出来的答案是这样的:
是不是很奇怪,我当时也是很懵逼的,但是理解了之后就觉得自己好蠢了。下面让我来说一说自己的拙见:
正如我前面所说的,实例化子类对象的时候会首先调用父类的构造器,当父类的构造器执行完毕之后再来执行子类的构造器。
说一下实例化的过程:
1. 如果一个类存在父类,那么将先初始化父类的静态代码块,当然包括静态对象,然后初始化子类的静态变量。
2. 初始化父类的非静态代码,包括非静态的变量等(父类的属性其实已经被初始化了,我认为)
3. 执行父类的构造函数
4. 初始化子类的非静态代码
5. 执行子类的构造函数
对于这段代码,很多掌握不牢固的初学者会认为看起来是将Son向上追溯变成了Father类对象,然后就应该进行Father类里面的操作(我当时也是这么想的)。其实不是这样的,表面上看起来是追溯成了父类对象,其实new内存中存放的还是一个子类的对象,所以调用的还应该是子类的方法。与此同时,子类重写了父类的方法,也即是父类的方法是在子类中来实现的,那么,调用的时候还是应该调用子类中的方法。如果没有重写父类的方法,那么还是将从父类中进行调用。等一下我将会附上没有重写时的代码。
没有重写的代码:
如果你实在是想调用到父类对的函数,那么只能使用super关键字进行调用。
使用super关键字:
然后就能打印出父类的属性了。也就验证了我上面的猜想,实例化时就已经被初始化了(我也不知道说的对不对,不对的地方请指正!)
好了,今天就到这里,以后想到了接着来补充。
补充一点接口的东西:
接口作为引用类型来使用,任何实现该接口的类的实例都可以存储在该接口类型的变量中,通过这些变量可以访问类中所实现的接口中的方法,Java 运行时系统会动态地确定应该使用哪个类中的方法,实际上是调用相应的实现类的方法。
示例如下:
- public class Demo{
- public void test1(A a) {
- a.doSth();
- }
- public static void main(String[] args) {
- Demo d = new Demo();
- A a = new B();
- d.test1(a);
- }
- }
- interface A {
- public int doSth();
- }
- class B implements A {
- public int doSth() {
- System.out.println("now in B");
- return 123;
- }
- }
now in B
大家看到接口可以作为一个类型来使用,把接口作为方法的参数和返回类型。