s实践是检验真理的唯一标准!
多态:一个引用可以表现出多种行为/特性。 |
向上转型 ::产生的原因 | |
类名称 类引用 =new 该类对象; | |
父类名称 父类对象 =new 子类对象; |
之前我创建对象的代码是: 当前类名称,当前类引用,=new 当前类对象();
鸭子的父类是鸟,鸟的父类是动物。
现在我让 Animal bird1= new bird(); 从右向左去读,鸟是一个动物, 这是不错的。
现在我让 Bird bird1= new Duck(); 从右向左去读,鸭子是一个鸟,这是不错的。
现在我让Animal animal1=new Duck(); 从右向左去读,鸭子是一个动物,这也是不错的。
推论:只要是有继承的传递关系,都可以由父类名称去new一个子类对象。
初始化 |
然后让Bird 继承Animal, 让Duck继承Bird。 再创建一个Test类。
多态最大的意义在:参数统一化,降低使用难度。
假如在Test中创建一个fun()方法来接收Animal以及其子类的对象作为参数,如果没有向上转型:如下
自然界Animal的对象多如繁星,由于每个动物类型不一样,有多少子类方法就得重载多少次!
而且使用者要先知道Animal有的子类多少个对象,如下:
向上转型产生的原因:
写一行 fun(先祖) 就行了,省略掉了下面的两行。
问:当Animal,有一个新的子类的时候很容易扩展吗?
比如唐老鸭是鸭子的子类,无非多双筷子,fun不用动的。如下:
下一步我在每一个类中都假如一个eat方法,Animal 的public 会让所有子类继承,每个子类都有自己的eat方法。(eat方法就是重写方法,后面会讲)
public void eat(){
System.out.println("Animal 类的 eat方法");
}
在Test 的fun中调用eat方法:
输出结果表示:fun中animal 局部变量的引用调用eat方法时,当传入不同的对象时,表现出来不同的eat方法行为---> 多态性
同一个引用(变量名称),同一个方法名称,根据对象的不同表现出来了不同的行为---> 多态性
下面讲讲为什么会这样? 这个方法的本质就在于方法重写。
《下面看看什么是方法重写》
在说方法重写之前,复习下方法重载。
发生在同一个类中,定义了若干个方法名称相同,参数列表不同的一组方法---重载方法 (overload)根据不同的参数调用不同的方法。
发生在有继承关系的类之间,子类定义一种方法,该方法只有权限和父类不同,此方法就是重写。
(override)
那么,fun(Animal animal){animal.eat()}调用的是谁???
代码附上,复制到本地看看结果就不需要解释了:
代码段1
package polymorphism; public class Test { public static void main(String[] args) { Animal animal =new Animal(); Animal animal1 =new Bird(); Animal animal2 =new Duck(); Animal animal3 =new MrsTang(); //调用fun传入 animal1,2,3 fun(animal); fun(animal1); fun(animal2); fun(animal3); } public static void fun(Animal animal){ animal.eat(); } }
代码段2
package polymorphism; public class Bird extends Animal{ public void eat(){ System.out.println("Bird 类的 eat方法"); } }
代码段3
package polymorphism; public class MrsTang extends Duck{ public void eat(){ System.out.println("MrsTang 类的 eat方法"); } }
代码段4
package polymorphism; public class Duck extends Bird{ public void eat(){ System.out.println("Duck 类的 eat方法"); } }
代码段5
package polymorphism; public class Animal { public void eat(){ System.out.println("Animal 类的 eat方法"); } }
回答:调用的是重写的eat方法。
那么如果子类没有重写这个方法,我该调用谁呢?
try:创建一个Animal的子类 Me
package polymorphism; public class Me extends Animal{ }
package polymorphism; public class Test { public static void main(String[] args) { Animal animal =new Animal(); Animal animal1 =new Bird(); Animal animal2 =new Duck(); Animal animal3 =new MrsTang(); Animal animal4 =new Me(); //调用fun传入 animal1,2,3 fun(animal); fun(animal1); fun(animal2); fun(animal3); fun(animal4); } public static void fun(Animal animal){ animal.eat(); } }
Animal 类的 eat方法 Bird 类的 eat方法 Duck 类的 eat方法 MrsTang 类的 eat方法 Animal 类的 eat方法
看到输出的时候:如果Me类没有重写eat方法,输出的就是“Animal 类的 eat方法”,就是说调用的是父类的eat方法。
如果Me是MrsTang的子类呢?
Try:
package polymorphism; public class Me extends MrsTang{ }
Animal 类的 eat方法 Bird 类的 eat方法 Duck 类的 eat方法 MrsTang 类的 eat方法 MrsTang 类的 eat方法
果然不错,输出的就是直接父类的eat方法。
那么如果直接父类也没有重写eat方法呢?
try:
package polymorphism; public class Test { public static void main(String[] args) { Animal animal =new Animal(); Animal animal1 =new Bird(); Animal animal2 =new Duck(); //这两行不重写时 Animal animal3 =new MrsTang(); Animal animal4 =new Me(); fun(animal); fun(animal1); fun(animal2); fun(animal3); fun(animal4); } public static void fun(Animal animal){ animal.eat(); } }
package polymorphism; public class Me extends MrsTang{ }
package polymorphism; public class MrsTang extends Duck{ // public void eat(){ // System.out.println("MrsTang 类的 eat方法"); // } }
Animal 类的 eat方法
Bird 类的 eat方法
Duck 类的 eat方法
Duck 类的 eat方法
Duck 类的 eat方法结果显示:Me 的直接父类MrsTang没有重写时,两个子类都调用了Duck的重写。
最极端的没意义,就不试了。
那么,当发生重写时,权限要求子类权限大于等于父类权限。private<defalt<protected<public
(不包含private权限)
Try:
结果显示:方法权限有要求子类大于等于父类
那么,请你试试父类fun权限private,子类public,
try:
IEDA不让用private
删掉改成default权限可以。
之前我说过,private 中使用Setter 和Getter方法,子类才有权限访问父类的方法。
所以我把Test的main方法放到Animal 这个类下,让他可见,然后注释掉Test类
try:
package polymorphism; public class Animal { private void eat(){ System.out.println("Animal 类的 eat方法"); } public static void main(String[] args) { Animal animal =new Animal(); Animal animal1 =new Bird(); Animal animal2 =new Duck(); //这两行不重写时 Animal animal3 =new MrsTang(); Animal animal4 =new Me(); fun(animal); fun(animal1); fun(animal2); fun(animal3); fun(animal4); } public static void fun(Animal animal){ animal.eat(); } }
package polymorphism; public class Bird extends Animal{ public void eat(){ System.out.println("Bird 类的 eat方法"); } }
package polymorphism; public class MrsTang extends Duck{ public void eat(){ System.out.println("MrsTang 类的 eat方法"); } }
package polymorphism; public class Duck extends Bird{ public void eat(){ System.out.println("Duck 类的 eat方法"); } }
package polymorphism; public class Me extends MrsTang{ }
Animal 类的 eat方法 Animal 类的 eat方法 Animal 类的 eat方法 Animal 类的 eat方法 Animal 类的 eat方法
@overide 校验重写命令; |
为什么重写报错?
因为:私有权限的方法没法被重写。
方法重写能不能重写一个static方法? |
答:多态的本质--父类调用了多个子类的对象,子类对象重写相应的方法, 才有 了特殊的行为。而static 和对象无关。方法重写只发生在普通方法中。
所以重写方法不能发生在static方法中。
重载 | 重写 | |
概念 | 方法名称相同,参数的类型及个数不同 | 方法名称,返回值类型,参数的类型及个数完全相同 |
范围 | 一个类 | 继承关系 |
限制 | 没有权限要求 | 被重写的方法不能拥有比父类更严格的访问控制权限,不含private。 |
static | 没要求 | 不能重写static方法 |
记住:使用父类调用普通方法时,若子类重写了该方法,则调用该对象所在子类重写后的方法。
方法传参和引用赋值 | |
方法的返回值 | 返回一个Animal类型的对象,起名叫test,但是实际代码实现的时候ruturn一个子类的对象。第一张图是优化第二张图的。 |
默认先调用父类的无参构造fun,然后调用子类重写后的fun,然后输出num=0 ,注意此时子类对象还没初始化,现处于super()阶段,num自然是0,
package polymorphism; //父类-唐老鸭 public class MrsTang { public MrsTang(){fun();} public void fun(){System.out.println("MrsTang 类的 fun方法");} }
package polymorphism; //子类-米老鼠 public class Mouse extends MrsTang{ private int num=10; public void fun(){System.out.println("Mouse.fun,num="+num);} public static void main(String[] args) { Mouse mouse=new Mouse(); mouse.fun(); } }
拿去DeBug看看。
package polymorphism; public class MrsTang extends Animal{ public MrsTang(){eat(); System.out.println("MrsTang类的 eat方法");} public void play(){System.out.println("MrsTang 类独有的 play方法");} }
package polymorphism; public class Animal { public void eat(){ System.out.println("Animal 类的 eat方法"); } public static void main(String[] args) { Animal animal=new MrsTang(); animal.eat(); } }
Animal 类的 eat方法
MrsTang类的 eat方法
Animal 类的 eat方法
如果我调用子类拓展的play方法,发现调用不了 那么注意:
类名称 引用名称 =new 类 实例();
引用名称 . 方法名称();
能通过 " . " 访问的方法 类名称 说了算,能访问的这些方法必须都在类中定义过,编译器现在类中查找是否包含指定方法。
至于这个方法到底表现出什么样子,我们后边的 实例 方法说了算。
实例会重写这个方法,表现出新特性。
总之,先判断有没有这个调用,如果有,再判断是啥样子。
回到之前的play调用,有什么方法可以让我调用子类的拓展方法吗?
TRY TRY TRY:
Animal animal =new 唐老鸭();
animal.play();
animal这个引用是披着鸭子皮的动物,本质上是一个鸭子,披上了Animal 的外衣,此时只能调用Animal中定义的方法,比如这个鸭子它不会下蛋,不会浮水,不会嘎嘎嘎,但是会吃喝拉撒睡。
如果此时要调用play方法,需要脱掉这层外衣,还原为子类引用--------向下转型
子类名称 自类引用 =(子类名称) 父类引用
MrsTang mrstang =(MrsTang) animal; // 脱掉animal对应的对象的外衣还原为具体的子类引用
向下转型没有new 新的对象,
将父类引用强制类型转换为子类引用:
子类 is a 父类 ---> 天然的 唐老鸭是一个动物;
父类 is a 子类 ---> 动物不一定是唐老鸭。。
package polymorphism; public class Animal { public void eat(){ System.out.println("Animal 类的 eat方法"); } }
package polymorphism; public class MrsTang extends Animal{ public MrsTang(){eat(); System.out.println("MrsTang类的 eat方法");} public void play(){System.out.println("MrsTang 类独有的 play方法");} public static void main(String[] args) { Animal animal=new MrsTang(); animal.eat(); //play在Animal 中不存在,将animal引用还原为MrsTang类引用 MrsTang mrsTang=(MrsTang)animal; mrsTang.play(); } }
Animal 类的 eat方法
MrsTang类的 eat方法
Animal 类的 eat方法
MrsTang 类独有的 play方法
new了几次就有几个对象,变得只是引用的名称。
比如说:Animal animal =new 我自己();
我就是一个动物的化,我只应该具备 一些动物的共通行为,吃喝拉撒睡;
但是写代码这个行为就必须在 人这个类中才有。强转为人,因为写代码是人独有的。
要发生向下转型必须先发生向上转型, 毫无关系的两个类无法强转。
怎么理解这句无关系的类 不能强转呢?
看图中代码 绿字是诠释。 ClassCastRxception=类型转换异常
要发生向下转型首先就需要先发生向上转型, new一个类个人家产生关系,之后在可以强转。
下面教一教如何规避 :当发生向下转型的时候如何规避掉类型转换异常 |
答案就是--用if else 来判断 是否可以向下转型:
package polymorphism;
public class MrsTang extends Animal{
public MrsTang(){eat();
System.out.println("MrsTang类的 eat方法");}
public void play(){System.out.println("MrsTang 类独有的 play方法");}
public static void main(String[] args) {
Animal animal1=new Animal();
Animal animal2=new MrsTang();
// 如果 animal1和 MrsTang之间有关系,我才强转
if (animal1 instanceof MrsTang){
MrsTang mrsTang=(MrsTang) animal1;
System.out.println("animal1转型成功!");
}else {
System.out.println("animal1不是指向MrsTang类型的引用");
}
if (animal2 instanceof MrsTang){
MrsTang mrsTang=(MrsTang) animal2;
System.out.println("animal2转型成功!");
}else {
System.out.println("animal2不是指向MrsTang类型的引用");
}
}}
用一个父类输出多个子类 |
package Sharp;
/**
* 所有图形当前的父类
*/
public class Sharp {
//打印当前图形的样子,什么样子呢?不知道
// 当前的Sharp没有具体的实现,就叫空实现,就是一个空的{}。
public void print(){}
}
package Sharp;
public class Cycle extends Sharp{
@Override //检查当前有没有正确复写
public void print(){
System.out.println("圆形");
}
}
package Sharp;
public class Square extends Sharp {
//快速重写的快捷键 Alt+Insert 或者直接写方法名
@Override
public void print() {
System.out.println("正方形");
}
}
package Sharp;
public class Triangle extends Sharp{
@Override
public void print() {
System.out.println("三角形");
}
}
package Sharp;
public class Test {
public static void main(String[] args) {
//这个方法可以接受所有Sharp类,调用其print方法打印图形
fun(new Cycle());
fun(new Square());
fun(new Triangle());
}
//向上转型,子类对象被一个父类引用
public static void fun(Sharp sharp){
sharp.print();
}
}
圆形
正方形
三角形
这三个子类都是Sharp的子类且都重写了print()方法,多态是非常依赖子类重写方法的!普通父类没法强制要求子类重写方法。
解释:比如我创建一个子类是椭圆形,但是没有重写print()方法,父类是没法强制要求子类去写的,输出也没有问题。
若要强制要求子类去重写父类方法的化,就需要用到抽象类。
抽象类 | 人类 |
此时Sharp ,print()方法都是抽象的概念,所以这就是一个抽象类。