我是一所高中的计算机科学老师,我正在用 Java 教授多态性。我的学生问我这个问题,我没有很好的答案。
假设我有一个 Employee 类。另外,我有一个 Lawyer 类,它是 Employee 的子类。现在假设方法 sue() 是在 Lawyer 类中定义的,但不在 Employee 类中。鉴于所有这些,以下代码将导致错误:
Employee ed = new Lawyer();
ed.sue();
问题是,为什么Java语言是这样构建的,这会导致错误?所有Lawyers都可以起诉,而ed是指向Lawyer对象的引用,所以ed不应该可以起诉吗?
这与人们在现实世界中、在计算机编程环境之外的随意对话中所期望的逻辑推理完全相反。假设我在办公楼里与首席执行官交谈,他说:
“这是埃德,我的一名员工,他是一名律师。不是我公司的每个员工都可以起诉人,但是律师可以起诉人。”
如果我听到首席执行官这么说,我就会得出结论,Ed 可以起诉人。
我在这个比喻中遗漏了什么吗?基本上我是说这段代码:
Employee ed = new Lawyer();
类似于以下语句:
“这是埃德,我的一名员工,他是一名律师。”
我还想说,当我们在 Lawyer 类中定义 sue() 方法,而不是在 Employee 类中时,这相当于说“不是每个员工都可以起诉,但律师可以起诉。”
顺便说一下,我知道 Java 中有一些方法可以绕过这个限制。例如,我可以让艾德扮演一名律师。
Employee ed = new Lawyer();
((Lawyer)ed).sue();
这不会导致错误。
或者,我可以首先简单地使用律师推荐信:
Lawyer ed = new Lawyer();
ed.sue();
这不会导致错误。
或者,我可以在 Employee 类中定义 sue() 方法,这样律师的 sue() 版本就会覆盖 Employee 的版本,然后原来的两行代码就可以正常运行而不会出现错误。
但是为什么我们需要使用这些解决方法呢?为什么指向子类对象的超类引用不能调用子类的方法?为什么 Java 是这样构建的?
这不是“Java 中的限制”。静态类型允许我们对引用可以引用的类型做出非常有用的声明。
Employee ed
告诉您,您不能假设ed
指的是Lawyer
,并且您可以将对Doctor
的引用分配给ed
。能够表达这些约束并让编译器检查它们,可以使您的代码更易于理解,并消除错误来源。
看看你的解决方法:
Employee ed = new Lawyer();
((Lawyer)ed).sue();
如果
ClassCastException
不引用 ed
,则会抛出 Lawyer
。
如果给
sue()
添加一个Employee
方法,你将如何为Lawyer
以外的子类实现它?
唯一正确的解决方案是:
Lawyer ed = new Lawyer();
ed.sue();
明确约束
ed
必须引用 Lawyer
实例。