1. 继承的概述
1.1 继承的定义
继承机制:简单来说,就是子类继承父类的属性和行为,使得子类对象具有父类相同的属性和行为(除构造方法外,全部继承 );(举例说明 :相当于徒弟拜师学艺,师傅将自己的武功进行传授,这样,徒弟就继承了与师傅相同的武功,当然徒弟也可以对所学武功进行扩展);它是面向对象程序设计中代码复用采取的重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,以产生新的类,产生的新类又称为派生类,同时继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程;
注意:上述的父类 又称为超类 或者基类 ,而子类 又称为派生类 ;
1.2 继承的作用
(1)通过继承来抽取类之间的共性部分,以实现代码的复用,提高程序的开发效率;
(2)通过继承实现多态(后面讲);
1.3 继承的格式
通过extends 关键字来声明一个类是继承于另一个类中的,形式如下:
class 父类 {
...
}
修饰符 class 子类 extends 父类 {
}
需要注意的是:
子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同 ,否则就没有必要继承了 ;
2. 继承之后的成员变量
2.1 成员变量与父类不重名
当子类与父类中存在不重名的成员变量时,对访问没有任何影响,代码如下所示:
(1)定义一个Base 类
public class Base {
int a;
int b;
}
(2)再定义一个Derived 类,用来继承Base 类
public class Derived extends Base {
int c;
int d;
public void method() {
a=10;
b=20;
c=30;
d=40;
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
}
public static void main(String[] args) {
Derived d=new Derived();
d.method();
}
}
输出结果:
10
20
30
40
2.2 成员变量与父类重名
当子类与父类中存在不重名的成员变量时,对访问是有影响的,代码如下所示:
(1)定义一个Base 类
public class Base {
int a=10;
int b=20;
}
(2)再定义一个Derived 类,用来继承Base 类
public class Derived extends Base {
int a;
byte b;
int c;
int d;
public void method() {
a=100;
b=110;
c=120;
d=130;
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
System.out.println("=======================");
System.out.println(super.a);
System.out.println(super.b);
System.out.println(this.c);
System.out.println(this.d);
}
public static void main(String[] args) {
Derived d=new Derived();
d.method();
}
}
输出结果:
100
110
120
130
=======================
10
20
120
130
由上可以知道,当子类中出现了与父类同名的成员变量时,访问时,子类会优先访问自己的成员变量(与变量的类型无关 );换言之就是它会把与父类同名的成员隐藏了,若要访问,有以下两种方式:
(1)使用Set() 或Get() 方法
使用方式:在父类中产生Set() 或Get() 方法,然后在基类中直接调用即可;
(2)使用super 关键字;
使用格式:super.父类成员名 ;
成员变量访问遵循就近原则,自己有就优先访问自己的,没有则向父类中找;
3. 继承之后的成员方法
3.1 成员方法不重名
同样的,当子类与父类的成员方法不重名时,这时对调用是没有任何影响的,也就是说,通过子类对象访问父类与子类不同名的方法时,会优先在子类中找,能找到则访问,否则在父类中找,找到就访问,否则编译报错;代码如下:
(1)定义一个Fu 类
public class Fu {
public void methodA(){
System.out.println("Fu----methodA()");
}
}
(2) 再定义一个Zi 类,用来继承Fu 类
public class Zi extends Fu{
public void methodB(){
System.out.println("Zi---methodB()");
}
public static void main(String[] args) {
Zi zi=new Zi();
zi.methodA();
zi.methodB();
}
}
输出结果:
Fu----methodA()
Zi---methodB()
3.2 成员方法重名(重载/重写)
(1)当方法名相同,参数列表不同时,就构成了我们之前所学的重载(即:方法的重载) ; (2)当方法名相同,参数列表相同时,则形成了重写 ;
代码如下: (1)定义一个Base 类
public class Base {
public void methodA(){
System.out.println("我是父类方法A");
}
public void methodB(){
System.out.println("我是父类方法B");
}
}
(2)再定义一个Derived 类,用来继承Base 类
public class Derived extends Base {
public void methodA(int a){
System.out.println("我是子类带形参的方法A");
}
public void methodB(){
System.out.println("我是子类方法B");
}
public void methodC(){
System.out.println("我是子类方法C");
}
public void testMethod(){
methodA(10);
methodA();
methodB();
super.methodB();
methodC();
}
public static void main(String[] args) {
Derived d=new Derived();
d.testMethod();
}
}
输出结果:
我是子类带形参的方法A
我是父类方法A
我是子类方法B
我是父类方法B
我是子类方法C
上述代码中,基类的方法A与子类的方法A就构成了重载,(1) 当传入参数值时,就会访问带形参的子类中的A方法;(2) 当没有传入参数值时,首先会优先访问子类自己的方法A,但是没有找到,所以访问基类的方法A;
而不同的是,上述的代码中,子类的方法B就与基类中的方法B构成了重写,因为原型一模一样,这样,当访问与基类重名的B方法时,优先访问子类自己的,同时会将基类中重名的方法屏蔽掉,要想访问,就必需要使用super 关键字;
4. 继承之后的构造方法
在继承体系中,子类对象构造时:
创建那个类的对象,就会调用那个类的构造方法
仔细来说,就是当创建子类对象时,就会调用子类的构造方法,但在子类构造方法的第一行会默认的有隐含的super() 调用,即告诉编译器去调用基类的构造方法;(很重要呢 调试代码会看的更清楚哦),所以看到的结果如下,并不是先调用父类的构造方法,再调用子类的构造方法;
代码如下:
(1)定义一个Fu 类
public class Fu {
int a;
int b;
public Fu(){
System.out.println("我是父类构造方法");
}
}
(2)定义一个Zi 类
public class Zi extends Fu{
int c;
int d;
public Zi(){
System.out.println("我是子类构造方法");
}
public static void main(String[] args) {
Zi zi=new Zi();
}
}
输出结果:
我是父类构造方法
我是子类构造方法
可以从对象模型来进行讨论:
所以说,子类对象其实就是一个父类对象,子类对象和父类对象是is-a 的关系,因为,前半部分是一样的;在构造子类对象时,先要将从父类继承下来的成员初始化完成后,然后再初始化子类自己新增加的成员;
需要注意的是:
(1) 若父类显式定义无参或者默认的构造方法,此时,子类根据是否需要选择定义构造方法 ;
(2) 如果父类提供的构造方法是带有参数的,此时编译器不会再给子类生成默认的构造方法,此时用户必须要为子类显式定义构造方法 ; 原因:因为子类要在其构造方法中通过super 调用基类的构造方法来完成子类对象从基类继承下来的成员的构造;(有点绕,但很重要,如上图所画的)
(3) 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句 ; (4) super(...)只能在子类构造方法中出现一次,并且不能和this同时出现 ;
5. super 与 this
super 不能在静态方法中使用
用来访问父类的成员变量和方法
5.1 super 与 this区别
相同点:
(1) 都是Java中的关键字; (2)只能在类的非静态方法中使用,用来访问非静态成员方法和字段; (3)在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在-----------------就会导致子类对象从父类继承下来的成员被多次构造;
不同点:
(1)this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是父类对象的引用 ;
(2) 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性 ;
(3) this是非静态成员方法的一个隐藏参数,super不是隐藏的参数 ;
(4) 成员方法中直接访问本类成员时,编译之后会将this还原,即本类非静态成员都是通过this来访问的;在子类中如果通过super访问父类成员,编译之后在字节码层面super实际是不存在的(通过字节码文件可以验证) ;
(5) 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有 ;
6. 继承之后初始化问题
接下来,我们来看一下继承关系下的初始化问题,
如下代码所示:
(1)定义一个Person 类
public class Person {
public Person(){
System.out.println("我是父类构造方法");
}
{
System.out.println("我是父类实例代码块");
}
static{
System.out.println("我是父类静态代码块");
}
}
(2)定义一个Student 类,用来继承Person 类
public class Student extends Person{
public Student(){
System.out.println("我是子类构造方法");
}
{
System.out.println("我是子类实例代码块");
}
static{
System.out.println("我是子类静态代码块");
}
public static void main(String[] args) {
Student s1=new Student();
System.out.println("====================");
Student s2=new Student();
}
}
输出结果:
我是父类静态代码块
我是子类静态代码块
我是父类实例代码块
我是父类构造方法
我是子类实例代码块
我是子类构造方法
====================
我是父类实例代码块
我是父类构造方法
我是子类实例代码块
我是子类构造方法
由于静态代码块是在类加载阶段完成的,因此先执行,而继承体系下,子类要依靠父类,所以父类执行完成后就执行子类的静态代码块,而实例代码块是被拷贝到构造方法之前的,所以优于构造方法执行;
7. 继承的方式
在Java中,支持的继承方式有以下三种:
(1)单继承 :
(2)多层继承
(3)不同类继承同一个类
(4)多继承 (不支持) 注意:这种继承方式是不支持的;同时,一般不希望出现超过三层的继承关系;
|