文章目录
- 前言
- 一、继承
- 1.继承概念
- 1.1 继承的语法
- 1.2 父类成员方法
- 1.2.1 子类访问父类的成员变量
- 1.2.2 子类访问父类的成员方法
- 1.3 super、this 关键字
- 1.4 子类构造方法
- 1.5 继承的方式
- 1.6 final 关键字
- 1.7 继承与组合
- 二、多态
- 2.1 多态的概念
- 2.2 多态实现的条件
- 2.3 对重写的认识
- 2.3.1 重写与重载
- 2.4 向上转型与向下转型
- 2.4.1 向上转型
- 2.4.2 向下转型
- 2.5 避免在构造方法中调用重写方法
- 总结
✨✨✨学习的道路很枯燥,希望我们能并肩走下来!
编程真是一件很奇妙的东西。你只是浅尝辄止,那么只会觉得枯燥乏味,像对待任务似的应付它。但你如果深入探索,就会发现其中的奇妙,了解许多所不知道的原理。知识的力量让你沉醉,甘愿深陷其中并发现宝藏。
前言
本篇是对继承的介绍,super的使用与this的区别,final关键字,继承方式的讲解和多态的实现,重写,向上/下转型等。如有错误,请在评论区指正,让我们一起交流,共同进步!
本文开始
一、继承
1.继承概念
继承:用于对共性进行抽取,实现代码的复用。
继承机制:允许程序员在保持原有类特性的基础上进行拓展,增加新的功能。这样产生类称为派生、子类。原有的类称为父类,基类,超类。
1.1 继承的语法
使用extends关键字
修饰符 class 子类 extends 父类 {
}
1.子类会将父类中的成员变量或者成员方法继承到子类中了
2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与父(基类的不同,否则就没有必要继承了
1.2 父类成员方法
1.2.1 子类访问父类的成员变量
1.子类和父类不存在同名方法
class Base {
int a = 1;
int b;
}
class Derived extends Base {
int c;
public void func() {
System.out.println(a);//访问继承父类的a
System.out.println(c);//访问子类自己的c
}
}
2.子类和父类成员变量同名
class Base {
int a;
int b;
int c = 9;
}
class Derived extends Base {
int c = 10;
public void func() {
System.out.println(a);
System.out.println(b);
System.out.println(c);
//System.out.println(d);//报错,子类父类都没有定义
}
}
子类和父类有同名变量,依照就近原则,先使用子类自己的变量,子类没有在找父类的变量!
【注】在子类方法(对象)中,访问成员:
1.访问成员变量子类中有,就先访问子类自己的
2.访问成员变量子类中无,就先访问父类的,父类没有,就报错
3.访问成员变量父类子类同名的成员变量,就先访问子类自己的
1.2.2 子类访问父类的成员方法
1.子类和父类成员方法名相同
class Base {
public void methodBase() {
System.out.println("Base里的methodBase()");
}
}
class Derived extends Base {
public void menthodBase() {
System.out.println("Derived里的menthodBase()");
}
}
2.子类和父类成员方法名不同
优先访问自己的成员方法,自己没有时再到父类中找,如果父类中也没有则报错。
class Base {
int a;
int b;
int c = 9;
public void methodBase() {
System.out.println("Base里的methodBase()");
}
}
class Derived extends Base {
int c = 10;
public void methodDerived() {
System.out.println("Derived()");
}
}
【小结:】
1.子类对象访问父类与子类的 同名方法,根据调用方法传递参数列表选择访问(重载),没有则报错
2.子类对象访问父类与子类的 不同名方法时,优先在子类中找,找不到,在父类找,找到访问,找不到报错
1.3 super、this 关键字
super作用 : 子类和父类中存在相同的成员时,使用super,super可以在子类方法中访问父类的成员。
super调用在子类中调用了父类的成员变量和方法
class Base {
int a;
int b;
int c = 9;
public void methodBase() {
System.out.println("Base里的methodBase()");
}
}
class Derived extends Base {
int c = 10;
public void methodBase() {
System.out.println("Derived里的menthodBase()");
super.methodBase();
}
public void func() {
System.out.println("Derived中的 c = " + c);
System.out.println("Base中的 c = " + super.c);
}
}
代码结果:
【注】
1.在子类方法中,如果想要明确访问父类中成员时,借助super关键字
2.只能在非静态方法中使用
this,super不同点:
1.this是当前对象的引用,当前对象即调用实例方法的对象;super相当于是子类对象中从父类继承下来部分成
员的引用
2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
3. 在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造方法中出现
4.构造方法中一定会存在super()的调用,用户没有写编译器也会增加,但是this()用户不写则没有
super,this相同点:
1.只能在类的非静态方法中使用,用来访问非静态成员方法和字段
2.在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
1.4 子类构造方法
子类对象构造时,需要先调用父类构造方法,然后执行子类的构造方法。(先构造父类,在构造子类)
1.父类定义无参构造方法或默认构造方法,在子类构造方法中第一行默认含有super()调用;(父类或者子类不写构造方法,默认父类子类无参构造)
class A {
public A() {
System.out.println("A的无参构造");
}
}
class B extends A{
public B() {
super();//调用父类无参构造
System.out.println("B的无参构造");
}
}
public class Test {
public static void main(String[] args) {
new B();
}
}
代码结果:
2.如果父类含有带参数的构造方法,此时子类构造需要选择合适的父类构造方法调用;(父类如果写了含参数的构造方法,则就没有无参的构造方法,如果需要用需要自己写无参构造)
3.在子类构造方法中,super()调用父类构造,必须是子类构造函数中的第一条语句;
4.super()只能在子类构造方法中出现一次,并且不能和this同时使用;(this(),super()在构造方法中都必须在第一行,所有不能共存)
1.5 继承的方式
1.单继承:
B =》A
class A {
...
}
class B extends A{
}
2.多层继承
C =》B =》A
class A {
....
}
class B extends A{
....
}
class C extends B {
....
}
3.不同类继承同一个类
同一包中不同java文件中
public class A {
}
public class B extends A {
}
public class C extends A {
}
4.多继承(不支持)=》err
JAVA中不支持多继承
public class A {
}
public class B {
}
public class C extends A,B {
}
1.6 final 关键字
1.final 修饰变量或字段,表示常量(不能修改)
final int a = 10;
a = 20;
2.final 修饰类:表示类不能被继承
final public class A {
}
//err
public class B extends A {
}
3.final 修饰方法:不能被重写
1.7 继承与组合
组合:仅仅将一个类的示例作为另一个类的字段,为了达到代码的重用
继承:表示对象之间是 is-a 的关系,比如:狗是动物,猫是动物
组合:表示对象之间是 has-a 的关系,比如:学校作为类,学生和老师都是学校里的示例字段
class Stduent {
...
}
class Teacher {
...
}
class School {
Stduent[] stduents = new Stduent[3];
Teacher[] teachers = new Teacher[3];
}
public class TestDemo {
public static void main(String[] args) {
School school = new School();
}
}
二、多态
2.1 多态的概念
多态 :为了完成某个行为,不同的对象去完成时会产生不同的状态。(同一件事,发生在不同对象上,会产生不同的结果)
例如:动物吃东西
狗吃狗粮,鸟吃鸟粮,鱼吃鱼粮。同样是吃饭的行为,但他们吃的什么却不同。
2.2 多态实现的条件
Java实现多态的条件:
- 必须在继承体系下(子类继承父类)
- 子类必须要对父类中方法进行重写
- 通过父类的引用 , 调用重写的方法
多态示例图示:
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。
发生重写,子类发生不同的状态,7吃狗粮, qq吃鸟粮
我们下面讲解为什么重写后会调用子类的重写的eat()方法,为什么不调用父类的?
2.3 对重写的认识
重写(override):也称为覆盖。即外壳不变,核心重写! 也就是说子类能够根据需要实现父类的方法。
重写是子类对 ①父类非静态、②非private修饰,③非final修饰,④非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。
被final修饰的方法称为密封方法。
重写规则:
- 子类重写父类,方法的返回值类型、方法名、参数列表相同
- 具有父子类关系,被重写的方法返回值类型可以不同
- 子类访问权限要 大于 父类的访问权限;父类方法是public修饰的,子类重写不能是protected
- 父类中被①static②private③构造方法都不能被重写
- 重写的方法,可以使用 @Override 来注释,可以帮助我们进行合法性校验。如果重写的方法名拼写错误,编译器就会发并报错。
发生动态绑定:编译的时候不确定,在从重写的时候,调用子类的重写方法
静态绑定: 重载,编译的时候已经确定调用那个方法
问题1:为什么重写后会调用子类的重写的eat()方法,为什么不调用父类的?
这是因为在重写的时候会发生动态绑定,会在程序运行的时候,帮我们调用了重写的方法。
问题2:为什么不是编译的时候调用子类的重写方法呢?
我们可以通过class字节码文件来看一下
找到字节码文件,通过命令行打开,输入:javap - c 文件名
通过查看字节码文件可知,编译时调用的还是父类的eat()方法。
2.3.1 重写与重载
2.4 向上转型与向下转型
2.4.1 向上转型
向上转型:父类对象引用子类的对象
语法格式:父类类型 对象名 = new 子类类型();
向上转型缺点:不能调用子类特有的方法
三种使用:
1.直接赋值
Animal animal = new Dog();
public static void main(String[] args) {
Animal animal = new Dog();
//animal这个引用只能引用Animal这个类里面的方法
//发生向上转型后:父类的引用只能访问父类自己的成员。不能访问到子类特有的成员
//相对于子类访问的范围更大一些
animal.name = " 7 ";
animal.eat();
//animal.wang();不能访问子类的方法
Animal animal1 = new Bird();
//向上转型。前提: Bird继承Animal
animal1.name = "qq";
animal1.eat();
}
2.方法传参
func(dog)
public static void func(Animal animal) {
//方法传参
}
public static void main(String[] args) {
Dog dog = new Dog();
func(dog);
}
3.返回值
Animal animal = func2();
public static Animal func2() {
//返回值
return new Dog();
}
2.4.2 向下转型
向下转型:为了调用子类特有的方法,我们可以将父类引用还原为子类对象
public static void main(String[] args) {
Animal animal = new Dog();
//向下转型:不安全
System.out.println("=========");
Bird bird = (Bird) animal;
bird.fly();
}
向下转型不安全,编译没问题,但是运行时会报错
代码修改:
public static void main(String[] args) {
Animal animal = new Dog();
//向下转型:不安全,需要判断使用关键字instanceof
//父类的类型给到子类
Dog dog = (Dog) animal;
dog.name = "7";
dog.wang();
System.out.println("=========");
if(animal instanceof Bird) {
//判断animal是不是引用了Bird的对象
Bird bird = (Bird) animal;
bird.fly();
}
}
判断后结果就不会报错了
2.5 避免在构造方法中调用重写方法
class B {
public B() {
// do nothing
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() " + num);
}
}
public class TestDemo {
public static void main(String[] args) {
D d = new D();
}
}
代码结果:
结果可知:
num值为0,并没有赋值为1,这是因为在构造器中调用方法所导致的;
代码执行过程:
1.构造D对象的同时调用B构造方法
2.B 的构造方法调用func()方法,此时会触发动态绑定,会调用D中的func()
3.此时D 对象自身还没有构造,此时num处于为初始化状态,值默认0,如果具备多态性,num的值应该是1
【注】尽量不要在构造器中调用方法!!!
当在父类的构造方法当中,去调用父类和子类重写的方法的时候,会发生动态内存绑定,会调用子类重写的同名方法,而产生问题!
总结
✨✨✨各位读友,本篇分享到内容如果对你有帮助给个👍赞鼓励一下吧!!
感谢每一位一起走到这的伙伴,我们可以一起交流进步!!!一起加油吧!!!