QS前50, THE前15世界名校, 经典Java多态试题!10道题你能做对5道吗!

本文深入剖析了一道来自德国慕尼黑工业大学的Java多态试题,详细解释了多态的难点,如多继承、静态与动态调用、方法重写等,并通过10道试题的解答,帮助读者巩固多态概念。内容涵盖试题难点分析、原题演练及答案解析,旨在提升读者对多态的实际运用能力。
摘要由CSDN通过智能技术生成

QS前50, THE前15世界名校, 经典Java多态试题!10道题你能做对5道吗!

在面对对象编程的过程中,多态是一条完全无法绕开的必经之路。多态属于计算机编程中的基础,同时也是基础中的难点,重点,易混淆点。将多态熟练掌握,不仅有助于我们在考试和面试中,面对试题更加得心应手;另一方面,也有助于我们在未来的学习和工作的过程中,将整个程序的结构梳理得更加清晰。

我个人在网上搜索、查找过许多关于Java多态的考试或者面试试题。一直没有找到一份具有代表性的试题。
网上大部分的试题,有下列几个致命的缺陷:

  1. 包含的情况不够全面,无法让大家透彻了解,使我们对多态的了解浮于表面。
  2. 大部分题目过于基础,说来说去就是关于能否强制转换,不足以与各大名校、名企的试题比拟,缺乏实战意义。
  3. 人云亦云,大部分讲解不够清晰,大多是复制和转载,甚至大多博主自身对多态的概念,都没有完全掌握,就开始讲解多态。

近期得到了一份德国慕尼黑工业大学计算机系(2021-THE世界排名第14位,QS世界排名第50位)关于多态的一道试题,大家可以尝试一下,能完全做对的同学,想必是对多态已经得心应手。
在这里插入图片描述

在这里插入图片描述

难点分析

此题的难点在于:

  1. A、B、C三个类为多继承关系,形成了多继承关系中的多态。
  2. 各类中的方法有成员方法,也有静态方法,且方法名相似,甚至相同。在继承关系下,不仅要寻找到对应方法,还要注意方法是通过继承还是覆写。
  3. 各个方法的参数名称类似,再加上创建对象的索引/对象名相似,再加上对象的向上向下类型转换。
  4. 综上所述,所有对多态没有全面理解,平日拉跨,考前磨枪,滥竽充数的小同学,一定会“biu”得一下,被揪出来!

名校原题演练

public class Poly {
	static class A {
		public void f() {
			System.out.println("A.f()");
		}
		public void f(A a) {
			System.out.println("A.f(A)");
			a.g();
		}
		public void f(B b) {
			System.out.println("A.f(B)");
			b.g();
		}
		public static void g() {
			System.out.println("A.g()");
		}
		public void h(A a, B b) {
			System.out.println("A.h(A, B)");
		}
	}
	
	static class B extends A {
		public void f(A a) {
			System.out.println("B.f(A)");
			a.f();
		}
		public static void g() {
			System.out.println("B.g()");
		}
		public void h(B b, A a) {
			System.out.println("A.h(B, A)");
		}
	}
	
	static class C extends B {
		public void f(B b) {
			System.out.println("C.f(B)");
			((C) b).f((C) b);
		}
		public void f(C c) {
			System.out.println("C.f(C)");
			c.g();
		}
		public void h() {
			System.out.println("C.h()");
		}
	}
	
	public static void main(String[] args) {
		A a = new A();
		B b = new B();
		A c = new C();
		a.f(a); // Call 1
		b.f(a); // Call 2
		b.f(b); // Call 3
		c.g(); // Call 4
		c.f(b); // Call 5
		c.h(); // Call 6
		((C) c).f(); // Call 7
		c.f(new C()); // Call 8
		a.f(null); // Call 9
		b.h(b, b); // Call 10 
	}
}

答案与个人解析

在多态试题中,最终选择调用什么方法,与参数名、变量名无关,所以不要害怕相同的名称会混淆。
多态的关键在于分清楚,创建出的对象在调用方法时,对象在静态调用,以及动态调用时,分别是以哪个类型来调用的。

A a = new A();   我们可以看成:静态A动态A --> (A,A)
B b = new B();   我们可以看成:静态B动态B --> (B,B)
A c = new C();   我们可以看成:静态A动态C --> (A,C)

Call 1:   	A.f(A)
			A.g()
			
解析:		a.f(a); // Call 1
			看成:(A A).f(A A)
            静态:A调用成员方法f(A)
            动态:因为(A A),括号内第二个A代表,动态
            调用也是A --> 打印 A.f(A) 
                 
            继续调用(A A).g()
            g()static方法,所以只看静态调用
            反正这里静动都是A,所以打印 A.g()就好
            

Call 2:	B.f(A)
			A.f()

解析:		b.f(a); // Call 2
			看成:(B B).f(A A)
			静态:B调用成员方法f(A)
            动态:因为(B B),括号内第二个B代表,动态
            调用也是B --> 打印 B.f(A) 
                 
            继续调用(A A).f()
            静态:A调用成员方法f()
            动态:因为(A A),括号内第二个A代表,动态
            调用也是A --> 打印 A.f() 


