前言:
🌈✨小怡给大家分享了Java的类和对象,今天小怡给大家分享的是继承和多态。
1.继承
1.1 为什么需要继承
Java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是现实世界错综复杂,事物之间可能会存在一些关联,那设计程序时就需要考虑,比如猫和狗:
通过上述代码我们可以发现,猫和狗的类存在大量重复,那么能否将共性抽取呢?面向对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用。
1.2 继承概念
继承机制: 是面向对象程序设计使代码可以复用的最重要手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
我们可以把他们的共性抽取出来,建一个Animal类,其中,Animal类称为父类/基类或者超类,Dog和Cat称为Animal的子类/派生类。继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。
1.3 继承的语法
在Java中如果要表示类之间的继承关系,需要借助extends关键字:
修饰符 class 子类 extends 父类{
//...
}
对上面的场景重新设计一下:
注意:
1.子类会将父类中的成员变量或者成员方法继承到子类中了;
2.子类继承到父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了。
1.4 父类成员访问
1.4.1 子类中访问父类的成员变量
1. 子类和父类不存在同名成员变量
public class Base{
int a;
int b;
}
public class Derived extends Base{
int c;
public void method(){
a=10;//访问父类的a
b=20;//访问父类的b
c=30;//访问子类自己的c
}
}
2. 子类和父类成员变量同名
public class Base{
int a;
int b;
int c;
}
public class Derived extends Base{
int a;
char b;
public void method(){
a=100;
b=101;
c=102;
}
}
大原则:就近原则。成员变量访问遵循就近原则,自己优先自己的,没有就向父类中找。
在子类方法中 或者 通过子类对象访问成员时:
- 如果访问的成员变量子类中有,优先访问自己的成员变量;
- 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错;
- 如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
1.4.2 子类中访问父类的成员方法
1.成员方法名字不同
public class Base{
public void methodA(){
System.out println("Base中的methodA()");
}
}
public class Derived extends Base{
public void methodB(){
System.out.println("Derived中的methodB()");
}
public void methodC(){
methodB();
methodA();
}
}
总结:成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有时再到父类中找,如果父类中没有则报错。
2.成员方法名字相同
public class Base {
int a;
int b;
public void methodA(){
System.out.println("Base中的methodA()");
}
public void methodB(){
System.out.println("Base中的methodB()");
}
}
public class Derived extends Base {
int a;
char b;
public void methodA(int a){
System.out.println("Derived中的method(int)");
}
public void methodB(){
System.out.println("Derived中的methodB()");
}
public void methodC(){
methodA();
methodA(20);
methodB();
}
}
[说明]
- 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错;
- 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法传递的参数选择合适的方法访问,如果没有则报错。
1.5 super关键字
Java提供了super关键字,主要作用:在子类方法中访问父类的成员。
super 只能指代当前类的父类,不能指代父类的父类,甚至继续向上指代。当没有提供任何的构造方法的时候,Java中会提供目前屏蔽起来的代码。但只要你写了任何已给构造方法,都不会给你自动提供。
【注意事项】
1.只能在非静态方法中使用;
2.在子类方法中,访问父类的成员变量和方法。
1.6 子类构造方法
子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。
public class Base{
public Base(){
System.out.println("Base()");
}
}
public class Derived extends Bae{
public Derived(){
System.out.println("Derived()");
}
}
public class Test{
public static void main(String[] args){
Derived d=new Derived();
}
}
在子类构造方法中并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法,因为:子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分。父子父子肯定是先有父再有子,所以在构造子类对象的时候,先调用基类的构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整。
注意:
1.若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法;
2.如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败;
3.在子类构造方法中,super()调用父类构造时,必须是子类构造函数中的第一条语句;
4.super()只能在子类构造方法中出现一次,并且不能和this同时出现。
1.7 super和this
【相同点】
1.都是Java中的关键字;
2.只能在类的非静态方法中使用,用来访问非静态成员方法和字段;
在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在。
【不同点】
1.this时当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象从父类继承下来部分成员的引用;
2.在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性;
3.在构造方法中:this()用于调用本类构造方法,super()用于调用父类构造方法,两种调用不能同时在构造方法中出现;
4.构造方法中一定会存在super()的调用,用户没有写编译器也会增加,但是this()用户不写则没有。
1.8 再谈初始化
在没有继承关系时的执行顺序:
1.静态代码块先执行,且只执行一次,在类加载阶段执行。
2.当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行。
有继承关系时的执行顺序:
先执行父类和子类的静态代码块,再执行父类的实例和构造代码块,最后执行子类的实例和构造代码块。
1.9 final关键字
final关键可以用来修饰变量、成员方法以及类。
1.修饰变量或字段,表示常量,即不能修改:
final int a=10;
2.修饰类:表示此类不能被继承:
final public class Animal{
...
}
3.修饰方法表示该方法不能被重写(后续介绍)
1.10 继承与组合
和继承类似,组合也是一种表达类之间关系的方式,也是能够达到代码重用的效果。组合并没有涉及到特殊的语法,仅仅是将一个类的实例作为另一个类的字段。
继承表示对象之间的关系是is-a的关系:比如:猫是动物、狗是动物;
组合表示对象之间的关系是has-a的关系:比如:汽车。
2.多态
2.1 多态的概念
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。总的来说:同一件事情,发生在不同对象身上,就会产生不同的结果。
2.2 多态实现条件
在Java中要实现多态,必须满足下面几个条件,缺一不可:
1.必须在继承体系下;
2.子类必须要对父类中方法进行重写;
3.通过父类的引用调用重写的方法。
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。
a这个引用调用eat方法可能会有多种不同的表现,这种行为就称为多态。
2.3 重写
重写(override):也成为覆盖。重写是子类对父类非静态、非private修饰、非final修饰,非构造方法等的实现过程进行重新编写,返回值和形参都不能改变。即外壳不变,核心重写。重写的好处在于子类可以根据需要,定义特定于自己的行为。也就是说子类能够根据需要实现父类的方法。
【方法重写的规则】
- 子类在重写父类的方法时,一般必须与父类方法原型一致:返回值类型 方法名 (参数列表)要完全一致;
- 被重写的方法返回值类型可以不同,但是必须是具有父子关系的;
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为protected。
- 父类被static、private修饰的方法、构造方法都不能被重写 ;
- 重写的方法,可以使用 @override 注解来显式指定。有了这个注解能帮我们进行一些合法性校验。
【重写和重载的区别】
区别点 | 重写(override) | 重载(overload) |
---|---|---|
参数列表 | 一定不能修改 | 必须修改 |
返回类型 | 一定不能修改,除非可以构成父子关系 | 可以修改 |
访问限定符 | 一定不能做更严格的限制,可以降低限制 | 可以修改 |
即:方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
【重写的设计规则】
对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容。
静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用哪个方法。典型代表函数重载。
动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能确定具体调用哪个类的方法。
2.4 向上转移和向下转型
2.4.1 向上转型
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。
语法格式:
父类类型 对象名=new 子类类型();
eg:Animal animal=new Cat(“元宝”,2);
animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。
【使用场景】
1.直接赋值;
2.方法传参;
3.方法返回。
需要注意的是,通过父类的引用,调用子类特有的方法是无法直接调用的,这里只能调用父类自己的,这也是向上转型的缺点。
【优缺点】
优点:让代码实现更简单灵活;
缺点:不能调用到子类特有的方法。
2.4.2 向下转型
将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用和再还原为子类对象即可,即向下转换。
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛出异常。Java中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为true,则可以安全转换。
if(animal instanceof Cat){
...
}
2.5 多态的优缺点
【好处】
1.能够降低代码的圈复杂度,避免使用大量的if-else。
圈复杂度:是一种描述一段代码复杂程度的方式。一段代码如果平铺直叙,那么久比较简单容易理解。而如果有很多的条件分支或者循环语句,就认为理解起来更复杂。因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数,这个个数就称为“圈复杂度”,如果一个方法的圈复杂度太高,就需要考虑重构。
2.可扩展能力更强。
【缺陷】
代码运行效率降低。
1.属性没有多态性,当父类和子类都有同名属性的时候,通过父类引用,只能引用父亲自己的成员属性;
2.构造方法没有多态性。
🌈✨今天的分享就到这里啦,小怡和大家一起分享一起进步一起学习,“理想是指路明灯。没有理想,就没有坚定的方向;而没有方向,就没有生活”。