我向在大学学习这门课程的学生讲授Java编程语言的基础知识。
今天他们中的一个让我对她的问题感到困惑,所以我告诉她给我一天的时间来思考这个问题,我会尽可能准确地给她答案。
她告诉我,当她在考试中使用关键字instanceof
时,老师非常生气。
另外,她说老师说没有办法证明多态性在使用这个词时是如何运作的。
我想了很多,试图找到一种方法来证明在某些情况下我们需要使用instanceof
,而且即使我们使用它,在这种方法中仍然存在一些多态性。
所以这是我做的例子:
public interface Animal
{
public void talk();
}
class Dog implements Animal {
public void talk() {
System.out.println("Woof!");
}
}
public class Cat implements Animal
{
public void talk() {
System.out.println("Meow!");
}
public void climbToATree() {
System.out.println("Hop, the cat just cimbed to the tree");
}
}
class Hippopotamus implements Animal {
public void talk() {
System.out.println("Roar!");
}
}
public class Main {
public static void main(String[] args) {
//APPROACH 1
makeItTalk(new Cat());
makeItTalk(new Dog());
makeItTalk(new Hippopotamus());
//APPROACH 2
makeItClimbToATree(new Cat());
makeItClimbToATree(new Hippopotamus());
}
public static void makeItTalk(Animal animal) {
animal.talk();
}
public static void makeItClimbToATree(Animal animal) {
if(animal instanceof Cat) {
((Cat)animal).climbToATree();
}
else {
System.err.println("That animal cannot climb to a tree");
}
}
}
我的结论如下:
第一种方法(APPROACH 1)是一个如何编程到接口而不是实现的简单演示。我认为多态性在方法 makeItTalk(Animal animal)
的参数中是清晰可见的,并且通过使用动物对象也可以调用方法谈话的方式。(这部分是可以的)
第二部分让我感到困惑。她在考试的某个时刻使用instanceof
(我不知道他们的考试是怎么样的),并且由于老师说,你没有证明多态性,因此没有被正确接受。
为了帮助她了解何时可以使用instanceof
,我想告诉她,她可以使用它,当她需要调用的方法不在界面中时,但它只是在其中一个实施课程。
正如你所看到的,只有猫可以爬到树上,让河马或狗爬到树上是不合逻辑的。我认为这可能是何时使用 instanceof
但是方法2中的多态性呢?
你在那里看到多少种多少用途(只有方法2)?
你认为这一行有多种类型的多态性吗?
((Cat)animal).climbToATree();
我认为确实如此,因为为了实现这种类型的Casting,对象需要具有IS-A关系,这在某种程度上是多态的。
您如何看待,是否正确?
如果是的话,你会用自己的话来解释,那个演员依赖于多态?
答案 0 :(得分:5)
instanceof
方法被认为是坏的原因很简单。猫不是唯一可能爬树的Animal
。
如果您需要添加Koala课程,那么会发生什么。然后,您的简单if
变得不那么简单or
。那么,当你添加另一个类时会发生什么?还有一个。而另一个。这是instanceof
被视为糟糕的主要原因。因为它将实现耦合到具体类,而不是为被调用者打开它来确定要做什么。
如果对无法攀爬的动物进行调用,只需实施makeItClimbToATree()
方法即可抛出CantClimbTreesException
。这样你就可以拥有两全其美。易于实施,易于扩展。
恕我直言,instanceof
只有一个真正有效的用途:在一个测试用例中,从一个方法测试返回的实例与预期的返回类型匹配(用非类型安全的语言)。
基本上任何其他用途都可能被重构或以不同方式设计,以消除对其使用的需要。
另一种看待它的方法是:多态性允许您从代码中消除几乎所有条件语句。您无法摆脱的唯一条件(至少所有条件)都在对象创建方法中(例如在必须根据运行时参数选择类的工厂中)。几乎任何其他条件都可以被多态性取代。因此,任何执行条件执行的东西都是反多态的。这并不是说它很糟糕(Good and Good Enough之间存在巨大差异),但在学术讨论中,它并不是多态的......
永远不要忘记60/60规则。总开发时间的60%将用于维护您编写的代码,并且60%的时间将用于添加新功能。让维护更轻松,您的生活也会更轻松。这就是为什么instanceof
不好的原因。它使初始设计更容易,但使长期维护变得复杂(无论如何更昂贵)......
答案 1 :(得分:4)
在上面的示例中,无需调用
makeItClimbToATree (new Hippopotamus ());
如果makeItClimbToATree不会指望一只动物,可以很容易地避免它,但是更具体的东西,它真的能够攀爬一棵树。允许动物,因此使用动物的必要性是不可见的。如果你在动物名单中管理动物,那将更加明显。
虽然ircmaxells解释开始很好,但在介绍考拉和其他树木登山者时,他并没有看到隐藏在海葵中的第二个扩展:海洋风暴,冬季睡眠,蓝眼睛,虫儿等动物的不同能力,等等。你最终会得到boolean over boolean,不断重新编译基类,以及打破扩展的客户类,这需要再次重新编译,并且无法以类似的方式介绍它们自己的可能性。
客户A需要客户B声明NotBugEatingException,以使您的行为进入基类。
引入您自己的界面,结合instanceof,是一种更清晰的方法,更灵活。客户A可能会定义潜水LikeAPenguin和客户B大肆宣传,他们彼此都不了解,既不会影响Animal类,又不会引发无用的重新编译。
import java.util.*;
interface Animal {
public void talk ();
}
interface TreeClimbing {
public void climbToATree ();
}
class Dog implements Animal {
public void talk () { System.out.println("Woof!"); }
}
class Cat implements Animal, TreeClimbing {
public void talk () { System.out.println("Meow!"); }
public void climbToATree () { System.out.println ("on top!"); }
}
public class TreeCriterion {
public static void main(String[] args) {
List <Animal> animals = new ArrayList <Animal> ();
animals.add (new Cat ());
animals.add (new Dog ());
discuss (animals);
upTheTree (animals);
}
public static void discuss (List <Animal> animals) {
for (Animal a : animals)
a.talk ();
}
public static void upTheTree (List <Animal> animals) {
for (Animal a : animals) {
if (a instanceof TreeClimbing)
((TreeClimbing) a).climbToATree ();
}
}
}
我们不需要第三种动物,狗和猫就足够了。我使它们默认可见而不是公开,以使整个示例适合单个文件。
答案 2 :(得分:2)
你认为这一行有多种类型的多态性吗?
((Cat)animal).climbToATree();
没有。特别是,因为Cat
是示例中的叶类。
我认为确实如此,因为为了实现这种类型的Casting,对象需要具有IS-A关系,这在某种程度上是多态的。
多态性需要IS-A关系,但不是相反。
多态性是指基于抽象接口调度(可能)不同方法的时间。如果您没有调度,那么它不使用多态。在您的示例中,使用instanceof
强制转换为没有子类的类,您无需调度。
(当然,Java中“多态”的方法不止一种。您可以使用接口,使用抽象类或使用具有子类的具体类来实现它......或者可以在其中编写的假设子类接口(以及基于接口的调度)通常是最好的方法,因为它们可以将API与类的标识完全分离。)
另外,使用instanceof
这样的通常表示设计不佳和/或建模不佳。具体来说,它强调了只有猫可以攀爬的假设,如果我们将其他动物包括在模型/程序中,这是非常虚伪的。如果发生这种情况,您的代码就会中断。
答案 3 :(得分:0)
也许我错过了这一点并且没有得到考试问题的背景,但是Animal
是否可以攀爬树应该是实现Animal
的类的一部分。例如,如果Animal
是一个接口,则可以使用方法boolean isCapableOfClimbing()
,然后每个实现类都能够指示其功能。
尝试使动物爬升的方法可以使用它。对于试图让动物爬树的方法检查它是否是特定类的实例是没有意义的,因为那时该方法指定了应该在实现类中指定的东西。一个简单的方法不应该为它正在使用的类提供行为。
关于何时使用instanceof
的问题,一旦几乎总是使用的地方是覆盖类的equals()
方法,因为它只接受Object
而且你通常必须确保它是相同的类型,以便它可以被投射然后进行有意义的比较。
答案 4 :(得分:0)
下面的代码怎么样?它通过将爬树分离为您可以在动物身上实现或不实现的另一个界面来解决一般性问题。它更适合这个问题:爬树不是所有动物的内在属性,只有它们的一部分。至少在我看来它比投掷NotImplementedException
更清晰,更优雅。
public interface Animal {
public void talk();
}
public interface AnimalCanClimbTrees extends Animal {
public void climbToATree();
}
public class Dog implements Animal {
public void talk() {
System.out.println("Woof!");
}
}
/* Animal is probably not needed, but being explicit is never bad */
public class Cat implements Animal, AnimalCanClimbTrees
{
public void talk() {
System.out.println("Meow!");
}
public void climbToATree() {
System.out.println("Hop, the cat just cimbed to the tree");
}
}
class Hippopotamus implements Animal {
public void talk() {
System.out.println("Roar!");
}
}
public class Main {
public static void main(String[] args) {
//APPROACH 1
makeItTalk(new Cat());
makeItTalk(new Dog());
makeItTalk(new Hippopotamus());
//APPROACH 2
makeItClimbToATree(new Cat());
makeItClimbToATree(new Hippopotamus());
}
public static void makeItTalk(Animal animal) {
animal.talk();
}
public static void makeItClimbToATree(Animal animal) {
if(animal instanceof AnimalCanClimbTrees) {
((AnimalCanClimbTrees)animal).climbToATree();
}
else {
System.err.println("That animal cannot climb to a tree");
}
}
}
答案 5 :(得分:0)
instanceof
运算符与多态无关。它仅用于查看对象是否是特定类的实例。您会在equals()
方法中看到此运算符被大量使用,因为该方法将通用Object
作为参数:
public class Cat implements Animal{
@Override
public boolean equals(Object obj){
if (obj == null || !obj instanceof Cat){
//obj is null or not a "Cat", so can't be equal
return false;
}
if (this == obj){
//it's the same instance so it must be equal
return true;
}
Cat catObj = (Cat)obj; //cast to "Cat"
return this.getName().equals(catObj.getName()); //compare the two objects
}
}
如果一个类没有实现一个方法,那么它应该抛出一个异常。我相信你应该抛出的“官方”例外是UnsupportedOperationException
。为了“正确”,我认为Animal
接口应该有public void climbToATree();
方法。 climbToATree()
和Dog
类中的Hippo
方法应抛出UnsupportedOperationException
,因为它们无法实现此方法。但是如果你经常抛出这个异常,那么你的对象模型可能有问题,因为这不是我不常想的常见事情。
另请注意,在Java中使用@Override
注释和多态编程是有帮助的(但不是必需的)。如果具有此批注的方法不覆盖父方法,实现抽象方法或(在Java 6中)实现接口方法,则会导致抛出编译错误。这有助于捕获您在方法签名中所犯的任何错误。例如:
public String tostring(){
return "foobar";
}
如果没有注释,程序将编译并成功运行。但这不是你的意图!你想覆盖toString(),但是你不小心拼错了名字!!
答案 6 :(得分:0)
我很惊讶没有人写过关于Late Binding的任何内容。 Java中的多态性=后期绑定。当我们最终知道它的类型时,被调用的方法将被附加到对象。在您的示例中:
if(animal instanceof Cat) {
((Cat)animal).climbToATree();
}
您正在Cat对象上调用climbToATree()
,以便编译器接受它。在运行时,无需检查调用对象的类型,因为climbToATree()
仅属于Cat
。因此,这些代码行中没有多态性。
关于与多态性相关的铸造,它不是。如果转换是合法的,则转换仅限制在两个对象中共享的字段。你可以这样做:
class A {
int getInt() {}
}
class B extends A {
int getInt() {}
}
// in main
A a = new B();
A b = (A)a;
b.getInt(); // This would still call class B's getInt();
强制转换本身没有添加任何值,getInt()在运行时绑定到运行时类型a
,即B类。
答案 7 :(得分:-2)
多态和OOP方法是将方法makeItClimbToATree放在Animal接口上:
public interface Animal{
public void talk();
public void makeItClimbToATree();
}
然后Animal的实现者将提供该方法的行为,除了Cat以外的所有方法都可以抛出异常。这是多态的,因为您通过单个方法操作Animal的不同实现。
使用instanceOf运算符的函数被认为是“坏”OOP,因为它需要了解所有实现类型以确定方法的行为。