2

尝试重构一些代码时,我偶然发现了一个可以通过以下代码演示的问题:

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) {

    }

}

为什么找不到 in 的具体类型,other才能catAttack(other)调用最具体的方法呢?相反,必须进行此检查:

if (other instanceof Cat)
 catAttack((Cat) other);
if (other instanceof Dog)
 catAttack((Dog) other);

编辑:这个问题可能有点不清楚,这是我作为对答案的评论发布的澄清:

猫可以攻击一只狗或另一只猫,它们都是动物,我很好奇为什么将另一只动物扔给狗或猫的负担在我身上,为什么 java 不尝试看看它是否是猫或狗,因为我有两种方法可以匹配猫或狗,然后如果它是大象则抛出错误?

4

5 回答 5

1

猫可以攻击一只狗或另一只猫,它们都是动物,我很好奇为什么将另一只动物扔给狗或猫的负担在我身上,为什么 java 不尝试看看它是否是猫或狗,因为我有两种方法可以匹配猫或狗,然后如果它是大象则抛出错误?

不幸的是,您的建议与当前的 java 编译器实现的方法匹配系统不一致。

编译器必须执行所谓的静态类型检查,它基本上包括静态检查(即在程序运行之前)代码是否是类型安全的,在后一种情况下会导致运行时出错 -时间。

例如,考虑:

float b = 4.6f;
int a = 5 + b;

这里编译器知道 b 是 float 类型,知道 operator + 有一个 (int + float = float) 的条目,并且知道 a 是 integer 类型。因此,它推断该表达式具有浮点类型,并且必须转换为整数才能分配给 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 类型,因此真正的类可以是任何扩展 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 }
}

这可能离完美的解决方案还很遥远,但只是为了让您了解可以做什么和不能做什么。

于 2013-08-07T04:34:24.277 回答
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 。除了 Cat 和 Dog 。让我们说 Rabbit。所以在编译时,另一个实例可以是扩展 Animal 的东西。它也可以是兔子。由于编译器不够聪明或不够有帮助,无法找出扩展动物的每个类并检查传递给 catAttack 的其他实例的类型,因此会显示编译时错误。如果你有方法

private void catAttack(Animal other) {

    }

然后它将被编译

于 2013-08-07T04:27:01.620 回答
0

简单地说。因为您的 Cat 类中没有以下方法。

private void catAttack(Animal other) {
    }

编辑: 请记住,您可以直接将超类引用分配给子类。这实际上是您正在做的事情。假设如果您像下面这样

Animal animal=new Cat();

然后你打电话animal.Attack(animal)你可以做的 CatAttack 方法

if(animal instanceOf Cat)
{
catAttack((Cat)animal)
}

因为动物引用包含 cat 的对象,所以您可以显式地进行强制转换。

于 2013-08-07T03:59:48.317 回答
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 */
    }
}

如果你仔细想想,这种方法是有道理的。ACat在这种情况下,有其自身的攻击性质。您可以执行以下操作:

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,那么它就是一个 Animal(所以它遵循 Animal 接口)。如果我使用某种框架(例如 Spring),我可以动态注入基于一些外部配置文件运行的内容,以创建许多可能的场景,而无需复制和粘贴或重新编写代码。

希望这会有所帮助。

谢谢

于 2013-08-07T04:24:59.453 回答
0

这是一个双重调度的案例;另请参阅此SO question和此代码气味帖子

当我们需要取决于两个对象类型的行为时,我们使用双重调度,在这种情况下,你的两个动物。当您很少获得新课程时,它最有用。

有不同的实现方式,但这里有一种。

在 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+")");
}

所以你明白为什么它被称为双重调度。此代码可以大大改进,您当然应该考虑影响和替代方案。

于 2013-08-07T06:11:00.290 回答