• Java多态性的“飘渺之旅”


    原文出处:斯武丶风晴


    摘要: 如何从Java多态性进行飘渺之旅呢? 我们用例子来旅行。

      1 朵星人A:人类,是一个很奇妙的物种。
      2 朵星人B:他们好像分为两种,嗯 先生,以及美女?
      3 朵星人C:对,更年轻的有叫 美少女的。
      4 朵星人D:他们之间怎么打招呼的?我们问问AI(编译器大佬)吧。。
      5 朵星人A:可以有。启动吧~

    第一次启动:

      1 /**
      2  * 编译时多态
      3  *
      4  * @author Sven Augustus
      5  */
      6 public class StaticTest {
      7 
      8 	static abstract class Human {
      9 
     10 	}
     11 
     12 	static class Man extends StaticTest.Human {
     13 
     14 	}
     15 
     16 	static class Woman extends StaticTest.Human {
     17 
     18 	}
     19 
     20 	static class Girl extends StaticTest.Woman {
     21 
     22 	}
     23 
     24 	public void sayHello(Object guy) {
     25 		System.out.println("你...");
     26 	}
     27 
     28 	public void sayHello(Human guy) {
     29 		System.out.println("你好");
     30 	}
     31 
     32 	public void sayHello(Man guy) {
     33 		System.out.println("您好,先生");
     34 	}
     35 
     36 	public void sayHello(Woman guy) {
     37 		System.out.println("您好,美女");
     38 	}
     39 
     40 	public void sayHello(Girl guy) {
     41 		System.out.println("您好,美少女");
     42 	}
     43 
     44 	public static void main(String[] args) {
     45 		StaticTest test = new StaticTest();
     46 		StaticTest.Human manAsGuy = new StaticTest.Man();
     47 		StaticTest.Human womanAsGuy = new StaticTest.Woman();
     48 		StaticTest.Woman girlAsWoman = new StaticTest.Girl();
     49 		test.sayHello(manAsGuy);
     50 		test.sayHello(womanAsGuy);
     51 		test.sayHello(girlAsWoman);
     52 	}
     53 
     54 }
    View Code

    输出:

    编译器大佬告诉了他们答案。

      1 朵星人众人:纳尼,他们叫的好奇怪啊?好没礼貌啊,没“您”。为毛呢??还有最后一个明明是美少女,你怎么叫美女啊?!
      2 编译器大佬:你们好意思吗,你们都标为人类或美女。
      3           我怎么知道他们具体是先生还是美女,亦或是美少女啊!!!所以我就只知道这么多。

    StaticTest.Human manAsGuy = new StaticTest.Man();

    test.sayHello(manAsGuy);

    从这里,Human 称为 声明类型(也有叫静态类型) ,Man 称为 实际类型

    很清楚,现在关键在于 manAsGuy 作为 参数。

      1 在编译阶段,编译器就可以根据 参数 的 声明类型(或叫静态类型) 决定使用哪个重载版本的方法。
    朵星人A:说的好有道理,我们让他们自己称呼吧。
    朵星人B:可以有。
    朵星人C:赞同。
    朵星人D:不会有问题吧?
    朵星人A:不会的。就这样吧~~~

    第二次启动:

      1 /**
      2  * 运行时多态
      3  *
      4  * @author Sven Augustus
      5  */
      6 public class DynamicTest {
      7 
      8 	static abstract class Human {
      9 
     10 		public void sayHello() {
     11 			System.out.println("你好");
     12 		}
     13 	}
     14 
     15 	static class Man extends DynamicTest.Human {
     16 
     17 		public void sayHello() {
     18 			System.out.println("您好,我是Y先生");
     19 		}
     20 	}
     21 
     22 	static class Woman extends DynamicTest.Human {
     23 
     24 		public void sayHello() {
     25 			System.out.println("您好,我是X美女");
     26 		}
     27 	}
     28 
     29 	public static void main(String[] args) {
     30 		DynamicTest.Human manAsGuy = new DynamicTest.Man();// 注释1
     31 		DynamicTest.Human womanAsGuy = new DynamicTest.Woman();
     32 		manAsGuy.sayHello();
     33 		womanAsGuy.sayHello();
     34 	}
     35 
     36 }
    View Code

    输出:

    编译器大佬好像去休息了,交给社会JVM回答问题。

    DynamicTest.Human manAsGuy = new DynamicTest.Man();// 注释1

    manAsGuy.sayHello();

    这里与上个例子不同的是,manAsGuy不作为 参数,是作为引用变量 去 调用方法。

    这时候,编译器只知道 引用变量manAsGuy的 静态类型,对于实际类型 就无能为力。因此在运行时由JVM方法表动态绑定。

    我们发现,
    
    引用变量调用方法 的时候,决定去调用哪个方法,是由 实际类型 在运行时确认调用哪个方法,而不是 声明类型(或叫静态类型)。
    
    呵呵,当然这个解释还是比较勉强。我们继续。
    朵星人A:咦,他们太客气了,都“您好”,还首先介绍自己,好不矜持啊。
    朵星人B:地球人是这样的吗??
    朵星人C:是这样的。他们不知道对方是谁,只知道自己是谁的时候是这样的。
    朵星人D:好像不是啊。
    朵星人A:那你说是怎样的?
    朵星人D:他们需要知道对方是谁啊!
    朵星人B:有道理、
    朵星人C:赞同。
    朵星人A:就这样吧~~~

    第三次启动:

      1 /**
      2  * 编译时多态 和 运行时多态 混合测试
      3  *
      4  * @author Sven Augustus
      5  */
      6 public class MixTest {
      7 
      8 	static class Human {
      9 
     10 		public String sayHello(MixTest.Human human) {
     11 			return "你好";
     12 		}
     13 
     14 		public String sayHello(MixTest.Man human) {
     15 			return "您好,先生";
     16 		}
     17 
     18 		public String sayHello(MixTest.Woman human) {
     19 			return "您好,美女";
     20 		}
     21 
     22 		/*public String sayHello(MixTest.Girl human) {
     23 			return "您好,美少女";
     24 		}*/
     25 	}
     26 
     27 	static class Man extends MixTest.Human {
     28 
     29 		public String sayHello(MixTest.Human human) {
     30 			return "你好,我是Y先生";
     31 		}
     32 
     33 		public String sayHello(MixTest.Woman human) {
     34 			return "您好,美女,我是Y先生";
     35 		}
     36 
     37 		public String sayHello(MixTest.Girl human) {
     38 			return "您好,美少女,我是Y先生";
     39 		}
     40 
     41 		// 先生对先生比较谨慎,没那么快介绍自己 =。=
     42 	}
     43 
     44 	static class Woman extends MixTest.Human {
     45 
     46 		public String sayHello(MixTest.Human human) {
     47 			return "你好,我是X美女";
     48 		}
     49 
     50 		public String sayHello(MixTest.Woman human) {
     51 			return "您好,美女,我是X美女";
     52 		}
     53 
     54 		public String sayHello(MixTest.Girl human) {
     55 			return "您好,美少女,我是X美女";
     56 		}
     57 
     58 		// 美女对先生比较含蓄,没那么快介绍自己 =。=
     59 	}
     60 
     61 	static class Girl extends MixTest.Woman {
     62 
     63 		public String sayHello(MixTest.Human human) {
     64 			return "你好,我是O美少女";
     65 		}
     66 
     67 	}
     68 
     69 	public static void main(String[] args) {
     70 		MixTest test = new MixTest();
     71 		MixTest.Human guy = new MixTest.Human();
     72 		MixTest.Human manAsGuy = new MixTest.Man();
     73 		MixTest.Man man = new MixTest.Man();
     74 		MixTest.Human womanAsGuy = new MixTest.Woman();
     75 		MixTest.Woman woman = new MixTest.Woman();
     76 		MixTest.Girl girl = new MixTest.Girl();
     77 
     78 		System.out.print("假设大家在QQ等聊天软件上认识,这时候一般来招呼如下");
     79 		System.out.println("当然先生对先生比较谨慎,没那么快介绍自己:");
     80 		printMessage("一个人 欢迎 一个人", guy.sayHello(guy),
     81                      "[我不想你知道我的性别,我也不知道你的性别,囧]");
     82 		printMessage("一个人 欢迎 一名先生", guy.sayHello(man),
     83                      "[我不想你知道我的性别,我知道你是一名先生,嘿嘿]");
     84 		printMessage("一个人 欢迎 一名美女", guy.sayHello(woman),
     85                      "[我不想你知道我的性别,我知道你是一名美女,哈哈]");
     86 		printMessage("一个人[其实是先生] 欢迎 一个人", manAsGuy.sayHello(guy),
     87                      "[我不想你知道我的性别,但是你知道我是先生,可是我不知道你的性别,汗]");
     88 		printMessage("一个人[其实是先生] 欢迎 一个人[其实是先生]", manAsGuy.sayHello(manAsGuy),
     89                      "[我不想你知道我的性别,但是你知道我是先生,可我不知道你的性别(或许你是一名先生),呵]");
     90 		printMessage("一个人[其实是先生] 欢迎 一个人[其实是美女]", manAsGuy.sayHello(womanAsGuy),
     91                      "[我不想你知道我的性别,但是你知道我是先生,可我不知道你的性别(或许你是一名美女),嘿]");
     92 		printMessage("一个人[其实是先生] 欢迎 一名先生", manAsGuy.sayHello(man),
     93                      "[我不想你知道我的性别,但是你知道我是先生,我知道你也是一名先生,呵呵]");
     94 		printMessage("一个人[其实是先生] 欢迎 一名美女", manAsGuy.sayHello(woman),
     95                      "[我不想你知道我的性别,但是你知道我是先生,我知道你是一名美女,噢噢]");
     96 		printMessage("一个人[其实是先生] 欢迎 一名美少女", manAsGuy.sayHello(girl),
     97                      "[我不想你知道我的性别,但是你知道我是先生,我知道你是一名美少女,噢]");
     98 		printMessage("一名先生 欢迎 一个人 ", man.sayHello(guy),
     99                      "[我是一名光明磊落的先生,可我不知道你的性别,额]");
    100 		printMessage("一名先生 欢迎 一个人[其实是先生]", man.sayHello(manAsGuy),
    101                      "[我是一名光明磊落的先生,可我不知道你的性别(或许你是一名先生),咦]");
    102 		printMessage("一名先生 欢迎 一个人[其实是美女]", man.sayHello(womanAsGuy),
    103                      "[我是一名光明磊落的先生,可我不知道你的性别(或许你是一名美女),嗯]");
    104 		printMessage("一名先生 欢迎 一名先生", man.sayHello(man),
    105                      "[我是一名光明磊落的先生,我知道你也是一名先生,非常好,我先观察]");
    106 		printMessage("一名先生 欢迎 一名美女", man.sayHello(woman),
    107                      "[我是一名光明磊落的先生,我知道你是一名美女,我先介绍自己]");
    108 		printMessage("一名先生 欢迎 一名美少女", man.sayHello(girl),
    109                      "[我是一名光明磊落的先生,我知道你是一名美少女,我先礼貌介绍自己]");
    110 	}
    111 
    112 	private static volatile int index = 1;
    113 
    114 	private static void printMessage(String title, String message, String narrator) {
    115 		System.out.println((index++) + "" + String.format("%-35s%-20s%s",
    116                                                            new String[]{title, message, narrator}));
    117 	}
    118 
    119 }
    View Code

    输出:

    社会JVM一片混沌,不知所云,乱出答案。

    朵星人A:看不懂人类的世界,太复杂了吧。
    朵星人B:地球人是这样的吗??
    朵星人C:是这样的。他们百变。
    朵星人D:额。让人类自己解读吧。

    现在 这个例子 混杂了 编译时多态 和 运行时多态。

    因此,我们首先观察一下,发现:

    a、结果 1-3中,是 单纯的编译时多态。

    b、结果 4-8 对比 10-14中,“一个人[其实是先生]”  和 “ 一名先生 ”( 引用变量) 在欢迎(方法调用) 同一个类型的人(同一静态类型参数)的时候,欢迎语是一致(调用的具体方法可能一致的?)。

    c、结果9 对比 15 中,我们发现结论 b 不生效了。为什么呢?我们发现  一个人[其实是先生]” 和 “ 一名先生 ”还是有区别的。

    我们仔细观察一下代码实现。

    Human类有 对 Human、Man、Woman的欢迎方法

    Man类有 对 Human、Woman、Girl的欢迎方法

    结果9:

    MixTest.Human manAsGuy = new MixTest.Man();

    manAsGuy.sayHello(girl), 

    因为manAsGuy 声明是Human 类,方法从Human类开始搜索,Human类没有欢迎Girl的方法,

    因此按照最适合方法版本,兼容找到了Human 类的欢迎Woman的方法,

    又因为实际类型是Man类,该方法有重写,因此实际执行了Man类的欢迎Woman的方法。

    首先定义声明类型 与 实际类型 存在向上转型的情况,称之为“动态绑定”。
    
    如 Parent p = new Children();

    我们得出了一个

    方法调用步骤:

    1、编译器检查引用对象的声明类型、方法名;
    
    假设我们调用x.func(args) 方法,如果x声明为X类,那么编译器会列举X类所有名称为func的方法,以及从X类的超类继承的所有名称为func的方法。
    
    2、接下来,编译器检查方法提供中的参数类型
    
    如果在第1步中列举的所有func方法中找到一个 参数类型 与 args的声明类型 最为匹配的,
    
    如果方法调用,不是动态绑定,编译器就确定调用 该func(args)方法。
    
    如果方法调用,是动态绑定。那么继续下一步。
    
    --------------------------------以下动态绑定-------------------------------------------
    
    3、当程序运行并且使用动态绑定调用方法时,JVM会调用x对象实际类型相匹配的方法版本。
    
    意思就是,如果 X x= new T();实际类型是T类,那么如果T类有定义了与第2步方法签名一致的func(args)方法,也就是重写,那么T类的该func(args)方法会被JVM实际调用,否则就在T类的超类X类中继续寻找。
  • 相关阅读:
    普通百姓如何应对通货膨胀
    经济
    将到来的战略转变:移动 Web 还是移动 Apps?
    ASP.Net 第一天笔记 MVC 控制器与视图数据传递注意事项
    关于阿里云 ETC服务器 端口开放问题
    .net 委托 +lamda表达式
    de4Dot用法 解决 .net程序 reflecter反编译 “索引超出了数组界限”问题
    fastReport.net 初了解
    关于JQuery Ajax 跨域 访问.net WebService
    JQuery AJAX 通过一般处理程序 取列表
  • 原文地址:https://www.cnblogs.com/huangwenjie/p/7604426.html
Copyright © 2020-2023  润新知