Call 3:	A.f(B)
			B.g()

解析:		b.f(b); // Call 3
			看成:(B B).f(B B)
			静态:B调用成员方法f(B),但是在B类中找不
			到f(B)方法。因为B是A的子类,所以我们进入
			A类中找f(B)方法,若A类中有f(B)方法,B类
			就会继承,结果发现A中存在f(B)方法
			动态:因为(B B),括号内第二个B代表,动态
			调用也是B --> 打印 A.f(B)
            
            继续调用 (B B).g()
            g()static方法,所以只看静态调用
            反正这里静动都是B,所以打印 B.g()就好


Call 4:	A.g()

解析:		c.g(); // Call 4
			看成:(A C).g()
			g()static方法,所以只看静态调用!!!
            这里静动都是A,动态是C,但是static静态方
            法完全不看动态调用,所以打印 A.g()


Call 5:	C.f(B)
			java.lang.ClassCastException

解析:		c.f(b); // Call 5
			看成:(A C).f(B B)
			静态:A调用成员方法f(B),静态选定A.f(B)
			动态:因为(A C),括号内第二个C代表,动态
			调用是C,找C类中是否含有f(B)方法,找到后,
           	打印 C.f(B)
			
			继续调用((C) b).f((C) b)
			看成((C)B B).f((C)B B),但是对象b
			无论是静态还是动态,都是B,且B是C的父类,
			所以 (C)b 的强转是错误的。所以运行报错:
			java.lang.ClassCastException
            除非 B b = new C(); 强转(C)b 才正确
			

Call 6:	Method not found //找不到方法

解析:		c.h(); // Call 6
			看成:(A C).h()
			静态:A调用成员方法h(),但是A类中根本
			不存在无参数的h()方法,所以静态编译就错误
			程序都无法通过编译(编写程序的区域已经报
			错),更不要谈动态调用了


Call 7:	A.f()

解析:		((C) c).f(); // Call 7
			看成:((C)A C).f()
			因为c可以看成(静A 动C),其中是含有C的,
			所以强转(C)c 是可行的
			静态:C调用成员方法f(),但是在C类中找不到
			f()方法。因为C是B的子类,所以我们进入B类
			中找f()方法,若B类中有f()方法,C类就会继
			承,结果发现B中,同样不存在f()方法
			因为B是A的子类,所以我们进入A类
			中找f()方法,若A类中有f()方法,C类同样会
			通过B类继承A类,结果发现A中存在f()方法,
			静态选中方法C.f(),只是C.f()在C中是“隐身”
			的,在A类中可以看到f()的方法体
			动态:因为((C)A C),强转成功后也就是
			(C C),括号内第二个C代表,动态调用也是C
			--> 打印 A.f(),注意这里打印的是“A.f()”,
			是因为方法体中要求的,但是实际我们是
			通过C.f()


Call 8:	C.f(B)
			C.f(C)
			B.g()

解析:		c.f(new C()); // Call 8
			看成:(A C).f(C C)
			静态:A调用成员方法f(C),但是在A类中找不
			到f(C)方法。因为C是B的子类,所以我们在A类
			中找f(B)方法,若A类中有f(B)方法,结果发现
			A中存在f(B)方法,静态选中A.f(B)
			动态:因为(A C),括号内第二个C代表,动态
			调用是C,在C类中找到f(B) --> 打印 C.f(B)
            注意:虽然C类中含有方法f(C),但是所有动态
            调用,都是建立在静态调用的基础上。
            静态调用在A中,只能调到f(B),所以动态最终
            调用的也一定是方法f(B)

			继续调用((C) b).f((C) b)
			注意:最初我们使用的是c.f(new C()),
			也就是(A C).f(C C),只是因为静态调用在A
			中只能找到f(B),但此时参数依然
			是 C-->(A C).f(C C)
			这里的b只是参数名称叫“b”,实际上可以
			看成:((C)C C).f((C)C C)
			原本就是C,完全不需要转换
			静态:C调用成员方法f(C),选定方法C.f(C)
			动态:因为(C C),括号内第二个C代表,动态
			调用也是C --> 打印 C.f(C)

			继续调用c.g()
			这里的c也同样,不再是我们自己最初创建的a,
			b,c里的c了,而是参数名“c”,此时参数看上
			一步调用时给的,是 
			C-->((C)C C).f((C)C C)
			g()static方法,所以只看静态调用
            反正这里静动都是C,结果我们发现C类中
            并没有方法g(),因为C是B的子类,所以我们
            进入B类中找f()方法,若B类中有f()方法,
            C类就会继承,结果发现B中,存在g()方法,
            打印B.g()
            同样这里打印的是“B.g()”,是因为方法体中
            要求的,但是实际我们是通过C.g()

			
Call 9:	A.f(B)
			B.g()

