什么是多态,多态的实现方法是什么?

作者:程序狗
链接:https://www.zhihu.com/question/30082151/answer/120520568
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

用一句话概括就是:事物在运行过程中存在不同的状态。先以教科书般举例说明,下文再举一个花木兰替父从军的例子帮助大家理解. 多态的存在有三个前提:
1.要有继承关系
2.子类要重写父类的方法
3.父类引用指向子类对,
但是其中又有很多细节需要注意。首先我们定义两个类,一个父类Animal,一个子类Cat。
父类Animal

class Animal {
	int num = 10;
	static int age = 20;
	public void eat() {
		System.out.println("动物吃饭");
	}

	public static void sleep() {
		System.out.println("动物在睡觉");
	}

	public void run(){
		System.out.println("动物在奔跑");
	}
}

子类Cat

class Cat extends Animal {
	int num = 80;
	static int age = 90;
        String name = "tomCat";
	public void eat() {
		System.out.println("猫吃饭");
	}
	public static void sleep() {
		System.out.println("猫在睡觉");
	}
	public void catchMouse() {
		System.out.println("猫在抓老鼠");
	}

}

测试类Demo_Test1

class Demo_Test1 {
	public static void main(String[] args) {
	Animal am = new Cat();
	am.eat();
	am.sleep();
	am.run();
	//am.catchMouse();这里先注释掉,等会会说明
        //System.out.println(am.name);//这里先注释,待会说明
	System.out.println(am.num);
	System.out.println(am.age);
	}
}

以上的三段代码充分体现了多态的三个前提,即:
1、存在继承关系
Cat类继承了Animal类
2、子类要重写父类的方法
子类重写(override)了父类的两个成员方法eat(),sleep()。其中eat()是非静态的,sleep()是静态的(static)。
3、父类数据类型的引用指向子类对象。
测试类Demo_Test1中 Animal am = new Cat();语句在堆内存中开辟了子类(Cat)的对象,并把栈内存中的父类(Animal)的引用指向了这个Cat对象。
到此,满足了Java多态的的必要三个前提。
---------------------------------------------------华丽的分割线----------------------------------------------------------------------
如果再深究一点呢,我们可以看看上面测试类的输出结果,或许对多态会有更深层次的认识。猜一猜上面的结果是什么。
<img src="//pic.ikafan.com/imgp/L3Byb3h5L2h0dHBzL3BpYzQuemhpbWcuY29tLzUyMWRiN2E0MTA2NWY5MjQ0MWE5NWIwZmYyOTRlNzA3X2IucG5n.jpg" data-rawwidth="671" data-rawheight="133" class="origin_image zh-lightbox-thumb" width="671" data-original="https://pic4.zhimg.com/521db7a41065f92441a95b0ff294e707_r.png">可以看出来可以看出来
子类Cat重写了父类Animal的非静态成员方法am.eat();的输出结果为:猫吃饭。
子类重写了父类(Animal)的静态成员方法am.sleep();的输出结果为:动物在睡觉
未被子类(Cat)重写的父类(Animal)方法am.run()输出结果为:动物在奔跑

System.out.println(am.num);//输出结果为10
System.out.println(am.age);//输出结果为20

那么我们可以根据以上情况总结出多态成员访问的特点:
成员变量
编译看左边(父类),运行看左边(父类)
成员方法
编译看左边(父类),运行看右边(子类)。动态绑定
静态方法
编译看左边(父类),运行看左边(父类)。
(静态和类相关,算不上重写,所以,访问还是左边的)
只有非静态的成员方法,编译看左边,运行看右边
---------------------------------------------------华丽的分割线----------------------------------------------------------------------
那么多态有什么弊端呢?有的,即多态后不能使用子类特有的属性和方法。往上面的代码看,子类Cat有一个特有的属性String name = "tomCat"; 并且还有一个特有的抓老鼠的方法catchMouse()。但是在测试类(Demo_Test)中,我们尝试调用子类特有的方法catchMouse()和打印子类特有的成员属性String name = "tomCat"; 就会报错。

am.catchMouse();
System.out.println(am.name);

<img src="//pic.ikafan.com/imgp/L3Byb3h5L2h0dHBzL3BpYzMuemhpbWcuY29tL2E0NGYzNDlhNDE3OTY5NWYyMjNjNjgzZDRlZGRiOGE2X2IucG5n.jpg" data-rawwidth="302" data-rawheight="223" class="content_image" width="302">原因就是多态的弊端,就是:不能使用子类特有的成员属性和子类特有的成员方法。
--------------------------------------------------华丽的分割线----------------------------------------------------------------------
如果在代码执行过程中还想使用Cat类中特有的属性String name和它特有的成员方法catchMouse()了怎么办呢?那我们就可以把这个父类引用指向了子类对象的家伙am再强制变回Cat类型。这样am就是Cat类型的引用了,指向的也是Cat对象了,自然也能使用Cat类的一切属性和一切的成员方法。

