Java语言面向对象三大核心特性之【继承】
目录
🐲 1.理解继承的概念
🐲 2.继承的语法格式(关键字extends)
🦄 3.1 子类中访问父类的成员变量
🦖 3.1.1 子类和父类没有同名的成员变量(直接访问)
🦖 3.1.2 子类和父类有同名的成员变量(关键字super)
🦄 3.2 子类中访问父类的成员方法
🦖 3.2.1子类和父类成员方法名字不同(直接调用)
🦖 3.2.2 子类和父类成员方法名字相同(关键字 super)
🐲 4.super关键字
🐲 5.super和this的异同点
🐲 6.子类对象的构造方法
🐲 7.在继承关系中代码块的执行顺序
🐲 8.访问限定符protected(受保护的)
🐲 9.继承类型
🐲 10.关键字final
🐲 11.继承和组合的选择
🐲 1.理解继承的概念
⚜️我们先思考一下,为什么会有继承这个概念,继承的作用是什么?
我们先看一张图片(图片取自网络,侵权删除)
这个图片反映了这些动物的一些共性,比如
兔子和羊都是食草动物,狮子和豹子又是食肉动物
食草动物和食肉动物,又都是动物。
每一层次都有一些共性,这样就可以比如说
各种动物是父类,下面的的这些兔子,羊等是子类
🤠父类更通用,子类更具体,子类会具有父类的一般特性也会具有自身的特性。
这样的例子放在我们java中,就比如这样
🟩所以根据这种共性,在java面向对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码的复用,
🟪并且在java中,它还允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,叫派生类。
⚜️ 再次强调一句话:共性抽取,实现代码复用 这是继承存在的目的 ⚜️
🐲 2.继承的语法格式(关键字extends)
既然已经知道了继承的存在意义和概念,下面就说明一下语法格式,
🟥语法格式很简单,但必须记住继承的关键字是extends
修饰符 class 子类(派生类) extends 父类(基类/超类) {
//
}
⚜️下面把前面写过的猫和狗的代码,进行共性抽取,写出派生类来
class Animal{ public String name; public int age; public void eat() { System.out.println(name+"正在吃饭!"); }}class Dog extends Animal { public float weight; public void bark() { System.out.println(name+"正在狗叫"); }}class cat extends Animal{ public void bark() { System.out.println(name+"正在猫叫"); }}
⚜️下面将现在写的继承代码,和之前写的代码进行比较
⚜️ 我们这里再强调两个必须要注意的点:
🟧(1)子类继承父类,会将父类中的成员变量或者成员方法都继承到它自己身上,
🟫(2)子类继承父类,子类必须新添加自己特有的成员,体现出和父类的相同,不然子类和父类相同了,那子类干嘛要继承呢
比如说这个
🐲 3.子类对父类成员的访问
⚜️下面思考这样一个问题,子类继承父类,会将父类中的成员变量和成员方法都继承下来,
那么在子类中如何访问从父类中继承下来的成员,能直接访问吗?
要回答这个问题就要把这个,分成两部分回答
🤠(1)在子类中,如何访问父类的成员变量
🤠(2)在子类中,如何访问父类的成员方法
🦄 3.1 子类中访问父类的成员变量
🦖 3.1.1 子类和父类没有同名的成员变量(直接访问)
这种很简单直接就可以访问到,看代码
//子类和父类不存在同名成员变量class Base{ public int a; public int b;}class Derived extends Base { public int c; public int d; public void test(){ System.out.println(this.a); System.out.println(this.b); System.out.println(this.c); System.out.println(this.d); }}public class Test02 { public static void main(String[] args) { Derived derived = new Derived(); derived.test(); }}
🦖 3.1.2 子类和父类有同名的成员变量(关键字super)
🟪当子类和父类有同名的成员变量时,优先访问自己的
class Base{ public int a=1; public int b=2;}class Derived extends Base { public int a=3; public int d=4; public void test(){ System.out.println(this.a);//3 System.out.println(this.b);//2 System.out.println(this.d);//4 }}public class Test02 { public static void main(String[] args) { Derived derived = new Derived(); derived.test(); }}
⚜️成员同名直接访问的话,访问的是子类自己的成员,那怎么才能访问到父类的成员呢?
🟦如果子类非要访问父类的成员,那就要使用 super 这个关键字了
🟧super 表示子类从父类中继承的这一部分成员的地址
子类访问父类成员变量
⚜️ 总结一下:
🟥(1)如果子类中有,优先访问自己的成员变量
🟩(2)如果子类中没有,就从父类中继承下来,如果父类中也没有,那就报错了
🟧(3)如果有成员变量同名,那就优先访问子类自己的
一句话,那就是:
🟪成员变量访问遵循就近原则,先访问自己,如果没有,找父类
🦄 3.2 子类中访问父类的成员方法
🦖 3.2.1子类和父类成员方法名字不同(直接调用)
这种很简单直接就可以调用到,看代码
class Base{ public int a=1; public int b=2; public void methodA() { System.out.println("Base::methodA()"); }}class Derived extends Base { public int a=3; public int d=4; public void methodB() { System.out.println("Derived::methodB()"); } public void test(){ methodA();//访问父类继承的methodA() methodB();//访问字类自己的methodB() }}public class Test02 { public static void main(String[] args) { Derived derived = new Derived(); derived.test(); }}
🦖 3.2.2 子类和父类成员方法名字相同(关键字 super)
我们先看这样一段代码
class Base{ public int a=1; public int b=2; public void methodA() { System.out.println("Base::methodA()"); } public void methodB() { System.out.println("Base::methodB()"); }}class Derived extends Base { public int a=3; public int d=4; public void methodA(int val) { System.out.println("Derived::methodA(int)"+val); } public void methodB() { System.out.println("Derived::methodB()"); } public void test(){ methodA();//Base::methodA() methodA(100);//Derived::methodA(int) methodB();//Derived::methodB() }}public class Test02 { public static void main(String[] args) { Derived derived = new Derived(); derived.test(); }}
通过观察:
⚜️子类和父类同名时,子类要访问父类的话,
🟥如果子类和父类同名方法中,构成了重载,也就是同名但参数列表不同时,就可以根据自己参数,选择合适的的方法进行访问
🟦如果子类和父类同名方法中,没有构成重载,那就是重写了,后面会提到重写
就还是会优先访问子类自己的方法。
⚜️再思考一下
🟪如果还是非要访问父类的同名成员方法,那该如何访问?
和前面那个一样还是用super.同名方法,就可以访问同名的父类成员方法了
class Base{ public int a=1; public int b=2; public void methodA() { System.out.println("Base::methodA()"); } public void methodB() { System.out.println("Base::methodB()"); }}class Derived extends Base { public int a=3; public int d=4; public void methodA(int val) { System.out.println("Derived::methodA(int)"+val); } public void methodB() { System.out.println("Derived::methodB()"); } public void test(){ methodA();//Base::methodA() methodA(100);//Derived::methodA(int) super.methodB();//Base::methodB() }}public class Test02 { public static void main(String[] args) { Derived derived = new Derived(); derived.test(); }}
子类访问父类成员方法
⚜️ 总结一下:
🟥(1)如果子类中有,优先访问自己的成员方法
🟩(2)如果子类中没有,就从父类中继承下来,如果父类中也没有,那就报错了
🟧(3)如果有成员方法同名,并且构成了重载,就可以根据自己参数,选择合适的的方法进行访问。
🟦(4)如果有成员方法同名,但没有构成重载,如果直接访问成员方法就会访问子类自己的
🟫(5)如果有成员方法同名,但没有构成重载,那就要用super . 同名方法,才可以访问父类成员方法。
一句话,那就是:
🟪成员变量访问遵循就近原则,先访问自己,如果没有,找父类
🐲 4.super关键字
前面已经用过了super关键字,这里再总结一下
🟥super关键字作用:在子类方法中访问父类的成员
🟦super 只能在非静态的方法中使用:这是因为
super还有一种用法就是
🟪super() 调用父类的构造方法
🐲 5.super和this的异同点
⚜️我们在前面都提过了super和this,它们两个都可以用来访问:
成员变量和成员方法,并且都可以作为构造方法中的第一条语句,那么它们两个还有什么异同点?
相同点:
(1)在构造方法中调用,必须是构造方法中第一句,并且super和this不能同时存在
(2)都是只能在非静态方法中使用,因为这两个都需要对象
(3)都是关键字
不同点:
(1)this 是当前对象的引用,
super是子类对象从父类继承下来部分成员的引用
(2)在构造方法中一定会存在super()的调用,不管写不写都有
this()是如果不写就不调用
(3)this()用于调用自己类的构造方法
super()用于调用父类的构造方法,两个不能同时使用
(4)在非静态的成员方法中,this是用来访问本类的方法和属性
super用来访问从父类继承下来的方法和属性
🐲 6.子类对象的构造方法
先看这段代码
⚜️注意看子类继承是代码报错了,这是因为什么?
🟩前面在父类中已经写了构造方法,而在子类中又没有重新写,编译器会自动, 提供一个无参的子类构造方法进行初始化,
一个父类构造方法有参,一个子类构造方法无参,所以才会报错。
⚜️下面写一个子类对象的构造方法,
class Animal{ public String name; public int age; public Animal(String name, int age) { this.name = name; this.age = age; } public void eat() { System.out.println(name+"正在吃饭!"); }}class Dog extends Animal { public float weight; public Dog(String name,int age,float weight) { super(name, age); //调用父类的构造函数,来初始化此时子类继承过来父类的属性 this.weight = weight; } public void bark() { System.out.println(name+"正在狗叫"); } @Override public String toString() { return "Dog{" + "name='" + name + '\'' + ", age=" + age + ", weight=" + weight + '}'; }}}
⚜️总结一下子类对象构造方法:
子类对象成员是由两部分组成:
🟧子类对象构造时,需要先调用父类构造方法,将从父类继承下来的成员构造完整,
然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整。
⚜️提几点注意:
🤠(1)如果父类执行默认的构造方法,那么在子类构造方法的第一行默认含有super()的调用
🤠(2)如果父类构造方法是带有参数的,此时编译器给子类不会执行默认的构造方法,
这就要程序员自己给子类定义构造方法了
🤠(3)在子类构造方法中,super()调用父类构造时,必须是子类构造方法中第一条语句。
🤠(4)super()只能在子类构造方法中出现一次,并且不能和this同时出现
这里再说一次,用idea如何快速写构造方法
🐲 7.在继承关系中代码块的执行顺序
⚜️直接先看这段代码,思考一下打印顺序
class Animal{ public String name; public int age; static { System.out.println("Animal的静态代码块!"); } { System.out.println("Animal的实例代码块!"); } public Animal() { System.out.println("Animal不带参数的构造方法!"); } public Animal(String name, int age) { this.name = name; this.age = age; } public void eat() { System.out.println(name+"正在吃饭!"); }}class Dog extends Animal { public float weight; static { System.out.println("Dog的静态代码块!"); } { System.out.println("Dog的实例代码块!"); } public Dog() { System.out.println("Dog的不带参数的构造方法!"); } public Dog(String name, int age, float weight) { super(name, age); this.weight = weight; } public void bark() { System.out.println(name+"正在狗叫"); } @Override public String toString() { return "Dog{" + "name='" + name + '\'' + ", age=" + age + ", weight=" + weight + '}'; }}}public class Test01 { public static void main(String[] args) { Dog dog = new Dog(); }}
我们看一下运行结果:
⚜️下来我们再思考一下,如果把代码在运行一次,执行结果会不会变化
🟪 这个说明了,静态的代码块只会执行一次
⚜️总结一下,在继承关系中代码块的执行顺序
🟫(1)父类静态代码块先执行 ---》下来执行子类静态代码块。(静态代码块最先执行)
🟦(2)父类实例代码块先执行 ---》下来执行父类构造方法。 (父类要比子类执行早)
🟧(3)子类实例代码块先执行 ---》下来执行子类构造方法
(实例代码块比构造方法执行早)
🟥(4)第二次实例化对象时,不论子类父类,静态代码块都不执行
🐲 8.访问限定符protected(受保护的)
将我们上一篇的表格拿过来
下面我们通过代码来看一看限定范围
🤠同一个包中的同一类
class Demo { protected int a; public void func() { System.out.println(a); }}
🤠同一包中不同的类
🤠不同包中同的子类
🐲 9.继承类型
先看一张图片(图片取自网络,侵权删除)
这张图片很清楚的画出了,java中的几种继承方式
下面来简单分析一下
⚜️(1)单继承
⚜️(2)多级继承
⚜️ (3)分层继承(也就是不同的类继承同一个类)
🐲 10.关键字final
🟥在前面很多地方都用到了 final 主要是用来修饰变量、成员方法以及类。
这里就简单提一下
⚜️(1)final修饰变量成员,就表示不能被修改的常量了
这里修改就会报错
⚜️(2)final 修饰类,就表示这个类不能被继承
被final修饰了,强行继承就会报错
⚜️(3)修饰方法:表示该方法不能被重写
🐲 11.继承和组合的选择
⚜️本篇前面一直在写继承,那么看到这里,你理解继承了吗,继承到底是什么?
🟥继承指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功
能,而且可以增加它自己的新功能的能力,直接点讲就是
🟪共性抽取,实现代码复用
🟦它的关系就是 is-a
⚜️那么我们这里为什么又会提到组合,组合是什么你了解吗?
🟥复用性是面向对象的语言好处之一,
🟧而这个复用性在java代码中,有三个表现形式:继承,组合,代理,
🟫所以我们下面将要介绍一下 复用性表现形式之 组合
⚜️对于组合的理解:
🟩组合是通过对现有对象进行拼装即组合产生新的具有更复杂的功能。
🟦组合体现的是整体与部分,所拥有的关系,也就是has-a的关系
🟪也把这种方式的代码复用叫黑盒式代码复用
⚜️那么最重要的一个问题就是,继承和组合该如何选择
这里提个建议,
🟧在两种都可行的情况下,优先使用组合
🟦原因是组合比继承更加灵活,也更有助于代码维护