解析:		a.f(null); // Call 9
			看成(A A).f(null)
			静态:A调用成员方法f(null),传入的参数
			为null,注意:调用f(null)和调用f()是
			不一样的!!! 
			f(null)是有参数的,参数为null
			f()是无参数的
			但是A类中有两个,带参数的f(A)f(B)方法
			若A,B为继承关系,由于null为不确定类型的
			对象,所以被当作子类型B处理
			若A,B没有继承关系,参数null无法在f(A)f(B)方法中抉择,直接编译报错,无法运行
			所以(A A).f(null)静态选中A.f(B)
			动态:因为(A A),括号内第二个A代表,动态
			调用也是A --> 打印 A.f(B)

			继续调用(B(null) B(null)).g()
			下面细心的同学会发现,此时参数是以null为
			对象,为什么null调用方法,不会出现空指
			针异常 NullPointerException呢??????
			请注意!!!g()static方法,静态static方
			法是通过类名调用的,与对象完全无关!
			此时null作为一个B类型的参数,其实就是B类
			调用g(),所以打印 B.g()
			
			
Call 10:	Ambiguous Method //方法冲突

解析:		b.h(b, b); // Call 10
			看成:(B B).h(B B, B B)
			静态:B调用成员方法h(B, B),发现B类中含
			有h(B, A)方法,根据之前说到的,由于B继承
			A,所以静态调用是可以调用h(B, A)方法的。
			但是我们依然想寻找有没有更合适的
			h(B, B)方法,所以我们向父类A中寻找,
			最终我们在A类又发现方法h(A, B)。
			好巧不巧,这两个方法都是“将就”的方法
			一个B中h(B, A),一个A中h(A, B),都没有
			方法h(B, B),两者不存在优先级,那么
			静态调用B,无法在B中h(B, A)和A中h(A, B)
			两者里抉择,所以直接编译报错,无法运行

			若在A类或B类中含有方法h(B, B),则
			不会报错,且调用此方法
			B类中含有h(B, B),A类中含有h(A, B),
			直接选择调用B中的h(B, B)很好理解,因为B
			中的h(B, B)是最近的且类型完全符合,调
			用优先级最高。
			但是为什么如果A中含有h(B, B),B中含
			有h(B, A),依然先调用A中的h(B, B)?????
			因为在多态中,如果方法或参数的静态调用
			不匹配,只能往父类(super)里寻找,而
			优先级从高到低:
			this.methode(O) > super.methode(O) 
			> this.methode((super)O) > 
			super.methode((super)O)

快速总结(建议先阅读:答案与个人解析)

Call 1:
(A A).f(A A):静动相同,直接找下去

Call 2:
(B B).f(A A):静动相同,直接找下去

Call 3:
(B B).f(B B):此时B类中没有f(B)方法,先进A类找有没有继承的f(B)方法

Call 4:
(A C).g():g()为静态static方法,只看静态调用 --> A.g(),且完全不存在动态

Call 5:
(A C).f(B B):对于多态的强转是:(子)父,自转是:(父)子
A C可强制转换为©A C,但B B不可强转为©B B,出现java.lang.ClassCastException,强转失败与成功不单由继承关系决定,而看动态

Call 6:
(A C).h():A中没有h()方法,直接编译错误,即使C有h(),但静态编译已经报错

Call 7:
(( C) A C).f():静态就在C中找,因为强转成功,若C中无f(),向父类中挨个找B中f(),A中…

Call 8:
(A C).f(C C):进A静态找f(C ),若没有f(C ),参数按继承关系向上f(B), f(A)…找定后再动态进C调用,只有静态选定后,按静态的选择去动态找
例:A中只有f(A), f(B),C中有f(A), f(B), f(C )。看起来动态C.f(C )更配,但是静态在A中选定了f(B),所以动态调用结果为C.f(B)

Call 9:
(A C).f(null):传入参数为null,当A中有两个方法f(A),f(B)时,若A,B为继承关系,则null为不确定类型的对象,所以被当作子类型B处理。若A,B无继承关系,则null无法选择进入哪一个方法,导致编译报错

Call 10
(B B).h(B B, B B):此时有两个方法:h(A, B),h(B, A),main方法无法自行选择,所以编译报错,若在A类或B类中含有方法h(B, B),则不会报错,且调用此方法

多态中,如果方法或参数的静态调用不匹配,只能往父类(super)里寻找,优先级从高到低:
this.methode(O) > super.methode(O) > this.methode((super)O) > super.methode((super)O)

说在最后

至此,此道多态的试题就讲解完毕了,不知道在看解析前,同学有没有做对一半呢。做对一半的同学基本功还是比较扎实的,没有做对一半的同学也没有关系,这道题的难度确实较高。若有同学有需要,未来我会继续讲解关于多态的基础知识。
不断努力,持续进步!

美国前总统林肯说:我这个人走得很慢,但我从来不后退!
黑石公司创始人苏世民说:财富管理首先不要亏钱!
韩国前世界冠军棋手李昌镐说:每手棋,我只求51%的胜率!
人生当中无论是我们的生活,家庭,情感,学习,工作,竞争,挣钱,等等。最好的方式就是保证朝着正确的方向持续改善,即使每次只是提升一点,即使每次只是向前半步!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值