
10-多态
10-多态
面向对象三大特征:封装、继承、多态(polymorphic)。
事物的多种形态体现。多态有个前提:必须是存在继承关系。
eg: 动物中的猫很萌。 狮子很霸气
重写 重载(在同一个类,存在同名方法,参照三个判断条件)
多态的前提
- 存在继承关系-- 父子类
- 要有方法重写
- 要有父类引用指向子类对象实体
/**
* 多态:事物有多种形态.
* 前提:1)存在继承关系;2)要有可重写方法;3)父类的引用 指向 子类的对象实体
* @author wyn
*/
public class Demo01 {
public static void main(String[] args) {
//1)存在继承关系 --> 我们来描述一只猫
Cat c = new Cat(); //猫 是一只猫c
c.eat(); //子类重写的方法, 结果:猫吃鱼
//3)多态的体现:要有 父类的引用 指向 子类对象实体 --> 再来描述:猫也是一只动物a
Animal a = new Cat(); //声明父类对象,即声明一个父类的引用变量,让引用变量指向 子类对象实体
a.eat(); //现在是动物吃饭?还是猫吃鱼? ---> 猫吃鱼g
}
}
class Animal{
String name = "animal";
public void eat() {
System.out.println("动物吃");
}
}
class Cat extends Animal{ //1.存在继承关系
String name = "cat";
@Override
public void eat() { //2.方法重写
System.out.println("猫吃鱼");
}
}
多态中的成员访问特点
/**
* 多态:事物有多种形态.
* 前提:1)存在继承关系;2)要有可重写方法;3)父类的引用 指向 子类的对象实体
* @author wyn
*/
public class Demo01 {
public static void main(String[] args) {
//1)存在继承关系 --> 我们来描述一只猫
Cat c = new Cat(); //猫 是一只猫c
c.eat(); //子类重写的方法, 结果:猫吃鱼
//3)多态的体现:要有 父类的引用 指向 子类对象实体 --> 再来描述:猫也是一只动物a
Animal a = new Cat(); //声明父类对象,即声明一个父类的引用变量,让引用变量指向 子类对象实体
a.eat(); //现在是动物吃饭?还是猫吃鱼? ---> 猫吃鱼g
}
}
class Animal{
String name = "animal";
public void eat() {
System.out.println("动物吃");
}
}
class Cat extends Animal{ //1.存在继承关系
String name = "cat";
@Override
public void eat() { //2.方法重写
System.out.println("猫吃鱼");
}
}
成员变量
- 父类引用指向子类对象实体:编译看左边(父类),运行时看左边(父类)
——为什么?
父类和子类同时有同名变量,在子类对象实体中,默认隐藏父类成员变量,此时子类对象实体中显式的成员变量是 子类成员变量。
——这边就涉及到了this和super关键字。this是指向当前对象的引用,super是指向当前对象父类的引用。
所以此时的父类引用指向子类对象f.num就相当于 super.num= 100;
成员方法
1.实例方法属于对象
父类引用指向子类对象实体:编译看左边(父类),运行时看右边(子类对象实体)
—— 涉及到了Java绑定原理-动态绑定
- 在编译时,会先判断左边的父类是否存在money()方法,存在在通过,不存在编译错误;
- 当该方法运行时,调用的是子类中的money()。
所以真正被压进栈的是 子类的money()。
2.静态方法属于类
编译看左边(父类),运行时看左边(父类)
——涉及到了Java绑定原理-静态绑定
静态方法(类方法)和类相关,算不上重写,所以访问还是看左边。
3.总结成员方法的特点
非静态的成员方法,编译看左边,运行看右边。
静态的成员方法,编译看左边,运行也看左边。---> 对象.静态方法 相当于 类.静态方法
java绑定原理
Java程序是经过编译和运行。
静态绑定
编译期间进行的绑定,对象的属性以及对象的类方法与成员变量的声明类型进行绑定。
多态(其他地方也是)中,静态绑定: 成员变量 和静态方法: 编译+运行都是看左边
动态绑定
java在运行时,动态决定实例方法的调用。
根据具体引用的对象实体来决定实例方法的调用叫运行期绑定。正是因为有了动态绑定机制才能实现java中的多态行为。
案例分析:超人的故事
有个人叫jack,特别有钱,去美国找了个哥们来谈生意,此时的他是穿着西装革履。突然,看到有人跳楼了,然后呢,jack说要去救人,那哥们赶紧拦住他,不相信他是个超人,担心他也想不开;此时,紧急时刻,jack顾不得隐藏自己的真实身份,为了证明他可以救人就内裤外穿直接飞出去救人了。
超人在谈生意的时候,表现的是一个人,并不会内裤外穿。所以此时他的名字叫做约翰。突然呢,听到有人跳楼了
平时是个person ;别人有难的时候才变身sup。
平时乔装成一个人。 所以叫jack
谈生意,是超人这个人在和我谈生意:所以是在谈 一个亿的大生意
突然有个人跳楼. 超人说要去救人,此时他是乔装成一个人,那我们不知道的情况下,肯定是不能让超人飞出去救人. 所以此时伪装成普通人的超人 不能飞出去救人
/**
* 超人的故事:1.超人是一个人 => 创建一个父类Person
* 2.超人平时是 西装革履的,伪装成一个普通人Person p ==>new Superman();创建一个超人实例,是一个普通人此时叫jack,他在谈一个亿的大单子
* 3.突然看到有人跳楼,和超人谈生意的哥们组制超人去救人,因为不相信他会飞, 此时,超人就要证明他确实会飞是个超人
* 证明的方式:超人变身穿上他的超人内裤 卸去普通人的伪装 Superman s ==> (Superman)p;
*/
public class TestSuperman {
public static void main(String[] args) {
//超人是一个普通人
Person p = new Superman();
System.out.println("此时超人叫:"+p.name+"来自"+p.contry);
p.谈生意();
p.聊天();
// p.fly(); //不相信jack会飞!
//证明jack是个超人
System.out.println("****"+p.name+"变身卸去伪装*****");
Superman s = (Superman) p; //jack 变身卸去伪装
System.out.println("此时超人重新介绍自己,叫:"+s.name+"来自"+s.contry);
s.fly();
s.聊天();
}
}
class Person {
public String name = "jack";
public static String contry ="地球";
public void 谈生意() {
System.out.println("谈生意");
}
public static void 聊天() {
System.out.println("通过空气介质,传递声音来聊天");
}
}
class Superman extends Person {
String name ="superman";
public static String contry ="中国";
@Override
public void 谈生意() {
System.out.println("谈一个亿的大单子");
}
public static void 聊天() {
System.out.println("不仅通过声音来聊天,而且还能用心灵感应的方式聊天..");
}
public void fly() {
System.out.println("会飞,可以飞出去救人");
}
}
多态中对象转型
上转型对象(上溯)
如果A类是B类的父类,当我们用子类创建对象,并把这个对象的引用放到父类的对象中时,称父类对象a是子类对象b的上转型对象。
总结: 父类引用 指向 子类对象实体 ===> 父类引用 是 子类对象实体 的上转型
父类引用只拥有 子类中 共有的成员变量和成员方法
eg:
SuperMan s = new SuperMan();
Person p = s; //p是子类对象s的上转型
Person p2 = new Person();
//不能称 p2是 s的上转型 错误的
上转型对象特点:
上转型对象的对象实体 是由子类负责创建的。所以上转型对象将失去原来对象的一些属性和功能。
- 上转型对象只拥有继承的变量+继承或者重写的方法。——只拥有父类和子类都有的成员变量/方法;
- 不能操作子类新增的成员变量和方法;---> 编译过不去
- 子类重写父类的方法,此时上转型对象调用的方法一定是子类重写后的方法。->实例方法运行看右边
- 静态变量和静态方法与类相关,父类静态成员和子类同名时,子类不能够隐藏父类的
- 子类对象拥有子类全部的成员变量/方法。注意事项:
- 不要将父类创建的对象和子类对象上转型对象混淆;
对象的向下转型(下溯)
可以将对象的上转型对象再强制转换到一个子类对象,此时 该子类对象又具备了子类所有属性和功能;(即下溯)
SuperMan s = new SuperMan();
Person p = s; //p是子类对象s的上转型对象
SuperMan s2 =(SuperMan) p;//s2是对象p的下转型
//所以此时 s2 拥有子类所有的属性和功能
注意事项:
- 父类创建的对象 ≠子类对象上转型对象
- 不可以将父类创建的对象的引用赋值给子类声明的对象
Person p2 = new Person();//p2是父类创建的对象的引用
SuperMan s3 =(SuperMan) p2; //这是错误的
//如果
p2 = new Superman(); //p2是 超人类的 上转型
Superman s4 = (Superman)p2; //上转型对象`再强制转换`到子类对象 才叫下溯
所以,必须是该子类的上转型对象才能被成功下溯成子类对象,否则会报类型转换异常。
如果写 1000行代码 p2 是再 400行定义 很难判断 p2 到底是否 superman的上转型
instanceof关键字
- 用于判断当前对象是否是类的一个实例。 ===> 判断 p2 是否是 Superman的引用变量
public static void main(String[] args) {
Superman s1 = new Superman();
Superman s2 = new Superman();
Person p1 =new Person();
Person p2 =new Superman();
boolean b2 = s1 instanceof Person; //子类的对象实体 父类对象可以引用
boolean b3 = p1 instanceof Person; //父类的对象实体 父类对象可以引用
boolean b4 = p2 instanceof Person; //子类的对象实体 父类对象可以引用
boolean b5 = p1 instanceof Superman; //父类的对象实体 子类对象不可以引用
boolean b6 = p2 instanceof Superman; //子类的对象实体 子类类对象可以引用
boolean b7 = s1 instanceof Superman; //子类的对象实体 子类类对象可以引用
System.out.println("父类对象可以引用的对象实体,所以instanceof右边跟着的类是父类:"+b2+","+b3+","+b4);
System.out.println("子类对象可以引用的对象实体,所以instanceof右边跟着的类是子类:"+b5+","+b6+","+b7);
}
- 使用instanceof 可能遇到的异常:java.lang.ClassCastException 类型转换异常
- 当 对象引用的 不是instanceof右边 类的对象实体,则报错。
引申--例子:在Superman类中重写Object类的equal() ,用于判断同时superman的对象内容是否相等
为什么equals()方法要重写?
——判断两个对象在逻辑上是否相等,如根据类的成员变量来判断两个类的实例是否相等,而继承Object中的equals方法只能判断两个引用变量是否是同一个对象实体。这样我们往往需要重写equals()方法。
类型自动提升及强制类型转换条件
- 创建对象的类之间存在继承关系。
- 向上转型(类型自动提升):父类引用 指向 子类对象实体
- 向下转型(强制转换):前提是必须是该子类的上转型对象;才能被成功下溯成子类对象。
多态的好处与弊端
- 好处
- 提高了代码维护性(继承保证)
- 提高了代码的扩展性(由多态保证)
- 具体体现:父类引用 用来当形式参数,可以接收任何子类对象实体,调用共有的方法
- 弊端
- 不能操作子类新增的成员变量和方法
/**
* 多态的好处
* 父类引用 用来当形式参数,可以接收任何子类对象实体,调用共有的方法
* 弊端:
* 父类引用无法使用 子类中独有的成员方法/变量
* @author wyn
*
*/
public class Demo04Animal {
public static void main(String[] args) {
Animal1 a =new Cat1();
//a.catchMouse();
method(new Cat1());
method(new Dog1());
}
public static void method(Animal1 a) { // 多态的好处:父类引用 用来当形式参数,可以接收任何子类对象实体,调用共有的方法
a.eat();
}
/*public static void method(Cat1 c) {
c.eat();
}
public static void method(Dog1 d) {
d.eat();
}*/
}
//父类Animal
class Animal1{
public void eat(){
System.out.println("动物吃饭...");
}
}
//子类cat
class Cat1 extends Animal1{
//2)重写了父类eat()方法
@Override
public void eat() {
//super.eat();
System.out.println("猫喜欢吃鱼..");
}
public void catchMouse() {
System.out.println("猫抓老鼠");
}
}
class Dog1 extends Animal1{
//2)重写了父类eat()方法
@Override
public void eat() {
//super.eat();
System.out.println("狗喜欢吃猪大骨..");
}
public void lookHome() {
System.out.println("狗看家");
}
}
注意:
- 开发时,很少创建对象用父类引用接收,直接子类引用接收,这样可以使用子类对象特有的属性和方法
- 当作形式参数时,用多态最好,因为扩展性更强
总结什么是多态
- 当一个类有很多子类时,并且这些子类都重写了父类中的某个方法。
那么当我们把子类创建的对象的引用放到一个父类的对象中时(即上转型对象), 那么这个上转型对象调用这个方法时就具有多种形态,因为子类在重写父类方法时可能生产不同行为。
- 多态就是指父类的某个方法被子类重写时,可以各自产生自己的功能行为。
- 多态性中父类一般抽象为一个接口,(有多个实现类实现接口中某个方法)