目录
[1. 继承](#1. 继承)
[1.1 继承的概念](#1.1 继承的概念)
[1.2 extends关键字](#1.2 extends关键字)
[1.3 继承方式](#1.3 继承方式)
[2.1 成员变量](#2.1 成员变量)
[2.2 成员方法](#2.2 成员方法)
[3. super关键字](#3. super关键字)
[4. super、this 与 构造方法](#4. super、this 与 构造方法)
[4.1 子类中的super()调用](#4.1 子类中的super()调用)
[4.1.1 父类只有无参构造方法](#4.1.1 父类只有无参构造方法)
[4.1.2 父类有带参构造方法时](#4.1.2 父类有带参构造方法时)
[4.2 super 与 this 的异同与注意事项](#4.2 super 与 this 的异同与注意事项)
[5. 再谈初始化:类中代码的执行顺序](#5. 再谈初始化:类中代码的执行顺序)
[5.1 无继承关系](#5.1 无继承关系)
[5.2 有继承关系](#5.2 有继承关系)
[6. protected的包访问权限](#6. protected的包访问权限)
[7. final关键字](#7. final关键字)
[7.1 修饰变量](#7.1 修饰变量)
[7.2 修饰类](#7.2 修饰类)
[8. 组合与继承](#8. 组合与继承)
1. 继承
1.1 继承的概念
继承机制: 它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称为派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。
继承主要解决的问题是:共性的抽取,实现代码复用。
上述图示中,Dog和Cat都继承了Animal类,其中:Animal类称为父类/基类或超类,Dog和Cat可以称为Animal的 子类/派生类。继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。
1.2 extends关键字
在Java中如果要表示类之间的继承关系,需要借助extends关键字,具体如下:
修饰符 class 子类名 extends 父类名 {
// ...
}
子类继承父类之后,建议要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
例如:
cpp
// Animal.java
public class Animal {
public String name = "小小";
public int age = 3;
public void sleep(){
System.out.println(name+"正在睡觉");
}
}
// Dog.java
public class Dog extends Animal{
public void bark(){
System.out.println(name+"在汪汪叫");
}
}
// Test.java
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println("小狗叫"+dog.name);
dog.sleep();
dog.bark();
}
}
这里的Dog(狗类)继承了Animal(动物类),所以dog的名字也继承了父类Animal的名字"小小"。
同时父类能做的事情,子类继承过来也能做,比如这里的dog可以使用父类的sleep()方法。
而且子类添加了自己特有的成员------bark()成员方法。
1.3 继承方式
在现实生活中,事物之间的关系是非常复杂,灵活多样,比如:
类似的,java中有这3种继承关系:(箭头端表示子类)
1. 单继承
2. 多层继承
3. 不同类继承自同个类(一个父类可以有多个子类)
注意:java中不存在多继承的关系,即一个子类不能有多个父类。
2.继承类的成员访问
在刚刚的例子中,我们通过dog.name的方式访问到父类的name叫"小小",也通过dog.sleep的方式访问到父类的sleep方法。那如果Dog类中也有自己的name成员变量和sleep成员方法又会发生什么?
(子类的成员与父类的成员名字相同时,系统会访问子类的还是父类的?)
2.1 成员变量
cpp
//父类
public class Dad {
public int a = 10;
}
//子类
public class Child extends Dad{
public int a = 20;
{
System.out.println("在代码块中输出a:"+ this.a);
}
public void printa(){
System.out.println("使用成员方法输出a:"+ this.a);
}
}
//测试类
public class Test {
public static void main(String[] args) {
Child child = new Child();
//访问的是父类的a还是子类的a?
System.out.println("通过子类对象访问a:"+child.a);
child.printa();
}
}
结果都是20,而不是父类的10,说明访问的是子类的a。
继承类中的成员变量访问规则:子类优先,父类其次。
- 子类无,父类无:报错。
- 子类有,父类无:访问子类的成员变量。
- 子类无,父类有 :访问父类的成员变量。(向上寻找)
- 子类有,父类有 [重名] :优先访问子类的成员变量。
2.2 成员方法
cpp
//父类
public class Dad {
public void A(){
System.out.println("Dad中的方法A()");
}
public void B(){
System.out.println("Dad中的方法B()");
}
}
//子类
public class Child extends Dad{
public void A(int a) {
System.out.println("Child中的方法A(int)");
}
public void B(){
System.out.println("Child中的方法B()");
}
public void method(){
A(); // 没有传参,访问父类中的A()
A(20); // 传递int参数,访问子类中的A(int)
B(); // 直接访问,则永远访问到的都是子类中的B(),父类的无法访问到
}
}
//测试类
public class Test {
public static void main(String[] args) {
Child child = new Child();
child.method();
}
}
由这个例子我们可以总结出访问子类的成员方法的规则。
继承类中的成员方法访问规则:子类优先,父类其次;重载看参数,重写用子类。
- 子类无,父类无:报错。
- 子类有,父类无:访问子类方法。
- 子类无,父类有 :访问父类方法。(向上寻找)
- 子类有,父类有 [重名] :
a. 参数列表不同(构成方法重载):根据输入的参数来决定使用哪一个方法。
b. 参数列表相同(构成方法重写):优先使用子类的方法。
3. super关键字
由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成 员时,该如何操作?
直接访问是无法做到的,Java提供了super关键字,该关键字主要作用:在子类方法中访问父类的成员。
cpp
父类
public class Dad {
public int a = 10;
public void A(){
System.out.println("Dad中的方法A()");
}
}
子类
public class Child extends Dad{
public int a = 20;
public void A() {
System.out.println("Child中的方法A()");
}
public void field(){
System.out.println(a); //子类的变量a
System.out.println(super.a); //父类的变量a
}
public void method(){
A(); //子类的方法A
super.A(); //父类的方法A
}
}
测试
public class Test {
public static void main(String[] args) {
Child child = new Child();
child.field();
child.method();
}
}
在子类方法中,如果想要明确访问父类中成员时,借助super关键字即可。
注意:super关键字不能用于静态代码块、静态变量和静态方法。
因为super是对子类对象的父类的引用,需要子类对象的创建,而静态的成员不依赖于对象。
4. super、this 与 构造方法
4.1 子类中的super()调用
4.1.1 父类只有无参构造方法
当父类只有 无差的构造方法 或 无构造方法时,子类构造方法中默认会调用父类的无参构造方法:super()。【super()默认是子类构造方法的第一条语句。】
在子类构造方法中,并没有写任何关于父类构造的代码,但是在构造子类对象时,先执行父类的构造方法,然后执行子类的构造方法。
因为:子类对象中成员是由两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子,肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整 ,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整 。
例如:
4.1.2 父类有带参构造方法时
如果父类构造方法是带有参数的,此时需要:
- 用户要为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用。
- 该子类构造方法的第一句是super(...)。
例如:
4.2 super 与 this 的异同与注意事项
super和this都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语 句,那他们之间有什么区别呢?
【相同点】
不能用于静态变量、静态方法 和 静态代码块。
显式使用时,必须是构造方法中的第一条语句。
【不同点】this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性。
构造方法中一定会存在super(...)的调用,用户不写编译器也会增加,但是this(...)用户不写则没有。
【注意】this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造 方法中出现。
因为super和this要显式使用时,都必须是构造方法的第一句,但两者又不可能同时是第一句。
例如:
5. 再谈初始化:类中代码的执行顺序
我们知道,静态代码块、实例代码块和构造方法是在类加载时或者实例对象创建时执行的。那么它们执行的顺序是怎样的呢?
5.1 无继承关系
观察下面的代码,猜测一下在没有继承关系时的执行顺序
cpp
class Person {
public String name;
public int age;
//构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("构造方法执行");
}
//实例代码块
{
System.out.println("实例代码块执行");
}
//静态代码块
static {
System.out.println("静态代码块执行");
}
}
public class Test {
public static void main(String[] args) {
System.out.println("第一次:");
Person person1 = new Person("小明",10);//第一次触发类加载和静态代码块
System.out.println("============================");
System.out.println("第二次:");
Person person2 = new Person("大明",20);//第二次无类加载,不再执行静态代码块的内容
}
}
由这个结果可以总结出无继承关系时的执行顺序:
无继承关系时:
【类未加载】
静态代码块 --> 实例代码块 --> 构造方法
【类已加载】
实例代码块 --> 构造方法
补充:
- 静态变量的初始化、静态代码块和静态方法 的代码都是同一个时期执行的。(执行的顺序由代码文本的上下顺序决定)
- 静态成员执行完后,接下来就是成员变量、实例代码块和成员方法 的执行时期。(执行的顺序也是由代码文本的上下顺序决定)
- 构造方法最后执行。
5.2 有继承关系
当存在继承关系时,子类的执行顺序是怎么样的?
cpp
父类
class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person:父类构造方法执行");
}
{
System.out.println("Person:父类实例代码块执行");
}
static {
System.out.println("Person:父类静态代码块执行");
}
}
子类
class Student extends Person{
public Student(String name,int age) {
super(name,age);
System.out.println("Student:子类构造方法执行");
}
{
System.out.println("Student:子类实例代码块执行");
}
static {
System.out.println("Student:子类静态代码块执行");
}
}
public class Test {
public static void main(String[] args) {
System.out.println("第一次:");
Student student1 = new Student("小明",19);
System.out.println("===========================");
System.out.println("第二次:");
Student student2 = new Student("大明",20);
}
}
由该例子可以得出以下执行顺序:
有继承关系时:(创建子类对象)
【父类子类均未加载】
父类静态 --> 子类静态 --> 父类实例 --> 父类构造方法 --> 子类实例 --> 子类构造方法
【父类已加载、子类未加载】
子类静态 --> 父类实例 --> 父类构造方法 --> 子类实例 --> 子类构造方法
【父类子类都已加载】
父类实例 --> 父类构造方法 --> 子类实例 --> 子类构造方法
图示:
6. protected的包访问权限
在类和对象章节中,为了实现封装特性,Java中引入了访问限定符,主要限定:类或者类中成员能否在类外或者其 他包中被访问。
我们来回忆一下这些关键字:
如果把不同的包比作不同的村子,把子类和父类比作家族成员的话,那么:
public:可以理解为一个人的外貌和声誉,谁都可以看得到。(不同包中的不同类都可以访问)
protected:对于同一个村庄的村民当然知道你的外面特征和声誉(个同一包中的不同类可以访问);而对于其他地方或城市,也是你的亲戚和家族成员比较了解你一些(不同包中的子类和父类)。
无修饰(default):只有同一个村庄的人才知道你。(同一包中的不同类可以访问)
private:只有自己知道,其他人都不知道。
举例说明:
[同一个包]
其他类(子类+非子类):可使用的权限是public、protected、default
[不同包]
是继承类:可使用的权限是public、protected
非继承类:可使用的权限是public
private修饰的,只能父类使用。
7. final关键字
final关键字可以修饰变量、类和成员方法。
7.1 修饰变量
final修饰变量或字段时,表示常量,不能修改。
(final修饰的范围包括成员变量和局部变量)
例如:
cpp
public class Test {
final static int a = 1;
public static void main(String[] args) {
final int b = 2;
a = 2;
b = 3;
}
}
此时 成员变量a 和 局部变量b 都是常量,常量的值不能修改,所以会运行报错:
基本数据类型变量 :当
final
修饰基本数据类型变量时,该变量一旦被赋值后就不能再次改变其值。引用数据类型变量 :对于引用数据类型变量,
final
关键字表示该变量的引用不能再指向其他对象,但对象本身的内容是可以修改的。
7.2 修饰类
final修饰类时,表示此类不能被继承。
例如:
cpp
package demo1;
final public class Animal { //Animal类被final修饰
}
class Dog extends Animal{ //继承Animal类会报错
}
我们平时是用的 String 字符串类, 就是用 final 修饰的, 不能被继承:
7.3 修饰方法:表示该方法不能被重写【放在下一篇文章中介绍】
8. 组合与继承
组合的思想:
和继承类似,组合也是一种表达类之间关系的方式。它允许我们将对象组合成树形结构以表示部分-整体的层次结构。
继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
组合表示对象之间是have-a的关系,比如:汽车中有方向盘、发动机、前照灯...
组合的实现:
组合并没有涉及到特殊的语法 (诸如 extends 这样的关键字),仅仅是将一个类的实例作为另外一个类的字段。
汽车和其轮胎、发动机、方向盘、车载系统等的关系就应该是组合,因为汽车是有这些部件组成的:
cpp
// 轮胎类
class Tire{
// ...
}
// 发动机类
class Engine{
// ...
}
// 车载系统类
class VehicleSystem{
// ...
}
------------------------------------------------------------------------------------------------------------------------------------------------------【组合】
// 汽车类 将上述所有类组合起来
class Car{
private Tire tire; // 可以复用轮胎中的属性和方法
private Engine engine; // 可以复用发动机中的属性和方法
private VehicleSystem vs; // 可以复用车载系统中的属性和方法
// ...
}
------------------------------------------------------------------------------------------------------------------------------------------------------【继承】
// 奔驰汽车类 继承自汽车类
class Benz extend Car{
// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}
这里的轮胎实例变量、发动机实例变量 和 车载系统实例变量 都作为 汽车类的成员变量,这就是组合。
本期分享完毕,感谢大家的支持Thanks♪(・ω・)ノ