Java:理解多态

学习目标

了解多态相关的面向对象编程特性,理解多态在软件开发中的用处
关键是理解相关概念,将抽象的知识感性化

多态(polymorphism)的概念

在这里插入图片描述

  • 多态的最本质特征就是父类(或接口)变量可以引用子类(或实现了接口的类)对象。换句话说:子类对象可以被当成基类对象使用!
Parent p = new Child();
IMyClass obj = new MyClass();

在这里插入图片描述

Java中子类与基类变量之间的赋值

  • 子类对象可以直接赋值给基类变量
  • 基类对象要赋给子类对象变量,必须执行类型转换,其语法:
    子类对象变量=(子类名)基类对象名
  • 也不能乱转换。如果类型转换失败Java会抛出以下这种异常:
    ClassCastExpection
怎样判断对象是否可以转换?
  • 可以使用instanceof运算符判断一个对象是否可以转换为指定的类型:
Object obj = "Hello";
if(obj instanceof String)
   System.out.println("obj对象可以转换为字符串“);

Java关键字instanceof

  1. boolean result = obj instanceof Class;
    obj为一个对象,class表示一个类或者一个接口,当obj为Class的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result都返回true,否则返回false(⚠️编译器会检查obj是否能转换成右边的class类型,如果不能转换直接报错,如果不能确定类型,则会通过编译,具体看运行时定)
  2. obj 必须为引用类型,不能是基本类型
    int i=0; boolean result = i instanceof Integer;编译不通过,instanceof只能用做对象的判断
  3. obj为null
    Java分为两种数据类型,一种是基本数据类型,有八个分别是 byte short int long float double char boolean,一种是引用类型,包括类,接口,数组等等。而Java中还有一种特殊的 null 类型,该类型没有名字,所以不可能声明为 null 类型的变量或者转换为 null 类型,null 引用是 null 类型表达式唯一可能的值,null 引用也可以转换为任意引用类型。我们不需要对 null 类型有多深刻的了解,我们只需要知道 null 是可以成为任意引用类型的特殊符号。
    在JavaSE规范中对instanceof运算符的规定:如果obj为null,将返回false
System.out.println(null instanceof Object);//false
  1. obj为null
    很普通的一种用法
Integer integer = new Integer(1);
System.out.println(integer instanceof  Integer);//true
  1. obj为class接口的实现类
    集合中有一个上层接口List,其有个典型实现类ArrayList
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

所以可以用instanceof来判断某个对象是否是List接口的实现类,如果是返回true,否则返回false

ArrayList arrayList = new ArrayList();
System.out.println(arrayList instanceof List);//true
//反过来也是对的
List list = new ArrayList();
System.out.println(list instanceof ArrayList);//true
  1. obj为class类的直接或间接子类
public class Person {}
public class Man extends Person{}
Person p1 = new Person();
Person p2 = new Man();
Man m1 = new Man();
System.out.println(p1 instanceof Man);//false
System.out.println(p2 instanceof Man);//true
System.out.println(m1 instanceof Man);//true

第一种情况,Man是Person的子类,但Person不是Man的子类,所以返回false
问题

Person p1 = new Person();
System.out.println(p1 instanceof String);//编译报错
System.out.println(p1 instanceof List);//false
System.out.println(p1 instanceof List<?>);//false
System.out.println(p1 instanceof List<Person>);//编译报错

Java语言规范Java SE 8,用伪代码表示:

boolean result;
if (obj == null) {
  result = false;
} else {
  try {
      T temp = (T) obj; // checkcast
      result = true;
  } catch (ClassCastException e) {
      result = false;
  }
}

如果 obj 不为 null 并且 (T) obj 不抛 ClassCastException 异常则该表达式值为 true ,否则值为 false 。所以对于上面提出的问题就很好理解了,为什么 p1 instanceof String 编译报错,因为(String)p1 是不能通过编译的,而 (List)p1 可以通过编译。
7. instanceof实现策略
在这里插入图片描述
1、obj如果为null,则返回false;否则设S为obj的类型对象,剩下的问题就是检查S是否为T的子类型
 2、如果S == T,则返回true;
3、接下来分为3种情况,之所以要分情况是因为instanceof要做的是“子类型检查”,而Java语言的类型系统里数组类型、接口类型与普通类类型三者的子类型规定都不一样,必须分开来讨论。
  ①、S是数组类型:如果 T 是一个类类型,那么T必须是Object;如果 T 是接口类型,那么 T 必须是由数组实现的接口之一;
  ②、接口类型:对接口类型的 instanceof 就直接遍历S里记录的它所实现的接口,看有没有跟T一致的;
  ③、类类型:对类类型的 instanceof 则是遍历S的super链(继承链)一直到Object,看有没有跟T一致的。遍历类的super链意味着这个算法的性能会受类的继承深度的影响。

子类父类拥有同名方法

  • 当子类与父类拥有一样的方法,并且让一个父类变量引用一个子类对象时,到底调用哪个方法,由对象自己“真实”类型所决定,即:对象是子类型的,它就调用子类型的方法,是父类型的,就调用父类型的方法
  • 这个特性就是面向对象“多态”的体现
  • 如果子类和父类有相同的字段,则子类中的字段会代替或隐藏父类的字段,子类方法中访问的是子类中的字段(而不是父类中的字段)。如果子类方法确实想访问父类中被隐藏的同名字段,可以用super关键字来访问它
  • 如果子类被当作父类使用,则通过子类访问的字段是父类
ublic class welcome1 {
	public static void main(String[] args) {
		Parent parent=new Parent();
		parent.printValue();
		Child child=new Child();
		child.printValue();
		//父类的引用指向子类的对象,子类被当作父类用
		parent=child;
		parent.printValue();
		//访问的字段是父类的,但是方法是子类的也就是对象本身是什么类型,但是属性是引用的属性
		parent.myValue++;
		parent.printValue();
		
		((Child)parent).myValue++;
		parent.printValue();
		
	}
}

class Parent{
	public int myValue=100;
	public void printValue() {
		System.out.println("Parent.printValue(),myValue="+myValue);
	}
}
class Child extends Parent{
	public int myValue=200;
	public void printValue() {
		System.out.println("Child.printValue(),myValue="+myValue);
	}
}

输出部分:

Parent.printValue(),myValue=100
Child.printValue(),myValue=200
Child.printValue(),myValue=200
Child.printValue(),myValue=200
Child.printValue(),myValue=201

在这里插入图片描述

多态代码的实现

当多个类实现同一接口(或派生自同一抽象类)时,针对这些类所创建的对象调用接口所定义的方法时,会分别调用相应的类的具体实现代码

滞后绑定(late bind)

Java编译器是如何为多态代码生成字节码指令?在程序运行过程中,多态性是如何实现的?

查了一下《Java核心技术》大概意思就是:除了static,final,private和构造器是静态绑定的,如果要调用的方法依赖于隐式参数的实际类型,那么必须使用动态绑定。虚拟机会为每个类的方法建立一个方法表(method table),当真正调用这个方法时,会在这个方法表中寻找匹配的相关方法,先查当前的类的方法中有没有匹配的,然后在查当前类的父类的方法表,找到后虚拟机调用这个方法。
还是另启一篇学虚拟机吧,唉!!!学!!!加油!!奥利给!!冲冲冲!!!

实例理解多态

在这里插入图片描述

面向对象建模思维进化过程

思路一:三个动物每个动物对应一个类,每个类都定义一个eat方法,表示吃饲养员给他们的食物
在这里插入图片描述
再设计一个feeder类代表饲养员,其name字段保存饲养员的名字,三个方法分别代表喂养三种不同的动物,其参数分别引用三种动物对象。
在这里插入图片描述
在这里插入图片描述
代码量大,十分繁琐
思路二:引入继承
定义一个抽象基类Animal,其中定义一个抽象方法eat(),三个子类实现这个抽象方法。
在这里插入图片描述
feeder类的三个喂养方法合并成一个feedAnimal()方法,注意它接受一个类型为Animal参数,而不是三个具体的动物类型。依据多态特性,此方法可以接收任何一个派生自Animal类的子类对象
在这里插入图片描述
在这里插入图片描述
俺的思路就停留在这个高度了,唉,堪忧!
接着人家可厉害了(在我看来),feeder只需要喂养一群Animal就可以了,直接可以把所有的动物类型存在一个Animal数组里,然后给feeder传递一个Animal数组,就可以喂全部的动物了。在这里插入图片描述
在这里插入图片描述
然后就又想了:这样就完美了吗?就没有缺陷了?唉,还是得一直思考,一直审视自己观点。现在的情况是,解决了刚才的问题,但要考虑是否新方法会带来新问题:feedAnimals()方法接收的是一个Animal数组,这有一个限制,就是只能创建固定个数的数组,无法动态的增减动物个数。

如果这时候引进了新的动物咋办?
某些动物死亡咋办?
也不能再一直都改Animal数组的个数啊!
在这里插入图片描述
在这里插入图片描述

小结

  • 通过在编程中应用多态,可以使代码具有更强的适用性。当需求变化时,多态特性可以帮助我们将需要改动的地方减少到最低程度
  • 多态继承有两种主要形式:
  1. 继承多态
  2. 接口多态:使用接口代替抽象基类
  • 使用多态的最大好处是:当你要修改程序并扩充系统时,你需要修改的地方较少,对其他部分代码影响较小。千万不要小看这两个“较”字!程序规模越大,其优势就越突出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值