Java:多态性仅适用于具有相同签名的方法吗?

发布于 2024-12-12 09:49:18 字数 1860 浏览 0 评论 0原文

我见过的多态方法重写的唯一示例涉及不带参数的方法,或者至少具有相同的参数列表。考虑常见的动物/狗/猫的例子:

public abstract class Animal
{
    public abstract void makeSound();
}

public class Dog extends Animal
{
    public void makeSound()
    {
        System.out.println("woof");
    }
}

public class Cat extends Animal
{
    public void makeSound()
    {
        System.out.println("meow");
    }
}

public class ListenToAnimals
{
    public static void main(String[] args)
    {
        AnimalFactory factory = new AnimalFactory();
        Animal a = factory.getRandomAnimal(); // generate a dog or cat at random
        a.makeSound();
    }
}

在这种情况下,一切都很顺利。现在让我们添加另一个方法,该方法在抽象类中部分实现,同时在子类中获取更具体的行为:

public abstract class Animal
{   
    public abstract void makeSound();

    public void speak(String name)
    {
        System.out.println("My name is " + name);
    }
}

public class Dog extends Animal
{
    public void makeSound()
    {
        System.out.println("woof");
    }

    public void speak(String name)
    {
        super.speak(name);
        System.out.println("I'm a dog");
    }
}

public class Cat extends Animal
{
    public void makeSound()
    {
        System.out.println("meow");
    }

    public void speak(String name, int lives)
    {
        super.speak(name);
        System.out.println("I'm a cat and I have " + lives + " lives");
    }
}

public class ListenToAnimals
{
    public static void main(String[] args)
    {
        AnimalFactory factory = new AnimalFactory();
        Animal a = factory.getRandomAnimal(); // generate a dog or cat at random
        a.makeSound();
        // a.speak(NOW WHAT?
    }
}

在主方法的最后(注释)行中,我不知道要放在那里,因为我不知道我有什么类型的动物。我之前不必担心这个问题,因为 makeSound() 没有接受任何参数。但是speak()确实如此,并且参数取决于Animal的类型。

我读过一些语言,例如 Objective-C,允许可变参数列表,因此永远不会出现这样的问题。有谁知道在Java中实现这种事情的好方法吗?

The only examples of polymorphic method overriding I ever see involve methods that take no parameters, or at least have identical parameter lists. Consider the common Animal/Dog/Cat example:

public abstract class Animal
{
    public abstract void makeSound();
}

public class Dog extends Animal
{
    public void makeSound()
    {
        System.out.println("woof");
    }
}

public class Cat extends Animal
{
    public void makeSound()
    {
        System.out.println("meow");
    }
}

public class ListenToAnimals
{
    public static void main(String[] args)
    {
        AnimalFactory factory = new AnimalFactory();
        Animal a = factory.getRandomAnimal(); // generate a dog or cat at random
        a.makeSound();
    }
}

In this case, everything works out just fine. Now let's add another method that gets partially implemented in the abstract class while getting the more specific behavior in the subclasses:

public abstract class Animal
{   
    public abstract void makeSound();

    public void speak(String name)
    {
        System.out.println("My name is " + name);
    }
}

public class Dog extends Animal
{
    public void makeSound()
    {
        System.out.println("woof");
    }

    public void speak(String name)
    {
        super.speak(name);
        System.out.println("I'm a dog");
    }
}

public class Cat extends Animal
{
    public void makeSound()
    {
        System.out.println("meow");
    }

    public void speak(String name, int lives)
    {
        super.speak(name);
        System.out.println("I'm a cat and I have " + lives + " lives");
    }
}

public class ListenToAnimals
{
    public static void main(String[] args)
    {
        AnimalFactory factory = new AnimalFactory();
        Animal a = factory.getRandomAnimal(); // generate a dog or cat at random
        a.makeSound();
        // a.speak(NOW WHAT?
    }
}

In that last (commented) line of the main method, I don't know what to put there because I don't know what type of Animal I have. I didn't have to worry about this before because makeSound() didn't take any arguments. But speak() does, and the arguments depend on the type of Animal.

I've read that some languages, such as Objective-C, allow for variable argument lists, so an issue like this should never arise. Is anyone aware of a good way to implement this kind of thing in Java?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

可是我不能没有你 2024-12-19 09:49:18

您混淆了方法重写和方法重载。在您的示例中,Cat 类有两个方法:

public void speak(String name) // It gets this from its super class
public void speak(String name, int lives)

重载是一种定义具有相似功能但不同参数的方法的方法。如果您这样命名该方法,则没有什么区别:

public void speakWithLives(String name, int lives)

为了避免混淆,java 中的建议是在尝试重写方法时使用 @Override 注释。因此:

 // Compiles
