【Java】多态详解

(一)多态的相关定义

  1. 一个对象变量可以指示多种数据类型的现象称为多态,是同一个行为具有不同的表现现象的能力。

    比如玩游戏:按W键 (只是打个比方)
    1:在英雄联盟里,是英雄使用第二个技能。
    2:在绝地求生里,是让人物前进。

  2. 在运行时能自动地选择调用哪个方法的现象称为动态绑定

(二)多态存在的三个条件及优缺点

  • 三个条件

    1.要有继承关系。
    2.子类要重写父类的方法。
    3.父类引用指向子类对像。

  • 优点

    1.消除类型之间的耦合关系
    2.可替换性
    3.可扩充性
    4.接口性
    5.灵活性
    6.简化性

  • 缺点

    无法直接访问子类特有成员

(三)案例

- 父类

class Employee {
	String name = "员工";
	static int age = 25;
	public void work() {
		System.out.println("员工的工作");
	}

	public static void sleep() {
		System.out.println("员工睡觉了");
	}

	public void holiday(){
		System.out.println("员工放假了");
	}
}
  • 子类
class Programmer extends Employee {	//子类继承了父类
	String name = "程序员";
	static int age = 90;
    String myname = "小明";
	public void work() {	//重写了方法
		System.out.println("小明在敲代码");
	}
	public static void sleep() {
		System.out.println("小明在加班");
	}
	public void noholiday() {	//子类特有属方法
		System.out.println("小明在公司度假");
	}
}
  • 测试类
class Test {
	public static void main(String[] args) {	
	Employee p = new Programmer();	//父类引用指向子类对象
	p.work();
	p.sleep();
	p.holiday();
	//以下俩个方法调用就是多态的缺点,无法直接访问子类特有成员
	//p.noholiday();
    //System.out.println(p.myname);
	System.out.println(p.name);
	System.out.println(p.age);
	}
}

(四)理解方法调用

  • (1)上述代码运行结果及分析

运行结果

1.子类Pgrammer重写了父类Employee的非静态成员方法p.work(); 输出结果为:小明在敲代码。
2.子类重写了父类的静态成员方法p.sleep(); 输出结果为:员工睡觉了
3.未被子类重写的父类方法p.holiday();输出结果为:员工放假了

(2)终极分析

  • 下面假设调用x.f(param),隐式参数x声明为B类的对象
  1. 编译器查看对象的声明类型和方法名

    有可能存在多个名为f,但参数类型不一样的方法,如f(int)和方法f(String),编译器将会一一列举所有B类中名为f的方法和其超类A中为可访问属性且名为f的方法。
    至此编译器获得所有可能被调用的候选方法。

  2. 接下来查看调用方法时提供的参数类型

    如果在所有名为f的方法中存在一个与提供类型完全匹配,就选择这个方法。这个过程被称为重载解析。例如,对于调用x.f(“Hello”)来说,编译器将会挑选f(String),而不是f(int)。由于允许类型转换(int可以转换成double,Programmer可以转换成Employee等等),所有过程很复杂。如果编译器没有找到与参数类型匹配的方法,或者转换后有多个方法与之可以匹配就会报错。
    至此编译器已获得需要调用的方法名字和参数类型。

  3. .

    如果是private,static,final方法或者是构造器,那么编译器将可以准确地知道应该调用哪个方法,我们将这种调用方式为静态绑定。与之对应的是调用的方法依赖x的实际类型,并且在运行中实现动态绑定。上面例子中,p的实际类型为Pragrammer,编译器采用动态方法的方式生成一条调用方法的指令。

  4. 程序运行,且采用动态绑定调用方法时

    虚拟机一定调用与x所引用对象的实际类型最合适的那个方法。假设x的实际类型是B,B是A的子 类,如果B定义方法f(String),就直接调用它;否则在B的超类中寻找f(String),以此类推。
    每次调用方法都要进行搜索,时间开销相当大。因此虚拟机预先为每个类创建一个方法表,其中列出了所有方法的签名(签名是方法的名字与参数列表)与实际调用的方法,在调用方法时,虚拟机仅查这个表就行了

(3)调用总结

  • 那么我们可以根据以上情况总结出多态成员访问的特点:

    1.成员变量
    编译看左边(父类),运行看左边(父类)
    2.成员方法
    编译看左边(父类),运行看右边(子类)。动态绑定
    3.静态方法
    编译看左边(父类),运行看左边(父类)。
    (静态和类相关,算不上重写,所以,访问还是左边的)
    只有非静态的成员方法,编译看左边,运行看右边



(五)类型转换

  • 向上转型
Employee p = new Programmer();	//向上转型

子类对象Programmer转化为父类对象Employee,向上转型时,这个时候Programmer这个引用 调用的方法是子类方法。子类单独定义的方法会丢失。比如上面Programmer类中定义的noholiday方法,当Employee类引用指向Programmer类实例时是访问不到noholiday方法的,Employee.noholiday会报错。
子类引用不能指向父类对象。Cat c = (Cat)new Animal()这样是不行的。


向上转型的好处:
1.减少重复代码
2.使代码变得简洁
3.提高系统扩展性。

  • 向下转型
//向下转型 正确示范
Employee p = new Programmer();
Programmer t = (Prpgrammer)p;
~~~~ 

//假设Hr类(招聘官)也是继承了Employee,和Programmer一样是Employee类的子类
//向下转型 错误示范
Employee p = new Programmer();
Hr t = (Hr)p;

//向下转型 错误示范
Employee p = new Employee();
Programmer t = (Prpgrammer)p;

第一个不报错是因为Programmer(程序员)本来就是Employee(员工),可以向下转型成员。
第二个报错是因为Hr和Programmer都是同一超类下的不同子类,程序员这种肥宅(逃)怎么可能转换成Hr
第三个报错是因为 p 为Employee对象,不能进行向下转型
想要向下转型,就得先向上转型
这个时候t就能访问到属于自己的专属成员了 如t.noholiday(); t.myname

  • 综上所述

只能在继承内层内进行类型转换
可以用instanceof运算符检测能否在转换成功 if(Programmer instanceof Hr)

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值