文章目录
- 🚀文章导读
- 1.1 为什么需要继承
- 1.2 继承的概念
- **关于继承有如下三点请记住:**
- 1.4 父类成员访问
- 1.4.1 子类中访问父类成员变量
- 1.4.2 super 关键字
- 1.4.3 子类中访问父类成员方法
- 1.5 子类构造方法
- 1.6 面试题this 和 super 的区别(重点)
- 1.7 再谈代码块之间的运行顺序
- 1.8 protected关键字
- 1.9 继承方式
- 2.0 final关键字
🚀文章导读
本篇文章讲到的知识点稍多,请读者朋友耐心阅读,你将会收获慢慢干货;
看完本篇文章,需要掌握一下几点:
1、什么是继承?被哪些关键字和修饰符修饰时不能被继承
2、如何通过子类去访问父类中的成员
3、super 和 this 的区别
4、父类和子类中,构造方法的调用
1.1 为什么需要继承
类用来对世界上的物体进行描述,但是,在现实世界中,许多物体都有共有的特征,假如有两个物体,它们有一些共性特征,如果单单的只描述两个物体,那么它们共有的特征就会被描述两次,就会造成代码的繁琐,所以就引出了继承!
比如:两个动物,鸟 和 狗
假设,它们都有名字,年龄,颜色,这些都是它们共有的属性,它们都会睡觉,这是它们共有的行为
//Bird.java
public class Bird {
public String name;
public int age;
public String color;
public void sleep() {
System.out.println(name+"正在睡觉");
}
public void eat() {
System.out.println(name+"正在吃麦子");
}
}
//Dog.java
public class Dog {
public String name;
public int age;
public String color;
public void sleep() {
System.out.println(name+"正在睡觉");
}
public void eat() {
System.out.println(name+"正在吃狗粮");
}
}
它们都有一些共性特征,但是,能不能把这些共性特征个抽取出来呢,所以面向对象就有了第二大特性继承,专门用来对共性的抽取,以达到代码的复用
1.2 继承的概念
继承:继承就是面向对象中类与类之间的一种关系,继承的类称为子类(派生类),而被继承的类称为父类,基类,或超类。通过继承,使得子类具有父类的属性和方法,同时子类也可以加入新的属性和方法,或者去修改父类中的属性和方法。继承主要解决的问题是:共性的抽取,实现代码的复用。
那么,对于上述的例子,就可以进行共性的的抽取:
//Animal.java
//父类
public class Animal {
public String name;
public int age;
public String color;
public void sleep() {
System.out.println(name+"正在睡觉");
}
}
//Dog.java
//子类
public class Dog extends Animal {
public void eat() {
System.out.println(name+"正在吃狗粮");
}
}
//Bird.java
//子类
public class Bird extends Animal {
public void eat() {
System.out.println(name+"正在吃麦子");
}
}
注意:
1、子类会将父类的成员方法或者成员变量继承到子类中。
2、子类中必须要有自己特有的方法或者属性,以体现出和父类的不同
关于继承有如下三点请记住:
1、子类可以拥有父类非private的属性和方法
2、子类可以拥有自己的属性和方法,即子类可以对父类进行扩展
3、子类可以用自己的方式实现父类的方法(涉及到重写,可以参看这篇文章)《走进对象村6》
1.4 父类成员访问
问题:子类将父类的成员属性和方法都继承了下来,那么如何对父类中的成员属性和方法进行访问呢?就分了以下几种情况:
1.4.1 子类中访问父类成员变量
1、子类和父类不存在同名成员变量的情况:
//父类Animal
public class Animal {
public String name;
public int age;
public String color;
public void sleep() {
System.out.println(name+"正在睡觉");
}
}
//子类Dog
public class Dog extends Animal {
public String sex;
public void eat() {
System.out.println(name+"正在吃狗粮");
}
}
//Test类用于测试
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
}
}
在Test类中实例化一个Dog对象时,经过调试,Dog对象里继承了父类Animal里的成员属性。
2、子类和父类存在同名情况
//父类Animal
public class Animal {
public String name = "小黄";//与子类成员变量同名
public int age = 18;
public String color = "黄色";
public void sleep() {
System.out.println(name+"正在睡觉");
}
}
//子类Dog
public class Dog extends Animal {
public String sex = "雄性";
public String name = "小黑";//定义一个和父类同名的成员变量
public void print() {
System.out.println(name);//访问的是父类的name?还是子类的name?
System.out.println(sex);
System.out.println(color);
System.out.println(age);
}
public void eat() {
System.out.println(name+"正在吃狗粮");
}
}
//Test类
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.print();//调用print方法
}
}
经过运行可以看出,当父类成员变量和子类成员变量同名的情况下,会遵循就近原则,优先访问的是子类的成员变量。
总结
1、如果访问成员变量子类中有,则优先访问自己的成员变量
2、如果访问的成员变量子类中没有,则访问父类继承下来的,如果父类中也没有,则编译报错
3、如果访问的成员变量与父类重名时,则优先访问自己的
问题:当子类和父类成员变量同名时,如果非要访问父类的成员变量,应该怎么办。
1.4.2 super 关键字
要访问父类中的name时,就要用到关键字super,通过super.(成员变量名)的方式访问,在name前面加了一个super. 就成功访问了父类中的同名成员变量
在这篇文章中《走进对象村1》《走进对象村3》说明了this的用法,但是,在这里,super 和 this 又有什么不一样的作用呢???
在子类Dog中,新增了和父类成员变量同名的age变量,在子类中有sex 、name、 age、 三个成员变量,在父类中有name、 age、 color三个成员变量。
1、当用super去访问同名的成员变量时,优先访问的是父类中的同名成员变量
2、用this访问同名成员变量时,优先访问子类的同名成员变量
3、用this访问不同名的成员变量时,会先在子类中寻找,如果子类中没有的话,会再去访问父类中的,如果父类中没有,则编译报错。
所以,super.name 访问的是父类中的小黄,this.sex访问的是子类中的雄性,this.color访问的是父类中的黄色,this.age访问的是子类中的11
//父类Animal
public class Animal {
public String name = "小黄";//与子类成员变量name同名
public int age = 18;
public String color = "黄色";
public void sleep() {
System.out.println(name+"正在睡觉");
}
}
//Dog类
public class Dog extends Animal {
public String sex = "雄性";
public String name = "小黑";
public int age = 11;//与父类中同名的成员变量age
public void print() {
System.out.println(super.name);
System.out.println(this.sex);
System.out.println(this.color);
System.out.println(this.age);
}
public void eat() {
System.out.println(name+"正在吃狗粮");
}
}
1.4.3 子类中访问父类成员方法
访问方法和访问成员属性简直是一模一样的
1、成员方法名字不同时:
当成员方法名不同时,优先访问子类中的,子类中没有再去访问父类中的,如果父类中没有,则编译报错
//父类Animal
public class Animal {
public String name;
public int age;
public String color;
//成员方法1
public void method1() {
System.out.println("这是父类中的成员方法");
}
}
//子类Dog
public class Dog extends Animal {
public String sex;
public String name;
public int age;
//成员方法2
public void method2() {
System.out.println("这是子类Dog中的成员方法");
}
public void testMethod() {
method2();
method1();
}
}
//Test类
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.testMethod();
}
}
2、方法名称相同时
//父类Animal
public class Animal {
public String name;
public int age;
public String color;
//与子类中方法名同名
public void method1() {
System.out.println("这是父类中的method1");
}
//与子类中方法名同名
public void method2() {
System.out.println("这是父类中的method2");
}
public void method3() {
System.out.println("这是父类中的method3");
}
//不带参数的method5
public void method5() {
System.out.println("这是父类的method5");
}
}
//子类Dog
public class Dog extends Animal {
public String sex;
public String name;
public int age;
public void method1() {
System.out.println("这是子类Dog中的method1");
}
public void method2() {
System.out.println("这是子类中的method2");
}
public void method4() {
System.out.println("这是子类中的method4");
}
//带参数的method5
public void method5(int a) {
System.out.println("这是子类中的method5"+a);
}
public void testMethod() {
super.method1();
method2();
this.method3();
this.method4();
//构成了方法的重载
method5(99);//进行了传参,访问子类中的method5
method5();//没有进行传参,访问父类中的method5
}
}
//Test类
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.testMethod();
}
}
这里的method5方法构成了重载,编译器会根据参数列表的内容在子类和父类中寻找。
总结:
1、通过子类对象访问子类和父类不同名的成员方法时,优先在子类中寻找,找到则访问,找不到则在父类中寻找,在父类中也找不到则编译报错
2、通过子类对象访问子类和父类同名成员方法时,如果父类和子类的同名方法的参数列表不同(则构成了重载),根据参数列表在子类和父类中寻找对应的方法进行访问,如果没有则报错
3、关于super 和 this的规则和上面的访问成员变量的规则一模一样,这里不在作解释!!!
注意:
super关键字的作用:在子类方法中访问父类的成员
1、只能在非静态方法中使用,因为super和this一样是依赖于对象的
2、在子类方法中,访问父类的成员变量和方法
1.5 子类构造方法
在继承时,如果子类有构造方法的话,在执行时,父类又会发生怎样的变化呢?,俗话说,现有父再有子,所以,**子类有构造对象时,在执行时,会先执行父类的构造方法,然后执行子类的构造方法。**代码验证:
当在父类中显示的写出一个带参数的构造方法时,那么,在子类中也需要写出一个构造方法,可以看到,子类中的构造方法也报出了错误,这是因为:在对子类进行构造时,会先对父类进行构造;但父类构造完成后,再构造子类。所以需要借助super关键字先对父类进行构造。
//父类Animal
public class Animal {
public String name;
public int age;
public String color;
//父类构造方法
public Animal(String name) {
this.name = name;
System.out.println("父类中的"+name);
}
}
//子类Dog
public class Dog extends Animal {
public String sex;
public String name;
public int age;
//子类构造方法
public Dog(String name) {
super(name);
this.name = name;
System.out.println("子类中的"+name);
}
}
//Test测试类
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("小黄");
}
}
通过运行发现:在实例化一个Dog对象时,传入一个“小黄”字符串,编译器在对子类Dog进行加载时,不会先构造子类,而是通过super(参数)关键字先对父类进行构造,而父类当中的this.name 中的this则表示当前类中的成员变量name;而子类当中的this.name则表示子类中的成员变量name;
当然,如果在代码中没有显示的写出构造方法,并不是代表它没有构造方法,编译器会自动的带有隐藏的无参的默认构造方法,所以,在写代码时,如果没有写构造方法,其实它自身是带有默认的无参的构造方法,当在父类中写出带参数的构造方法时,子类也需要写出带参数的构造方法;
注意:super()语句和this语句是不能同时出现的
再次强调:
在子类方法中,并没有写任何关于父类构造的代码,但是在构造子类对象时,先执行父类的构造方法,然后执行子类的构造方法,因为:子类对象中的成员是由两部分组成,一部分是父类继承下来的,另一部分是子类新增的部分,所以,在构造子类对象时,先要调用父类的构造方法,通过super() 进行调用,将从父类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增的成员初始化完整;
注意:
1、如果父类显示定义无参或者默认的构造方法时,在子类的构造方法中第一行带有不显示的super()调用,即调用父类的构造方法
2、如果父类中的构造方法是带有参数的,此时,需要手动的为子类定义带有参数的构造方法,并在子类构造方法中,定义合适super()语句来调用父类的构造方法;
3、super(参数)只能在子类构造方法中出现一次,并且不能和this()同时出现,但是通过super==.== 或者this==.== 某个成员变量和方法时,这样是不冲突的,在这一点不要搞混
4、在子类构造方法中,super()语句必须是子类构造方法中的第一条语句,同样,super==.== 和 this==.== 某个成员变量或成员方法时,也不一定要在第一行。
1.6 面试题this 和 super 的区别(重点)
1、this关键字的用法:
this是自身的一个对象,代表对象的本身,可以理解为:指向对象本身的一个指针
this的用法分为:
1.普通的直接引用
2.形参与成员名字重名,用this区分
3.引用本类的构造函数
this(参数):调用本类中的另一种形式的构造函数(应为构造函数的第一条语句)
关于this的详细用法可以参看这个文章《走进对象村1》《走进对象村3》
2、super关键字的用法
1.普通的直接引用,与this类似,super指向当前对象的父类引用
2.子类中的成员变量名和父类中的成员变量名相同时,用super进行区分
3.引用父类构造函数
super(参数):调用父类中的某一构造函数(应为构造函数的第一条语句)
this 和 super 的区别
不同之处:
super它引用的是当前对象的父类中的成员,this代表当前对象名
super()在子类中调用父类构造方法,this()在在本类内调用其他构造方法
相同之处:
super()和this()均需要放在构造方法的第一行
this() 和 super() 不能同时出现在同一个构造函数中
this和super都是依赖与对象的,均不可在static环境中使用,包括:static变量、static方法、static语句块
1.7 再谈代码块之间的运行顺序
在前面的文章中,讲过静态代码块、普通代码块、构造方法之间的运行顺序,想要复习的小伙伴可以看这篇文章《static》,那么,现在类之间有了继承关系,那它们之间的顺序又会是怎样的呢???
下面先来复习一下在没有继承关系时,它们之间的顺序:
class Big {
private String name;
private int age;
public Big(String name, int age) {
this.name = name;
this.age = age;
System.out.println("这是一个构造方法");
}
{
System.out.println("这是一个实例(构造)代码块");
}
static {
System.out.println("这是一个静态代码块");
}
}
public class Test1 {
public static void main(String[] args) {
Big big = new Big("小光",14);
System.out.println("===============");
Big big2 = new Big("小安",11);
}
}
运行结果:
1、静态代码块
2、实例代码块
3、构造方法
当第二次实例化对象是,静态代码块就不会再次执行了,因为静态代码块只在类初次加载时开辟空间。
在有继承关系是,它们的执行顺序如下:
//父类Animal
public class Animal {
public Animal() {
System.out.println("这是父类的构造方法");
}
{
System.out.println("这是父类的实例代码块");
}
static {
System.out.println("这是父类的静态代码块");
}
}
//子类Dog
public class Dog extends Animal {
public Dog() {
System.out.println("这是子类的构造方法");
}
{
System.out.println("这是子类的实例代码块");
}
static {
System.out.println("这是子类的静态代码块");
}
}
//Test类用于测试代码
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println("==============");
Dog dog2 = new Dog();
}
}
执行顺序:
1、父类的静态代码块
2、子类的静态代码块
3、父类的实例代码块
4、父类的构造方法
5、子类的实例代码块
6、子类的构造方法
第二次实例化对象时,就不会再次加载静态代码块了
1.8 protected关键字
在前面讲封装时,提到过几个关键字,现在理解过继承后,再来看这个被遗留的protected访问限定修饰符,protected有一个权限是可以访问不同包下的子类,下面代码演示:
情况1:同一个包中的子类
//在testdemo1包中
package testdemo1;
//父类Test1
public class Test1 {
public int a = 10;
protected int b = 20;
int c = 30;
private int d = 40;
}
//在testdemo2包中
package testdemo1;
//子类Test2继承了Test1
public class Test2 extends Test1{
public void method1() {
super.a = 1;//public修饰的a在相同包下的子类中可以访问
super.b = 2;//protected修饰的b在相同包下的子类中可与i访问
super.c = 3;//default修饰的c在相同包下的子类中可以访问
super.d = 4;//private修饰的d在相同包下的子类中不能访问,必须通过父类中提供的方法对d进行访问
}
}
情况2:不同包中的子类
package testdemo1;
public class Test1 {
public int a = 10;
protected int b = 20;
int c = 30;
private int d = 40;
}
package testdemo2;
import testdemo1.Test1;
public class Test3 extends Test1 {
public void method2() {
super.a = 1;//public修饰的a可以在不同包的子类中访问
super.b = 2;//protected修饰的b可以在不同包的子类中访问
super.c = 3;//default修饰的c不可以在不同包的子类中访问
super.d = 4;//private修饰的d不可以在不同包的子类中访问
}
}
情况3:不同包中的非子类
package testdemo1;
public class Test1 {
public int a = 10;
protected int b = 20;
int c = 30;
private int d = 40;
}
package testdemo2;
import testdemo1.Test1;
public class Test4 {
public static void main(String[] args) {
Test1 test1 = new Test1();
System.out.println(test1.a);//public修饰的a可以在不同包下的非子类中访问
System.out.println(test1.b);//protected修饰的b不能在不同包下的非子类中访问
System.out.println(test1.c);//default修饰的c不能在不同包下的非子类中访问
System.out.println(test1.d);//private修饰的d不能在不同包下的非子类中访问
}
}
**注意:**父类中private修饰成员变量虽然不能在子类中直接访问,但是也继承到了子类中,访问权限和继承是不混淆的。这四种访问限定修饰符不管用哪一个对成员修饰,都可以被继承。具体在代码中用哪一个修饰符要根据工作场景灵活运用。
1.9 继承方式
继承方式分为一下三种,根据我们的工作场景灵活的运用,注意,Java中是不支多继承的
2.0 final关键字
final关键字可以用来修饰成员变量,变量, 成员方法,以及类,它就像是C语言中的const关键字
1、被final修饰的成员变量不能被修改
2、被final修饰的变量不能被修改
3、被final修饰的成员方法不能被重写(在后续讲解)
4、被final修饰的类不能被继承
public class Test1 {
public final int a = 10;//被final修饰
}
public class Test2 extends Test1{
public void method1() {
super.a = 1;//a不能被赋值
}
}
//Test1类被final修饰
public final class Test1 {
public final int a = 10;
}
了子类中,访问权限和继承是不混淆的。这四种访问限定修饰符不管用哪一个对成员修饰,都可以被继承。具体在代码中用哪一个修饰符要根据工作场景灵活运用。
如果以上内容有任何错误,希望读者可以在评论区留言,但愿你看完本篇文章可以让你收获满满❤🌹