class Demo_Test {
	public static void main(String[] args) {

	Animal am = new Cat();
	am.eat();
	am.sleep();
	am.run();
//	am.catchMouse();
//	System.out.println(am.name);
	System.out.println(am.num);
	System.out.println(am.age);

	System.out.println("------------------------------");
	Cat ct = (Cat)am;
	ct.eat();
	ct.sleep();
	ct.run();
	ct.catchMouse();
	}
}

<img src="//pic.ikafan.com/imgp/L3Byb3h5L2h0dHBzL3BpYzMuemhpbWcuY29tL2IyMjRmNmZhNDg4ZGI1MjA4OGYwMmQ1MDA2Nzc0NDhhX2IucG5n.jpg" data-rawwidth="677" data-rawheight="234" class="origin_image zh-lightbox-thumb" width="677" data-original="https://pic3.zhimg.com/b224f6fa488db52088f02d500677448a_r.png">很明显,执行强转语句Cat ct = (Cat)am;之后,ct就指向最开始在堆内存中创建的那个Cat类型的对象了。这就是多态的魅力吧,虽然它有缺点,但是它确实十分灵活,减少多余对象的创建,不用说为了使用子类的某个方法又去重新再堆内存中开辟一个新的子类对象。以上。。很明显,执行强转语句Cat ct = (Cat)am;之后,ct就指向最开始在堆内存中创建的那个Cat类型的对象了。这就是多态的魅力吧,虽然它有缺点,但是它确实十分灵活,减少多余对象的创建,不用说为了使用子类的某个方法又去重新再堆内存中开辟一个新的子类对象。以上。。
----------------------------------------------------------分割线------------------------------------------------------------------------
啦啦啦,收到这么多赞.很惊讶,举个通俗点的例子吧.
花木兰替父从军
大家都知道花木兰替父从军的例子,花木兰替父亲花弧从军。那么这时候花木兰是子类,花弧是父类。花弧有自己的成员属性年龄,姓名,性别。花木兰也有这些属性,但是很明显二者的属性完全不一样。花弧有自己的非静态成员方法‘骑马杀敌’,同样花木兰也遗传了父亲一样的方法‘骑马杀敌’。花弧还有一个静态方法‘自我介绍’,每个人都可以问花弧姓甚名谁。同时花木兰还有一个自己特有的非静态成员方法‘涂脂抹粉’。但是,现在花木兰替父从军,女扮男装。这时候相当于父类的引用(花弧这个名字)指向了子类对象(花木兰这个人),那么在其他类(其他的人)中访问子类对象(花木兰这个人)的成员属性(姓名,年龄,性别)时,其实看到的都是花木兰她父亲的名字(花弧)、年龄(60岁)、性别(男)。当访问子类对象(花木兰这个人)的非静态成员方法(骑马打仗)时,其实都是看到花木兰自己运用十八般武艺在骑马打仗。当访问花木兰的静态方法时(自我介绍),花木兰自己都是用她父亲的名字信息在向别人作自我介绍。并且这时候花木兰不能使用自己特有的成员方法‘涂脂抹粉’。-----多态中的向上转型
那么终于一将功成万骨枯,打仗旗开得胜了,花木兰告别了战争生活。有一天,遇到了自己心爱的男人,这时候爱情的力量将父类对象的引用(花弧这个名字)强制转换为子类对象本来的引用(花木兰这个名字),那么花木兰又从新成为了她自己,这时候她完全是她自己了。名字是花木兰,年龄是28,性别是女,打仗依然那样生猛女汉子,自我介绍则堂堂正正地告诉别人我叫花木兰。OMG!终于,终于可以使用自己特有的成员方法‘涂脂抹粉’了。从此,花木兰完全回到了替父从军前的那个花木兰了。并且和自己心爱的男人幸福的过完了一生。-----多态中的向下转型
----------------------------------------------------------华丽的分割线---------------------------------------------------------------
大家记得哈,向上转型向下转型一定是在多态这个前提下哈,否则强制将女儿变成父亲,或者将父亲变成女人,就变成东方不败了,系统此时就会报错非法类型转换。哈哈哈哈哈。另外开发中一般是利用多态声明形式参数,并将创建子类的匿名对象作为实际参数。以上。

时间: 2024-10-27 07:20:54

什么是多态,多态的实现方法是什么?的相关文章

Java 多态 父类和子类方法的访问控制权限

Java 多态 父类和子类方法的访问控制权限 @author ixenos 父类和子类方法的访问控制权限 继承是为了扩展类的功能,而这种扩展显然就是对一个原始类的扩展,目的还是向上转型来调用,所以这就是一个对象多态的问题. 既然多态,那么访问控制权限就要让父类也能访问. 也就是说,子类重写方法时不能使该方法拥有比父类更严格的访问控制权限. 否则违背了多态,导致向上类型转换后,使用父类变量无法调用该子类私有方法.

python开发学习-day07(面向对象之多态、类的方法、反射、新式类and旧式类、socket编程)