@Override
public void speak(String name)

// Doesn't compile - no overriding occurs!
@Override
public void speak(String name, int lives)

编辑:其他答案提到了这一点,但我重复它是为了强调。添加新方法使得 Cat 类在所有情况下都不再能够表示为 Animal,从而消除了多态性的优势。要使用新方法,您需要将其向下转换为 Cat 类型:

Animal mightBeACat = ...
if(mightBeACat instanceof Cat) {
  Cat definitelyACat = (Cat) mightBeACat;
  definitelyACat.speak("Whiskers", 9);
} else {
  // Definitely not a cat!
  mightBeACat.speak("Fred");
}

我的 IDE 中的代码检查工具会在 instanceof 上发出警告,因为关键字表明可能多态抽象失败。

You are confusing method overriding and method overloading. In your example the Cat class has two methods:

public void speak(String name) // It gets this from its super class
public void speak(String name, int lives)

Overloading is a way to define methods with similar functions but different parameters. There would be no difference if you had named the method thusly:

public void speakWithLives(String name, int lives)

To avoid confusion the recommendation in java is to use the @Override annotation when you are attempting to override a method. Therefore:

 // Compiles
@Override
public void speak(String name)

// Doesn't compile - no overriding occurs!
@Override
public void speak(String name, int lives)

EDIT: Other answers mention this but I am repeating it for emphasis. Adding the new method made the Cat class no longer able to be represented as an Animal in all cases, thus removing the advantage of polymorphism. To make use of the new method you would need to downcast it to the Cat type:

Animal mightBeACat = ...
if(mightBeACat instanceof Cat) {
  Cat definitelyACat = (Cat) mightBeACat;
  definitelyACat.speak("Whiskers", 9);
} else {
  // Definitely not a cat!
  mightBeACat.speak("Fred");
}

The code inspection tool in my IDE puts a warning on the instanceof as the keyword indicates possible polymorphic abstraction failure.

糖果控 2024-12-19 09:49:18

您的示例 Cat 不再是多态的,因为您必须知道它是 Cat 才能传递该参数。即使Java允许,你会如何使用它?

Your example Cat isn't polymorphic anymore, since you have to know it's a Cat to pass that parameter. Even if Java allowed it, how would you use it?

海风掠过北极光 2024-12-19 09:49:18

据我所知java不允许你这样做。 talk(name,lives) 现在只是 Cat 的功能。有些语言确实允许这种灵活性。要强制 java 允许这样做,您可以将参数设置为对象数组或其他集合。

但是,考虑到当您调用 talk 时,您现在必须知道无论如何要传递哪些参数,因此这一点有点没有实际意义。

As far as I know java doesn't allow you to do that. speak(name, lives) is now just the Cat's function. Some languages do allow this type of flexibility. To force java to allow this, you can make the paramater an array of objects or some other collection.

However, consider that when you call speak, you now must know which parameters to pass in regardless, so the point is somewhat moot.

流星番茄 2024-12-19 09:49:18

当您将多态方法调用为:

a.speak("Gerorge");

您不需要知道实例化了哪种类型的 Animal,因为这是多态性的目标。另外,由于您使用了这句话:

super.speak(name);

猫和狗都会有动物的行为加上自己的行为。

When you call a polymorphic method as:

a.speak("Gerorge");

You don't need to know what type of Animal has instantiated because this is the objective of polymorphism. Also since you have user the sentence:

super.speak(name);

Both Cat an Dog will have the behavior of Animal plus the own behavior.

迷离° 2024-12-19 09:49:18

你可以做

