一、多态
1.多态的概念
多态的概念:通俗来说,就是多种形态, 具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状 态。简单说就是,同一件事情,发生在不同对象身上,就会产生不同的结果。多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法
2.多态实现条件
在 java 中要实现多态,必须要满足如下几个条件,缺一不可:1. 必须在继承体系下,向上转型
2. 子类必须要对父类中方法进行重写
3. 通过父类的引用调用重写的方法
完成以上3步,就会发生动态绑定,动态绑定就是多态的基础
二、向上转型 、向下转型
1)向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。
常见的3个向上转型的时机:1.直接赋值 : 语法格式:父类类型 对象名 = new 子类类型 ()2.方法的参数,传参的时候3.返回值
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(this.name+"正在吃饭!"); } } class Dog extends Animal{ public Dog(String name, int age) { //帮助初始化子类从父类继承过来的成员 super(name, age); } public void bark(){ System.out.println(this.name+"旺旺叫"); } }
父类本身时只能调用自己的成员方法和成员变量
1.直接赋值时 - 向上转型
2. 调用函数传参时 - 向上转型
3.返回值 向上转型
2)向下转型(不安全)
向下转型: 将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换
不安全的原因是:不同于向上转型,子类向上转型的父类是唯一的。而向下转型,父类下面的子类有多种,不唯一,不能保证向下转型的那一类就是之前向上转型的那个类。例如:猫、狗 向上转型 动物(唯一)动物 向下转型 可以有 猫、狗、狼、马等(不唯一)
1)animal引用Dog,向上转型,狗是动物然后dog引用animal,向下转型,刚刚好这个动物是狗,强制类型转换为Dog,再用dog接收2)animal引用Dog,向上转型,狗是动物然后Cat引用animal,向下转型,但此时这个动物是狗不是猫,强制类型转换为Cat运行报错可以观察到:只有animal引用的对象是cat对象的实例
animal才能 向下转型 为cat
三、重写 - 父类和子类同名的方法
重写 (override) :也称为覆盖。重写是子类对父类 非静态 、 非private修饰 , 非fifinal修饰 , 非构造方法 等的实现过程 进行重新编写, 返回值和形参都不能改变 。 即外壳不变,核心重写! 重写的好处在于子类可以根据需要,定义特定 于自己的行为。 也就是说子类能够根据需要实现父类的方法。如果在继承关系上,满足者3点
1.方法的返回类型一样
2.方法名一样
3.方法的参数列表一样
那么,就说这两个方法之间的关系是重写
【 重写的设计原则 】对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容。
1.一般来说 参数列表、 方法名 必须是一样的 ,返回值可以不同(但必须是父子关系)
1)参数列表 方法名 返回值 全都相同
没写子类eat方法时,父类调用的eat是自己的方法
当子类有一个和父类同名的eat方法时(重写)
此时通过父类对象的引用,调用这个重写的方法
2)参数列表、方法名 相同,返回值 不同 。(称为:协变类型)
2.被重写的方法的访问修饰限定符( 子类要大于等于父类的)
private < 默认 < protected < public
如果父类是默认的,那么子类就只能是 默认 、protected 、 public。
3. 父类 private 、static、final、构造方法 不能被重写
1)被private修饰的方法 ,不能被重写
因为本身private的范围就是,同一包中同一类
然后此时父类和子类是在两个不同的类中,固然错了。
2)被static修饰的方法,不能被重写
重写后的方法,最后是依赖父类对象来调用的,
而static不依赖于对象,而是用类来调用的,所以重写报错。
3)被final修饰的方法,不能被重写
被final修饰,就表示这个类不能被继承。
而重写是在继承的基础上进行的,所以重写报错了。
4)构造方法不能被重写
构造方法是每个类特有的,不能重写
4.避免在构造方法中调用重写的方法
构造 D 对象的同时 , 会调用 B 的构造方法 .B 的构造方法中调用了 func 方法 , 此时会触发动态绑定 , 会调用到 D 中的 func此时 D 对象自身还没有构造 , 此时 num 处在未初始化的状态 , 值为 0. 如果具备多态性, num 的值应该是 1.所以在构造函数内,尽量避免使用实例方法,除了fifinal 和 private 方法。结论 : " 用尽量简单的方式使对象进入可工作状态 ", 尽量不要在构造器中调用方法 ( 如果这个方法被子类重写 , 就会触 发动态绑定, 但是此时子类对象还没构造完成 ), 可能会出现一些隐藏的但是又极难发现的问题 .
5.建议重写之后,在重写的方法上面写一个@Override
这是一个注解,提示这个方法是重写的
如果重写错误了,会给予提示
6.快捷键 生成 重写方法
按需要字节选择要重写的方法
回顾之前的 重写toString 方法
7.重写和重载的区别
四、动态绑定 和 静态绑定
1.动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体 调用那个类的方法。
打开字节码文件
在这个路径里,输入cmd
找到main方法
2.静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。
典型代表函数重载。
例如
五、多态的应用
当父类引用,引用的子类对象不一样的时候,调用这个重写的方法,所表现出来的行为时不一样的!
这种思想就时 多态!!!
【 使用多态的好处 】1. 能够降低代码的 " 圈复杂度 ", 避免使用大量的 if - else什么叫 " 圈复杂度 " ?圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙 , 那么就比较简单容易理解 . 而如 果有很多的条件分支或者循环语句, 就认为理解起来更复杂 .因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 " 圈复杂度 ".如果一个方法的圈复杂度太高, 就需要考虑重构 .不同公司对于代码的圈复杂度的规范不一样 . 一般不会超过 10
1.画图形
1)普通if方法
class Shape{
public void draw(){
System.out.println("画一个图形");
}
}
class Rect extends Shape{
@Override
public void draw() {
System.out.println("矩形");
}
}
class Triangle extends Shape{
@Override
public void draw() {
System.out.println("三角形");
}
}
class Cycle extends Shape{
@Override
public void draw() {
System.out.println("圆形");
}
}
public class Test2 {
public static void main(String[] args) {
Rect rect = new Rect();
Cycle cycle = new Cycle();
Triangle triangle = new Triangle();
String[] strings = {"cycle","rect","cycle","rect","triangle"};
for (String x:
strings) {
if(x.equals("cycle")){
cycle.draw();
} else if (x.equals("rect")) {
rect.draw();
}else {
triangle.draw();
}
}
}
}
2)多态方法
class Shape{
public void draw(){
System.out.println("画一个图形");
}
}
class Rect extends Shape{
@Override
public void draw() {
System.out.println("矩形");
}
}
class Triangle extends Shape{
@Override
public void draw() {
System.out.println("三角形");
}
}
class Cycle extends Shape{
@Override
public void draw() {
System.out.println("圆形");
}
}
public class Test2 {
public static void main(String[] args) {
// Shape shape = new Cycle();
// Shape shape1 = new Rect();
// Shape shape2 = new Triangle();
Shape[] shapes = {new Cycle(),new Rect(),new Cycle(),new Rect(),new Triangle()};
for (Shape shape:shapes){
shape.draw();
}
}
}