【问题标题】:Having overload methods from one abstract method in JAVA从 JAVA 中的一种抽象方法中获得重载方法
【发布时间】:2017-06-17 18:35:15
【问题描述】:

给定一个抽象类

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

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

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

以上方法不起作用,因为它迫使我在每个具体类中也实现通用 consume(Food) 抽象方法。

但我不希望具体类实现consume(Food),因为它们的重载变体已经解决了这一点,实际上,每个具体类应该只消耗不同和特定的食物,而不是任何食物,这样

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

应该报错。

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

您如何修复上述设计,以便每个具体类都可以有其重载方法,但他们知道有一个抽象方法 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
        }
    }
}

但这不是我想要的。我想看看使用方法重载的解决方案。

【问题讨论】:

  • 你在声明它们时忘记了class这个词。
  • 在 Java 中,重载方法是在编译时而不是运行时选择的,所以调用者有一些 Food 但不知道它是什么类型(例如,它已被传递到它)永远无法调用consume 的正确重载。 Java 类型系统不适合您正在尝试做的事情。
  • 在大约 2 分钟的时间内获得 5 个答案。
  • “我希望看到一个使用方法重载的解决方案”...对不起,你不会看到,不是在 Java 中。在运行时决定执行哪个方法称为 polymorphism,正如this site 明确指出的那样,“多态适用于覆盖,而不适用于重载”——这与 David 所说的重载方法相同在编译时选择,而不是在运行时选择。
  • 为什么需要通过继承实现不同类型的胃?泛型还不足以支持这种情况吗?

标签: java inheritance abstract-class abstract


【解决方案1】:

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

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

【讨论】:

  • 是的,谢谢,但这只是将问题从consume(Food) 推到consume(Fruit)。其中,您仍然需要检查 Fruit 的实例是苹果还是橙子。
  • @user1589188 仍然需要检查 Fruit 的实例是苹果还是橙子,但在 consume(Fruit) 内它始终是 Fruit 而不是某种 Meat。正如我所写:它部分解决了您的问题。
【解决方案2】:

要@Override 方法,您需要具有相同的返回值或子类型、名称和参数,这样您就无法更改“Orange orange”中的“Food food”。

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

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

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

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

【讨论】:

  • 你的第一句话不正确。从 Java 5 开始允许协变返回。重载方法可以返回其重载方法的返回类型的子类型。
  • 但我认为使用@Override 注释你需要使用相同的实现接口或扩展类的方法。我会找到一些关于论证它的文档,谢谢你的建议。
  • 如果你有一个返回T的方法,你可以用一个返回T2的方法覆盖它,如果T2T的一个子类(从Java 5开始)。这是关于使用相同精确方法的规则的唯一例外。参数类型仍然必须相同(不允许子类)。
  • 感谢 ajb,我在几分钟前看过文档并编辑了帖子。
【解决方案3】:

您面临的问题是,您的抽象方法签名要求将一个名为 Food 的超类传递给消费方法。这个抽象类的每个实现(以及它的抽象方法)都必须覆盖相同的签名。

例如

   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...
      }
    }

可以工作。尽管这不是很干净的代码,并且如果您的代码正确连接在一起,您将没有编译时验证。 但正如你所说,如果你在错误的输入食物上调用了错误的 Stomach.consume() 方法,你可能会引发 ClassCastException,你可以处理它。

【讨论】:

  • 是的,除了使用 instanceof 之外找不到其他选择。不是很优雅,但我们能做的就这么多。
【解决方案4】:

抽象类中的抽象方法不仅仅是“提供指导要实现什么”。抽象类允许您拥有可以是抽象类的任何子类的对象。也就是说,你可以有一个类似的方法

public void evaluateDigestion(AbstractStomach stomach) {
}

您可以使用任何类型的 Stomach 对象、FruitStomachMeatStomachDessertStomach 或其他方式调用它。在evaluateDigestion 内部,该方法可以调用stomach.consume(someKindOfFood)。因为这是为抽象类定义的consume(在编译时,evaluateDigestion 不知道该类是什么),所以consume 使用了Food 参数。然后,在运行时,对consume 的调用将“分派”到为具体类定义的consume(Food food) 方法。这就是为什么必须为每个具体类定义带有Food 参数的consume 的原因。调度调用不搜索带AppleOrange等参数的重载方法; Java 不这样做。 (所以你说它“已经被重载变体照顾”是不正确的;重载和覆盖之间有很大的区别,这可能会让新手感到困惑。)

所以你必须写

public void consume(Food food)

在每个具体类中。 非常笨重的实现方式是

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等类中定义具体版本,然后写consume之类的

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

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

【讨论】:

  • 喜欢你在这个话题上的知识。选择你的答案。
【解决方案5】:
The above doesn't work as it is forcing me to implement also the generic consume(Food)

这是正确的行为。通过抽象类AbstractStomach中的约定,您同意,

任何扩展AbstractStomach 的东西都会有一个方法 consume(Food) (any food) 然后在子类中你是 强迫它只有两种类型的食物,从而绕过合同。

【讨论】:

  • 我希望看到一份合同上写着“至少一种食物”而不是“任何食物”:)
猜你喜欢
  • 2021-12-16
  • 1970-01-01
  • 2013-01-02
  • 2011-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-30
  • 2021-03-13
相关资源
最近更新 更多