public void speak(Map ... mappedData)
    {
        System.out.println("My name is " + mappedData.get("name")+ " and I have "+mappedData.get("lives");
    }

但是,我建议将生命作为 Cat 的实例变量,并让你的工厂传递默认值(或者让构造函数为其提供默认参数)。

You can do

public void speak(Map ... mappedData)
    {
        System.out.println("My name is " + mappedData.get("name")+ " and I have "+mappedData.get("lives");
    }

However, I would advise making lives an instance variable of Cat and have your factory pass a default value (or have the constructor have a default parameter for it).

魂牵梦绕锁你心扉 2024-12-19 09:49:18
In this case best way is to use a DTO,

public class SpeakDTO
{
     //use getters and setters when you actually implement this
     String name;
     int lives;
}

public class Dog extends Animal
{
    public void speak(SpeakDTO dto)
    {
        super.speak(dto.name);
        System.out.println("I'm a dog");
    }
}

public class Cat extends Animal
{
    public void speak(SpeakDTO dto)
    {
        super.speak(dto.name);
        System.out.println("I'm a cat and I have " + dto.lives + " lives");
    }
}

public class ListenToAnimals
{
    public static void main(String[] args)
    {
        AnimalFactory factory = new AnimalFactory();
        Animal a = factory.getRandomAnimal(); // generate a dog or cat at random
        a.makeSound();

        SpeakDTO dto = new SpeakDTO();
        dto.name = "big cat";
        dto.lives = 7;

        a.speak(dto);
    }
}
In this case best way is to use a DTO,

public class SpeakDTO
{
     //use getters and setters when you actually implement this
     String name;
     int lives;
}

public class Dog extends Animal
{
    public void speak(SpeakDTO dto)
    {
        super.speak(dto.name);
        System.out.println("I'm a dog");
    }
}

public class Cat extends Animal
{
    public void speak(SpeakDTO dto)
    {
        super.speak(dto.name);
        System.out.println("I'm a cat and I have " + dto.lives + " lives");
    }
}

public class ListenToAnimals
{
    public static void main(String[] args)
    {
        AnimalFactory factory = new AnimalFactory();
        Animal a = factory.getRandomAnimal(); // generate a dog or cat at random
        a.makeSound();

        SpeakDTO dto = new SpeakDTO();
        dto.name = "big cat";
        dto.lives = 7;

        a.speak(dto);
    }
}
触ぅ动初心 2024-12-19 09:49:18

如果你想进行这样的调用,你可以使用反射来获取类:

if (a.getclass() == Cat.class) {
// speak like a cat
} else if (a.getclass() == Dog.class) {
.
.
.

当然这可能不是最好的设计,反射应该小心使用。

If you want to make a call like that, you could use reflection to get the class:

if (a.getclass() == Cat.class) {
// speak like a cat
} else if (a.getclass() == Dog.class) {
.
.
.

Of course this might not be the best design, and reflection should be used with care.

在巴黎塔顶看东京樱花 2024-12-19 09:49:18

Java 也有可变参数列表,但我认为这不是“最佳”方法,至少不是在所有情况下都是如此。

当子类具有接口未定义的行为时,Java 中没有太多不冗长或有点奇怪的选项。

您可以有一个 talk (),它采用标记接口并将 arg 构造委托给工厂。您可以传递参数映射。您可以使用可变参数。

最终,您需要知道将什么传递给该方法,无论使用哪种语言。

Java also has variable argument lists, but I'd argue that's not the "best" way to do it, at least not in all circumstances.

When subclasses have behavior that isn't defined by the interface, you don't have many options in Java that aren't verbose or a bit wonky.

You could have a speak () that takes a marker interface and delegate arg construction to a factory. You could pass a parameter map. You could use varargs.

Ultimately, you need to know what to pass to the method, no matter what language.

海螺姑娘 2024-12-19 09:49:18

我同意关于如果您必须在调用 talk 方法之前必须知道对象类型的情况下您如何真正破坏多态性的评论。如果您绝对必须能够访问这两种发言方法,那么这里是您可以实现它的一种方法。

public class Animal {      
    public void speak(String name) {
        throw new UnsupportedOperationException("Speak without lives not implemented");
    }
    public void speak(String name, int lives) {
        throw new UnsupportedOperationException("Speak with lives not implemented");
    }
}

public class Dog extends Animal {
    public void speak(String name) {
        System.out.println("My name is " + name);
        System.out.println("I'm a dog");
    }
}

public class Cat extends Animal {
    public void speak(String name, int lives) {
        System.out.println("My name is " + name);
        System.out.println("I'm a cat and I have " + lives + " lives");
    }
}

或者,您可以将 UnsupportedOperationExceptions 放入子类中(或者您可能希望使用已检查的异常)。 我实际上并不提倡其中任何一个,但我认为这是实现您所要求的最接近的方法,而且我实际上见过使用类似方法的系统。

I agree with the comments about how you've really broken polymorphism if you must know the type of object before you can call the speak method. If you absolutely MUST have access to both speak methods, here is one way you could implement it.

public class Animal {      
    public void speak(String name) {
        throw new UnsupportedOperationException("Speak without lives not implemented");
    }
    public void speak(String name, int lives) {
        throw new UnsupportedOperationException("Speak with lives not implemented");
    }
}

public class Dog extends Animal {
    public void speak(String name) {
        System.out.println("My name is " + name);
        System.out.println("I'm a dog");
    }
}

public class Cat extends Animal {
    public void speak(String name, int lives) {
        System.out.println("My name is " + name);
        System.out.println("I'm a cat and I have " + lives + " lives");
    }
}

Alternately you can put the UnsupportedOperationExceptions into the child classes (or you might want to used a checked exception). I'm not actually advocating either of these, but I think this is the closest way to implement what you requested, and I have actually seen systems that used something like this.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文