10-多态

面向对象三大特征:封装、继承、多态(polymorphic)。

事物的多种形态体现。多态有个前提:必须是存在继承关系。

eg: 动物中的猫很萌。 狮子很霸气

重写 重载(在同一个类,存在同名方法,参照三个判断条件)

多态的前提

  1. 存在继承关系-- 父子类
  2. 要有方法重写
  3. 要有父类引用指向子类对象实体
/**
 * 多态:事物有多种形态.
 * 前提: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的上转型   错误的

上转型对象特点:

上转型对象的对象实体 是由子类负责创建的。所以上转型对象将失去原来对象的一些属性和功能。

  1. 上转型对象只拥有继承的变量+继承或者重写的方法。——只拥有父类和子类都有的成员变量/方法;
    • 不能操作子类新增的成员变量和方法;---> 编译过不去
    • 子类重写父类的方法,此时上转型对象调用的方法一定是子类重写后的方法。->实例方法运行看右边
    • 静态变量和静态方法与类相关,父类静态成员和子类同名时,子类不能够隐藏父类的
  2. 子类对象拥有子类全部的成员变量/方法。注意事项:
  3. 不要将父类创建的对象和子类对象上转型对象混淆;

对象的向下转型(下溯)

可以将对象的上转型对象再强制转换到一个子类对象,此时 该子类对象又具备了子类所有属性和功能;(即下溯)

SuperMan s = new SuperMan();
Person p = s;     //p是子类对象s的上转型对象
SuperMan s2 =(SuperMan) p;//s2是对象p的下转型
//所以此时  s2 拥有子类所有的属性和功能

注意事项:

  1. 父类创建的对象 ≠子类对象上转型对象
  2. 不可以将父类创建的对象的引用赋值给子类声明的对象
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()方法。

类型自动提升及强制类型转换条件

  1. 创建对象的类之间存在继承关系。
  2. 向上转型(类型自动提升):父类引用 指向 子类对象实体
  3. 向下转型(强制转换):前提是必须是该子类的上转型对象;才能被成功下溯成子类对象。

多态的好处与弊端

  • 好处
    1. 提高了代码维护性(继承保证)
    2. 提高了代码的扩展性(由多态保证)
    3. 具体体现:父类引用 用来当形式参数,可以接收任何子类对象实体,调用共有的方法
  • 弊端
    • 不能操作子类新增的成员变量和方法
/**
 * 多态的好处
 * 父类引用 用来当形式参数,可以接收任何子类对象实体,调用共有的方法
 * 弊端:
 * 父类引用无法使用 子类中独有的成员方法/变量
 * @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("狗看家");
	}
}

注意:

  1. 开发时,很少创建对象用父类引用接收,直接子类引用接收,这样可以使用子类对象特有的属性和方法
  2. 当作形式参数时,用多态最好,因为扩展性更强

总结什么是多态

  • 当一个类有很多子类时,并且这些子类都重写了父类中的某个方法。

那么当我们把子类创建的对象的引用放到一个父类的对象中时(即上转型对象), 那么这个上转型对象调用这个方法时就具有多种形态,因为子类在重写父类方法时可能生产不同行为。

  • 多态就是指父类的某个方法被子类重写时,可以各自产生自己的功能行为。
  • 多态性中父类一般抽象为一个接口,(有多个实现类实现接口中某个方法)