s12-20160227-day07 *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* BLOCKS =============================================================================*/ p, blockquote, ul, ol, dl, table, pre { margin

多态实现之--虚方法

多态实现共有三种途径,分别是:虚方法.抽象类.接口,本篇讲述第一种方法:虚方法. 1. 什么是虚方法?虚方法,从语法上来说是被virtual关键字修饰的方法,从含义上讲,这个方法在类被继承后可以在子类重写(override). 2. 在子类中重写虚方法需要注意哪些问题?首先说明的是,子类中的重写并不是必须的,因为父类中的虚方法本身也是有函数体的(除非是抽象方法),所以如果进行重写则会覆盖从父类中的虚方法,否则直接调用父类的该方法. 3. 是怎样体现多态的?在调用的时候子类只需要调用同一个方法,如

浅谈 C# 多态的魅力 - 虚方法、抽象、接口实现

提前总结:需方法.抽象方法可以重写,子类继承父类,抽象方法没有方法体,不能实例化 前言:我们都知道面向对象的三大特性:封装,继承,多态.封装和继承对于初学者而言比较好理解,但要理解多态,尤其是深入理解,初学者往往存在有很多困惑,为什么这样就可以?有时候感觉很不可思议,由此,面向对象的魅力体现了出来,那就是多态,多态用的好,可以提高程序的扩展性.常用的设计模式,比如简单工厂设计模式,核心就是多态. 其实多态就是:允许将子类类型的指针赋值给父类类型的指针.也就是同一操作作用于不同的对象,可以有不同的

封装,多态,继承. super的方法,类的约束,

1.python的三大特性:python的三大特性,封装,多态,继承封装:函数 模块 类 对象多态 : python中默认支持多态继承: python中的子类继承父类属性和方法 鸭子类型: 看着像鸭子,它就是鸭子python中处处都是鸭子 # 两者之间没有任何关系,但是都遵循着一个隐形的标准两个类中的鸭子类型:# 优点:1,统一标准,减少了词汇量# 2,两者之间没有任何耦合性,但是可以产生关系,其中一个的对象可以调用另一个类的方法# 其他例子 :index pop clear 2. super的

c# 多态的美丽(虚方法、抽象、接口实现)

面向对象3大特性:封装.继承.多态. 面向对象2大原则: 1)里氏替换原则:子类可以给父类,父类不能赋给子类. 2)开放封闭原则: 封装变化,降低耦合.(对扩展开放,对修改封闭) **************** 多态定义:允许子类类型的指针赋值给父类类型的指针.父类可以成为一种子类,实现这种子类的操作. 多态实现:1)虚函数 overwrite  2)抽象类:abstract class,简化父类的定义,在不需要创建父类对象的场合,可以使用抽象类 2)接口 多态目的:可以屏蔽不同子类对象之间的

C#多态联系之虚方法

class Class1 { static void Main(string[] args) { YuanGong yg = new YuanGong(); JingLi jl = new JingLi(); LaoBan lb = new LaoBan(); //声明一个数组的父类 YuanGong[] ayg = { yg, jl, lb }; //遍历数组 for (int i = 0; i < ayg.Length; i++) { ayg[i].DaKa(); } Console.Rea

c# 多态实现_虚方法

实现方法: 虚方法, 抽象类, 接口 1.虚方法 将父类的方法标记为虚方法,使用关键字virtual,这个方法可以被子类重新写一遍. 在父类的方法前面加上一个virtual,在子类的方法前面加上一个override:如果子类的方法前面不加override,编译器不会报错,但这样的话,就无法通过父类来调用子类的方法,因为这个方法成了子类的独有的方法,只是名字与父类相同而已,与父类无关. 注意:如果需要对父类的方法进行重写的话,一定要在子类的方法面前加上overrie进行修饰. 什么时候使用:几个类

第10章 接口、继承与多态 多态

利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理. 如果定义一个四边形类,让它处理所有继承该类的对象,根据“向上转型”原则可以使每个继承四边形类的对象作为draw()方法的参数,然后在draw()方法中做一些限定就可以根据不同图形类对象绘制相应的图像,从而更为通用的四边形类来取代具体的正方形类和平行四边形类.这样处理能够很好地解决代码冗余问题,同时也易于维护,因为可以加入任何继承父类的子类对象,而父类方法也无需修改. package ten; public class Qua

oc语言学习之基础知识点介绍(四):方法的重写、多态以及self、super的介绍

一.方法重写 /* 重写:当子类继承了父类的方法时,如果觉得父类的方法不适合,那么可以对这个方法进行重新实现,那么这个就重写. 注意:也就是说,一定只能发生在父类和子类关系中. 然后是子类重新实现父类的方法,绝对不是再写一个自己类的方法. 代码中原话叫:子类重写父类方法. 因为父类定义的方法不一定适用于子类. 注意:如果有重写,那么调用的是自己重写后的方法,如果没有重写,那么就调用的是父类的方法. 所以我们方法有一个执行的过程: 1.先去自己类里面找这个方法,如果找到就执行. 2.如果没找到,就