在 JAVA 中有一个抽象方法的重载方法

Having overload methods from one abstract method in JAVA

给定一个摘要class

public abstract class AbstractStomach {
    public abstract void consume(Food food);
}

我想要一些具有不同重载方法的具体 classes:

public class FruitStomach extends AbstractStomach {
    public static class Apple extends Food {}
    public static class Orange extends Food {}
    @Override
    public void consume(Apple food) {...}
    @Override
    public void consume(Orange food) {...}
}

public class MeatStomach extends AbstractStomach {
    public static class Beef extends Food {}
    @Override
    public void consume(Beef food) {...}
}

上面的方法不起作用,因为它迫使我在每个具体 class.

中也实现通用 consume(Food) 抽象方法

但我不希望具体的 classes 实现 consume(Food) 因为它们的重载变体已经解决了这个问题,实际上,每个具体的 class 应该使用不同的和特定的只有食物,没有任何食物,所以

AbstractStomach stomach = new FruitStomach();
stomach.consume(new Beef());

应该会报错

如果我删除了抽象中的抽象方法 class 那么让他们扩展抽象 class 就没有意义了,因为它没有为他们提供实现什么的指导。

你如何修正上面的设计,让每个具体的 classes 都可以有自己的重载方法,但他们知道有一个抽象方法 consume() 可以实现,但不需要实现 consume(Food).

编辑


我可以在不使用重载的情况下解决上述问题,例如

public class FruitStomach extends AbstractStomach {
    public static class Apple extends Food {}
    public static class Orange extends Food {}
    @Override
    public void consume(Food food) {
        if (food instanceof Apple) {
           ...
        } else if (food instanceof Orange) {
           ...
        } else {
           // throws an error
        }
    }
}

但这不是我想要的。我希望看到一个使用方法重载的解决方案。

要@Override 方法,您需要具有完全相同的 return 类型或子类型、名称和参数,这样您就无法在 "Orange orange" 中更改 "Food food"。

我认为你可以做的是在你的方法内部以具体的 classes(或者你使用 instanceof 所做的那样)强制转换你需要处理的对象。或者你可以创建一个对象更好:

Food orange = new Orange();
Food apple = new Apple(); 

所以第一次在 Orange class 中使用方法覆盖,第二次在 Apple class 中使用一个方法。

如果不是您想要的解决方案,请发表评论,以便我尝试其他方法。

The above doesn't work as it is forcing me to implement also the generic consume(Food)

这是正确的行为。根据摘要合同 class AbstractStomach,您同意,

Anything which extends AbstractStomach will have a method consume(Food) (any food) and then in subclass you are forcing it to have only two types of food, thus bypassing the contract.

抽象 class 中的抽象方法比 "providing guidance what to implement" 多。摘要 classes 允许您拥有可以是摘要 class 的 any subclass 的对象。也就是说,你可以有一个像

这样的方法
public void evaluateDigestion(AbstractStomach stomach) {
}

并且您可以使用任何类型的 Stomach 对象、FruitStomachMeatStomachDessertStomach 或其他对象来调用它。在evaluateDigestion内,方法可以调用stomach.consume(someKindOfFood)。由于这是为抽象class定义的consume(在编译时,evaluateDigestion不知道class是什么),所以它是consume使用 Food 参数。然后,在 运行 时,对 consume 的调用将 "dispatch" 到为具体 class 定义的 consume(Food food) 方法。这就是为什么必须为每个具体 class 定义带有 Food 参数的 consume。调度调用不会搜索具有 AppleOrange 等参数的重载方法; Java 不会那样做。 (所以你说 "has been taken care of by the overloading variants" 是不正确的;重载和覆盖之间有很大的区别,这可能会让新手感到困惑。)

所以你必须写

public void consume(Food food)

在每个混凝土中class。 非常笨拙的实现方式是

public void consume(Food food) {
    if (food instanceof Apple) {
        consume((Apple)food);  // calls the overloaded version because you've use a cast to tell Java how to view the object
    } else if (food instanceof Orange) {
        consume((Orange)food);
    } else {
        throw ... // some exception that occurs when you try to eat the wrong kind of food
    }
}

最好在 Food 中定义一个抽象方法,假设 Food 是抽象的:

public abstract class Food {
    public void abstract consumedBy(AbstractStomach stomach);
}

AppleOrange等class中定义具体版本,然后写consume

public static void consume(Food food) {
    food.consumedBy(this);
}

但是,这不会阻止尝试让 FruitStomach 吃牛肉。您可能必须使用 instanceof 来确保食物兼容。 (一种可能性:定义一个扩展 Food 的 class Fruit,并使 AppleOrange 成为 Fruit 的子class ; 然后你可以说 food instanceof Fruit 来验证它是正确的食物。不过,可能有比这更好的设计模式。我想不出一个。)

您面临的问题是您的抽象方法签名需要一个名为 Food 的超级 class 传递到 consume-method。此抽象 class 的每个实现(及其抽象方法)都必须覆盖相同的签名。

例如

   public FruitStomach extends AbstractStomach {
    public static class Apple extends Food {}
    public static class Orange extends Food {}
    @Override
    public void consume(Food food) {
      if (food instanceof Orange) {
        Orange orange = (Orange) food;
        // do smth. with orange     
      } else{
        // so smth with food
      }
    }

   public MeatStomach extends AbstractStomach {
      public static class Beef extends Food {}
      @Override
      public void consume(Food food) {
        Beef beef = (Beef) food;
        // do smth. with beef...
      }
    }

可以工作。虽然这不是很干净的代码,如果您的代码正确连接在一起,您也不会得到 compile-time 验证。 但是正如您所说,如果您对错误的输入食物调用了错误的 Stomach.consume() 方法,您可能会引发 ClassCastException,您可以处理它。

您可以使用泛型来(部分)解决您的问题:

AbstractStomach 定义了一个类型参数 T,它声明了它消耗的 Food 类型:

public abstract class AbstractStomach<T extends Food> {
    public abstract void consume(T food);
}

然后可以声明一个食物类型Fruit和对应的FruitStomach:

public class Fruit extends Food {}
public class FruitStomach extends AbstractStomach<Fruit> {
    public static class Apple extends Fruit {}
    public static class Orange extends Fruit {}
    @Override
    public void consume(Fruit food) {}
}

和类似的类 MeatMeatStomach

public class Meat extends Food {}
public class MeatStomach extends AbstractStomach<Meat> {
    public static class Beef extends Meat {}
    @Override
    public void consume(Meat food) {}
}