先导:
不知道大家有没有在中学时代有过以下感受。
明明有些题目用的思想都是一模一样的,甚至方法都一模一样,只不过数据不一样而已。反正那个时候的笔者是直接略过这种题,因为方法已经掌握了,在做同种种类的题目对于自己已经没有任何提升了。
但是这种情况在程序员写代码的时候那可谓是屡见不鲜。有些代码明明是一样的,但是就是要反复地复制粘贴,这样费时且费力。但是Java却给我们提供了一个非常好的帮手,叫做继承。
由于Java是面对对象的,而对象又归属于类,类与类之间难免会有一些成员拥有共性,以动物类为例,猫和狗都是动物类,每一只猫猫都有自己的名字,每一只小狗都有自己的性别,但是,猫猫和小狗在一些行为上肯定存在着差异,如果我定义猫类和狗类每一次都需要定义他们的名字,性别等等方面,很明显代码存在着冗余,并且冗余程度随着类的增多而加大。
那么,如果类之间能够共享一些属性,那该多好!我们之前说了,Java为我们提供了一种写法,叫做继承,而继承就可以实现类中成员和方法的共享。
本文将聚焦以下问题。
1.继承是什么
2.super关键字
3.特殊考点-父子类中不同代码块的实现顺序
(臭不要脸环节)如果看完了这篇文章,能否给作者一个赞呢?如果给作者一个免费的关注那就再好不过啦~
那么,现在本文开始。
1.继承
1.1 什么是继承
在先导中,我们提到了,继承能够实现类中成员和方法的共享。但是这个太笼统了,成员是全部共享,还是只共享一方的?这其中有很多问题,这些问题我们慢慢解答,但是,我们最先需要解决的问题,就是,继承是什么???
继承定义如下:
继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
看完了这一点定义,我们就能够解决我们许多不了解的问题。继承,是对于类与类当中共性的抽取,那么,继承势必要涉及到多个类,同时,类与类之间有共性,也得有异性,如果类和类之间没有不同点,那么继承也没必要存在。
1.2 继承的语法
在条目1.1当中,我们明白了,继承得涉及到多个类,那么,必定存在谁继承谁这样的一层关系。那么,在Java中,抽取共性的类叫做父类(也可以被叫做超类,基类),继承父类的类叫做子类(也可叫做派生类),用图形表示,可以表示成这样。
从这一幅图来看,我们知道了,类1和类2是我原先想要定义的类,但是他们两个有相同的地方(例如名字,性别,年龄...),所以我抽取他们两个的共性作为父类,同时让子类1和子类2继承父类,这个时候只需要在子类1中定义类1的独属成员变量和方法,在子类2中定义类2的独属成员变量和方法,就等同于我实现了类1和类2的定义。这样定义的好处就是极大地实现了代码的复用!!!
在Java中如果要表示类之间的继承关系,需要借助extends关键字,具体语法如下:
修饰符 class 子类 extends 父类 {
// ...
}
用代码举例子就像这样:
class Animal{
String name;
String sex;
int age;
void eat(){
System.out.println(this.name+"正在吃饭");
}
}
class Dog extends Animal{
void bark(){
System.out.println(this.name +"正在汪汪汪!");
}
}
class Cat extends Animal{
void mew(){
System.out.println(this.name +"正在喵喵喵!");
}
}
class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "初一";
dog.bark();
dog.eat();
Cat cat = new Cat();
cat.name = "初二";
cat.mew();
cat.eat();
}
}
我们执行这个代码,结果如下。
很明显可以看到我在Dog类和Cat类当中并没有规定name变量和eat方法,但是我们就是能爱main方法调用这些属性和方法,所以,Cat类和Dog类都继承了Animal类当中的成员变量和方法。
那么,我们既然继承了这些成员属性和变量,接下来就得好好探讨如何使用他们。
2.super关键字
在我们详细讲解super关键字之前,我们得看一串代码。
public class Base {
int a;
int b;
}
public class Derived extends Base{
int c;
public void method(){
a = 10;
b = 20;
c = 30;
}
}
这个时候假如我调用method,那么毫无疑问,此时访问的a、b肯定都是从父类继承的a、b,此时我修改一下代码。
public class Base {
int a = 99;
int b = 0;
}
public class Derived extends Base{
int a;
int b;
int c;
public void method(){
a = 10;
b = 20;
c = 30;
}
}
我现在再次调用method,有的同学就开始犯难了,这访问谁啊?是访问父类中的a、b,还是访问访问子类自身的a、b呢?
我们来探究一下。
通过debug我们发现method访问的是子类中自己有的a、b,而不是父类当中继承的。
所以,在继承了其他类的类中调用成员变量和方法的时候,有以下规则。
在子类方法中 或者 通过子类对象访问成员时:
如果访问的成员变量子类中有,优先访问自己的成员变量。
如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报 错。
如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
总结:成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
在子类方法中 或者 通过子类对象访问方法时:
通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
通过子类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错。
但是哦,我们肯定会有需要在子类当中直接访问父类当中的成员变量或者是方法,这个时候就需要用到super关键字了。
2.1 super关键字的用法
class Base {
int a = 99;
String b = "bit";
void methodB() {
System.out.println("这是父类当中的methodB");
}
}
class Derived extends Base{
int a;
int b;
int c;
void methodB(){
System.out.println("这是子类当中的methodB");
}
public void method(){
a = 10;
b = 20;
c = 30;
System.out.println(super.a);
System.out.println(super.b);
super.methodB();
System.out.println("++++++++++++++++++++");
System.out.println(this.a);
System.out.println(this.b);
methodB();
}
public static void main(String[] args) {
Derived d = new Derived();
d.method();
}
}
那其实发现咱们的super关键字和this关键字都还挺相似的,现在我们来对this和super之间做一个对比!
2.2 this和super的对比
首先,他们两个有本质上的区别。this关键字本质上是一个引用,而super只是一个关键字!!!
super存在的意义更多的是为了增加代码的可读性。但是,super和this之间还是有着很多区别的。
super我们知道是用来直接访问父类当中的元素和方法,然而,你不知道的是,this其实也可以。但依据我们前面所讲的规则,如果说子类和父类当中有变量名相同或者是方法名相同,this就不可能能访问得到父类当中变量和方法。具体范围如下。
但是就像我说的,如果子类和父类都有a,如果通过this来访问a,你得到的结果只可能是10,而不是99 。
当然super关键字也和this关键字也有共同之处,例如,不能在静态方法中使用super关键字,不能调用静态成员变量。
当然,最重要的一点!super关键字只能在子类当中使用!!!!!!!
2.3 再谈初始化
既然this引用中,有this()这种用法,对应的,super当中也同样存在这种用法。
在前面的文章当中,this()是用来在类当中的构造方法里调用其他的构造方法,而构造方法是用于创建对象和对实例化对象进行初始化的。
但是如果一个类有继承,显然不能还像之前一样傻瓜式初始化,父类和子类谁先创建这个问题是必须要解决的。
这里就要知道,是先构造(创建)父类,还是先构造子类。其实你都看到这里了,用脚趾头都能想出来肯定是先构造父类,因为子类得继承父类,如果父类不先于子类构造完成,子类不就缺东西了吗!!!!!!!!
所以,在子类中,得先进行父类的构造,才能再进行子类的构造。那么也就是!父类的构造方法应该放在子类代码中的第一句!而这个第一句,就是super().
但是在前面的代码里面我并没有明确的写出来super,这个其实编译器帮我们做了!!!(快说谢谢编译器)
我们以这个代码为例。
class Animal{
String name;
String sex;
int age;
void eat(){
System.out.println(this.name+"正在吃饭");
}
}
class Dog extends Animal{
public Dog(){
super();
}
void bark(){
System.out.println(this.name +"正在汪汪汪!");
}
}
class Cat extends Animal{
void mew(){
System.out.println(this.name +"正在喵喵喵!");
}
}
class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "初一";
dog.bark();
dog.eat();
Cat cat = new Cat();
cat.name = "初二";
cat.mew();
cat.eat();
}
}
你看在Dog类当中,如果我们把隐去的代码都写出来,那么就不难发现,其实首先,程序从main方法开始执行。然后调用Dog构造方法,然后,程序走到Dog类,然后发现Dog是继承了Animal类,这个时候就得执行Animal的构造方法!!!如果我们不明显的写出来,那么Java会帮我们补充一个不带参数的super,但是如果我们有名曲的规定,那么Java则不再帮我们提供。
3.笔试题--父子类代码块的执行顺序
既然看完了前面三个点,我们发现,父类优先级高于子类,同时,静态代码块的优先度高于构造代码块高于普通代码块。那么,面对带有继承的题目,要你判断代码的执行顺序,你应该怎么做呢???
顺序是 父类静态代码块>子类静态代码块>父类构造代码块>父类的普通代码块>子类的构造代码块>子类的普通代码块.
本篇文章到此结束!!!
(再一次臭不要脸环节)如果看完了这篇文章,能否给作者一个赞呢?如果给作者一个免费的关注那就再好不过啦~