hi,大家好,今天为大家带来多态的讲解
💚💚💚💚1.对于引用的理解
💚💚💚💚2.多态的定义
💚💚💚💚3.多态的实现条件
💚💚💚💚4.重写
💚💚💚💚5.向上转型
💚💚💚💚6.动态绑定
💚💚💚💚7.静态绑定
💚💚💚💚8.向下转型
💚💚💚💚9.多态优缺点
💚💚💚💚9.重写的小点
今天的知识比较多,也会有代码帮助理解
1.为了加深对引用的理解,用一个swap交换函数来帮助理解
class MyValue { public int value; } public class TestDemo { public static void swap(MyValue my1,MyValue my2){ int tmp= my1.value; my1.value=my2.value; my2.value=tmp; } public static void main(String[] args) { MyValue myValue1=new MyValue(); MyValue myValue2=new MyValue(); myValue1.value =20; myValue2.value=30; swap(myValue1,myValue2); System.out.println( myValue1.value); System.out.println(myValue2.value); } }
运行结果如下
这个代码实现了交换,下面来画图理解一下引用
创建变量,在栈上开辟栈帧,new对象时在堆上
2 什么是多态呢???
多态就是多种形态,完成某个行为,最后的结果不尽相同。这个概念比较抽象,下面来举一个例子,相同的食材,给两个不同的人做,两个人做出来的味道是不一样的,比如做番茄炒蛋,有甜口的,还有咸口的,那么这就可以叫做多态,土豆可以做成炸土豆条,炸土豆片,这也是多态
3.多态的实现条件
实现多态需要三个条件
1.继承
2.重写
3.通过父类引用调用重写的方法
继承在上一篇已经介绍过了,我们现在直接说重写,说到重写,就得说一说重写的条件了
4.重写
1.方法名,返回值,参数列表必须完全一致,返回类型可一样也可不一样,但必须具有父子关系
2.重写又可以称作覆盖,是子类对父类的非静态,非构造,非fianl修饰,非private修饰的方法的重写
3.重写的方法访问修饰符的访问权限要大于等于父类的
4.父类被static、private修饰的方法、构造方法都不能被重写。
5.重写的时候,加上一个@Override注解,帮助我们检查重写的番薯方法是否有问题
下面来一段代码
6.被final 修饰的方法不能被重写,称作密封方法
class Animal{ public String name; public int age; public void eat(){ System.out.println(name+"正在吃饭"); } } class Dog extends Animal{ public void wangwang(){ System.out.println(name+"正在汪汪叫"); } @Override public void eat(){ System.out.println(name+"正在吃狗粮"); } } class Bird extends Animal{ public String wing; public void fly(){ System.out.println(name+"正在飞"); } public void eat(){ System.out.println(name+"正在吃鸟粮"); } } public class TestDemo2 { public static void main(String[] args) { Animal animal1=new Dog(); animal1.name="贝贝"; animal1.eat(); Animal animal2=new Bird(); animal2.name="喳喳"; animal2.eat(); } }
在这段代码中我们可以看到子类重写了父类的eat方法,那么疑问就来了,我们看到引用是父类引用,那么运行结果应该是父类的name,但是父类并没有起名字,所以是null+正在吃饭,那么这就要提到向上转型和动态绑定了
5.向上转型
在重写过程中,我们需要用到动态绑定,因为在第二点提到了是实现多态的第三点是,通过父类引用去调用重写的方法,所以要用到向上转型,向上转型就是从子类到父类,父类的引用指向子类的对象
如蓝笔画出来的那个一样,就是向上转型,父类引用指向子类对象
6.动态绑定
什么叫做动态绑定
就是在代码运行的时候很智能的帮我们调用子类中重写的父类方法,在编译的时候它并不能识别出调用哪个方法,但是在运行的时候就可以了,就像上面的代码运行结果一样,new 的是谁,就会调用哪个子类的重写方法
7.静态绑定
与动态绑定相对应得就是静态绑定,静态绑定又叫做早绑定,一般用于重载中,在编译的时候就知道应该执行哪一个方法,当然,在多态中一般是不用得,但是也能用,但是它不安全,一般不敢用,下面来写举个例子
class Animal{ public String name; public int age; public void eat(){ System.out.println(name+"正在吃饭"); } } class Dog extends Animal{ public void wangwang(){ System.out.println(name+"正在汪汪叫"); } @Override public void eat(){ System.out.println(name+"正在吃狗粮"); } } class Bird extends Animal{ public String wing; public void fly(){ System.out.println(name+"正在飞"); } @Override public void eat(){ System.out.println(name+"正在吃鸟粮"); } } class Cat extends Animal{ public void miaomiao(){ System.out.println(name+"正在喵喵叫"); } @Override public void eat() { System.out.println(name+"正在吃猫粮"); } } public class TestDemo2 { public static void main(String[] args) { Animal animal=new Dog(); Dog dog=(Dog)animal; dog.name="贝贝"; dog.eat(); dog.wangwang(); System.out.println("======="); Cat cat=(Cat)animal; cat.eat(); cat.miaomiao(); } public static void mai1(String[] args) { Animal animal1=new Dog(); animal1.name="贝贝"; animal1.eat(); Animal animal2=new Bird(); animal2.name="喳喳"; animal2.eat(); Animal animal3=new Cat(); animal3.name="小花"; animal3.eat(); } }
这个就是向下转型,很不安全,如下图,子类引用指向父类对象,报错了
但是有方法防止报错,使用库提供的instanceof函数,,现在对这个代码进行改良
这句话的意思是判断子类对象指向的引用是否包含Cat类,这样就不会报错了,但我们还是不提倡这样写
9.多态的优缺点
优点:能够降低代码的 "圈复杂度", 避免使用大量的 if - else
多态缺点
父类的属性不能重写,父类的构造方法不能重写
当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
10.重写的小点
避免在构造方法中调用重写的方法
class B { public B() { // do nothing func(); } public void func() { System.out.println("B.func()"); } } class D extends B { private int num = 1; public D () { super(); } @Override public void func() { //System.out.println("fafdadssa!!!!!"); System.out.println("D.func() " + num+" 因为父类此时还没有走完!"); } } public class Test4 { public static void main(String[] args) { D d = new D(); } }
当大家看到这段代码的时候大家觉得结果是几,一定有人以为是1,大漏特漏!!!
运行他看看
看,结果是0,为啥呢,因为父类都没有构造完成,所以不能到子类那一步,不要写这种代码,有坑,大家避雷!!!
下面写一个图形类的代码加深大家对多态的理解
class Shape { public void draw() { System.out.println("画图形!"); } } class Rect extends Shape { @Override public void draw() { System.out.println("画矩形!"); } } class Cycle extends Shape { @Override public void draw() { System.out.println("画圆!"); } } class Flower extends Shape { @Override public void draw() { System.out.println("❀!"); } } public class Test3 { public static void drawMap(Shape shape) { shape.draw(); } public static void drawMap() { Rect rect = new Rect(); Cycle cycle = new Cycle(); Flower flower = new Flower(); Shape[] shapes = {cycle,rect,cycle,rect,flower}; //int[] array = {1,2,3,4}; for(Shape shape : shapes) { shape.draw(); } } /* public static void drawMap3() { Shape rect = new Rect(); Shape cycle = new Cycle(); Shape flower = new Flower(); Shape[] shapes = {cycle,rect,cycle,rect,flower};//这里完成了向上转型!!!,和上面的写法其实是一样的 //int[] array = {1,2,3,4}; for(Shape shape : shapes) {//for each 遍历 shape.draw(); } } */ public static void main(String[] args) { drawMap(); //drawMap2() ; /*Rect rect = new Rect(); Cycle cycle = new Cycle(); drawMap(rect); drawMap(cycle); drawMap(new Flower());*/ } }
向上转型有三种方式
1.直接赋值
2.方法传参
3.方法返回值
显然上述代码用的是方法传参
好了,今天的讲解就到此结束,下一期为大家带来抽象类和接口!!!
886!!!🎉🎉🎉👀👀👀
备注:抽象类小部分知识在11.13日笔记