尝试重构一些代码,我偶然发现了一个可以通过以下代码演示的问题:
public static abstract class Animal {
public abstract void attack(Animal other);
}
public static class Cat extends Animal {
@Override
public void attack(Animal other) {
catAttack(other); <-------- Problem here
}
private void catAttack(Cat other) {
// Maybe a meow showdown wins the fight, no need to get physical
}
private void catAttack(Dog other) {
// Dogs are dangerous, run!
}
}
public static class Dog extends Animal {
@Override
public void attack(Animal other) {
}
}
为什么无法找到other
中catAttack(other)
的特定类型,以便可以调用最具体的方法?相反,必须完成此项检查:
if (other instanceof Cat)
catAttack((Cat) other);
if (other instanceof Dog)
catAttack((Dog) other);
编辑:这个问题可能有点不清楚,以下是我发布的评论答案:
猫可以攻击一只狗或其他两只碰巧都是动物的猫,我很好奇为什么把另一只动物投入到狗或猫身上的负担都在我身上,为什么不试试java,看看是否它是猫还是狗,因为我有两种方法可以匹配猫或狗,然后如果是大象则会抛出错误?
答案 0 :(得分:1)
我认为这是一个很好的问题。有些人可能会觉得导致Animal是一个无法实例化的抽象类。因此编译器应该支持catAttack(other)
,但问题是编译器只知道两种方法
private void catAttack(Cat other) {
// Maybe a meow showdown wins the fight, no need to get physical
}
private void catAttack(Dog other) {
// Dogs are dangerous, run!
}
但是也有可能有其他类扩展动物。除了猫和狗。让我们说兔子。所以在编译时,另一个实例可以扩展为Animal。它也可以是兔子。由于编译器不够智能或没有足够的帮助来找出扩展动物的每个类并检查传递给catAttack的其他实例的类型,因此它显示编译时错误。如果你有方法
private void catAttack(Animal other) {
}
然后它将被编译
答案 1 :(得分:1)
猫可以攻击一只狗或其他两只碰巧都是动物的猫,我很好奇为什么把另一只动物投入到狗或猫身上的负担都在我身上,为什么不试试java,看看是否它是猫还是狗,因为我有两种方法可以匹配猫或狗,然后如果是大象则会抛出错误?
不幸的是,你所建议的并不符合当前java编译器实现的方法匹配系统。
编译器必须执行所谓的静态类型检查,它基本上包括静态检查(即在程序运行之前)代码是否是类型安全的,这将在后一种情况在运行时导致错误。
举个例子,考虑一下:
float b = 4.6f;
int a = 5 + b;
这里编译器知道b的类型为float,知道operator +有一个(int + float = float)的条目,并且知道a的类型是整数。因此,它推断表达式具有float类型,并且必须强制转换为整数以分配给a。在java中不允许进行此转换,因此它会输出错误以防止精度损失。
对于对象,它基本上是相同的,知道层次结构中更深层次的类可以“转换”为更浅的父级,但不是相反。
因此,在您的情况下:
public void attack(Animal other) {
catAttack(other); <-------- Problem here
}
private void catAttack(Cat other) {
// Maybe a meow showdown wins the fight, no need to get physical
}
private void catAttack(Dog other) {
// Dogs are dangerous, run!
}
如果不运行整个程序,java编译器无法在方法other
中推断变量attack(Animal other)
的真实类。
它只能知道“指针”是Animal的类型,所以真正的类可以是扩展动物的任何东西,它必须对catAttack(other)
的调用进行类型检查(并静态地解析方法调用),只知道它
由于没有catAttack(Animal)
方法,java编译器无法保证代码的类型安全,并输出错误。
你能做的是:
public static abstract class Animal {
public abstract void attack(Animal other);
public abstract void beAttackedByCat(Animal cat);
public abstract void beAttackedByDog(Animal dog);
}
public static class Cat extends Animal {
@Override
public void attack(Animal other) {
other.beAttackedByCat(this);
}
public void beAttackedByCat(Animal cat){ // cat > cat }
public void beAttackedByDog(Animal dog){ // dog > cat }
}
public static class Dog extends Animal {
@Override
public void attack(Animal other) {
other.beAttackedByDog(this);
}
public void beAttackedByCat(Animal cat){ // cat > dog }
public void beAttackedByDog(Animal dog){ // dog > dog }
}
距离完美的解决方案可能只有几英里远,但只是为了让您了解自己能做什么以及不能做什么。
答案 2 :(得分:0)
简单地说。因为你的Cat Class中没有以下方法。
private void catAttack(Animal other) {
}
修改强> 请记住,您可以直接为子类分配一个superClass引用。这实际上就是您正在做的事情。假设如果您喜欢下面的内容
Animal animal=new Cat();
然后你打电话给animal.Attack(animal)
调用你可以做的CatAttack方法
if(animal instanceOf Cat)
{
catAttack((Cat)animal)
}
因为动物参考包含cat的对象,所以可以明确地进行转换。
答案 3 :(得分:0)
我非常肯定在java中你不能使一个类静态,除非它是一个内部类;但除了问题之外,这也是如此。
您必须进行投射,因为您无法将方法的正确原型private void catAttack(Cat other)
或private void catAttack(Dog other)
与catAttach(Animal other)
匹配。
为了解决这个问题,你可以编程到超类或接口。
通常以这种方式完成:
public interface Animal {
public void attack(Animal other);
}
public class Cat implements Animal {
public void attack(Animal other) {
/* TODO: implement attack method */
}
}
public class Dog implements Animal {
public void attack(Animal other) {
/* TODO: implement attack method */
}
}
如果你考虑一下,这种方法是有道理的。在这种情况下,Cat
具有攻击性质。你可以这样做:
public class Cat implements Animal {
public void attack(Animal other) {
if (this.equals(other)) {
System.out.println("The Cat won't fight itself.");
} elsif (other instanceof Cat) {
System.out.println("Cats are friendly to one another, so the Cat forfeits the fight!");
} elsif (other instanceof Dog) {
System.out.println("Cats hate Dogs, so the Cat viciously attacks the Dog!");
} else {
System.out.println("The Cat seems to be unamused by the other animal, and walks away...");
}
}
你可以使它更复杂:
public class MountainLion extends Cat {
@Override
public void attack(Animal other) {
System.out.println("Mountain Lions do not like to be challenged and will strike down any predator with the fire inside their heart!");
}
}
但在您使用它们的类中,您只需遵循界面模式,如下所示:
public class AnimalKingdom {
public static void main(final String[] args) {
Animal cat = new Cat(); // could be read in from something like Spring using context.getBean("catObject", Animal.class);
Animal dog = new Dog(); // same as above...
Animal randomAnimalType = new MountainLion(); // same as above...
cat.attack(cat);
cat.attack(dog);
cat.attack(randomAnimalType);
dog.attack(cat);
dog.attack(dog);
dog.attack(randomAnimalType);
randomAnimalType.attack(cat);
randomAnimalType.attack(dog);
randomAnimalType.attack(randomAnimalType); // this doesn't use super, doesn't check if the animal is the same instance...
}
}
关键是,如果我现在可以创建AnimalKingdom的行为而不必担心它是什么类型的动物,只是它是一个动物(因此它遵循Animal界面)。如果我使用某种框架(例如Spring),我可以动态地注入基于某些外部配置文件的运行,以创建许多可能的场景,而无需复制,粘贴或重写代码。
希望这有用。
由于
答案 4 :(得分:0)
这是double-dispatch的案例;另请参阅此SO question和此code smell post。
当我们需要依赖于两个对象类型的行为时,我们使用双重调度,在这种情况下,你的两只动物。当你很少得到新课时,这是最有用的。
有不同的实现方法,但这里有一个。
在Animal类中,我们有抽象方法(Animal也有一个name属性):
public abstract void attack(Animal other);
public abstract void attackedBy(Cat other);
public abstract void attackedBy(Dog other);
public abstract void attackedBy(Rat other);
在调用代码中我们可能有
Animal c1 = new Cat("C1");
Animal c2 = new Cat("C2");
Animal d1 = new Dog("D1");
Animal d2 = new Dog("D2");
Animal r1 = new Rat("R1");
Animal r2 = new Rat("R2");
c1.attack(c2);
c1.attack(d1);
c1.attack(r1);
d1.attack(c1);
d1.attack(d2);
d1.attack(r1);
r1.attack(c1);
r1.attack(d1);
r1.attack(r2);
在Cat和其他每个类中,我们都有代码,例如
public void attack(Animal other) {
System.out.println("Cat("+name+") attacks other");
other.attackedBy(this);
}
public void attackedBy(Cat other) {
System.out.println("Cat("+name+") attacked by Cat("+other.name+")");
}
public void attackedBy(Dog other) {
System.out.println("Cat("+name+") attacked by Dog("+other.name+")");
}
public void attackedBy(Rat other) {
System.out.println("Cat("+name+") attacked by Rat("+other.name+")");
}
诸如
之类的电话c1.attack(d1);
调用Cat.attack(Animal other)
,它将控制转移到另一个Animal对象(Dog):
public void attack(Animal other) {
System.out.println("Cat("+name+") attacks other");
other.attackedBy(this);
}
来电是Dog.attackedBy(Cat other)
:
public void attackedBy(Cat other) {
System.out.println("Dog("+name+") attacked by Cat("+other.name+")");
}
所以你明白为什么它叫做Double Dispatch。这个代码可以大大改进,你当然应该考虑其含义和替代方案。