Java:多态只对具有相同签名的方法有用吗?

时间:2011-10-27 03:15:53

标签: java parameters polymorphism factory

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

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中实现这种事情的好方法吗?

9 个答案:

答案 0 :(得分:6)

您正在混淆方法覆盖和方法重载。在您的示例中,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上发出警告,因为关键字表示可能出现多态抽象失败。

答案 1 :(得分:5)

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

答案 2 :(得分:2)

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

但是,考虑到当你打电话说话时,你现在必须知道要传递哪些参数,所以这一点有点没有实际意义。

答案 3 :(得分:1)

当您将多态方法称为:

a.speak("Gerorge");

您不需要知道实例化了哪种类型的动物,因为这是多态性的目标。此外,因为您有用户的句子:

super.speak(name);

Cat and Dog都会拥有Animal的行为加上自己的行为。

答案 4 :(得分:0)

你可以做到

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

但是,我建议将生命变成Cat的实例变量并让你的工厂传递一个默认值(或让构造函数有一个默认参数)。

答案 5 :(得分:0)

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

答案 6 :(得分:0)

如果你想这样打电话,你可以使用反射来获得课程:

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

当然,这可能不是最好的设计,应谨慎使用反射。

答案 7 :(得分:0)

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

当子类具有未由接口定义的行为时,您在Java中没有很多不冗长或有点不稳定的选项。

你可以有一个speak(),它接受一个标记接口并将arg构造委托给工厂。您可以传递参数图。你可以使用varargs。

最终,无论使用何种语言,您都需要知道要传递给方法的内容。

答案 8 :(得分:0)

如果在调用speak方法之前必须知道对象的类型,我同意有关如何真正打破多态的评论。如果你绝对必须可以访问这两种说话方法,这里有一种方法可以实现它。

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放入子类中(或者您可能希望使用已检查的异常)。 我实际上并没有提倡其中任何一种,但我认为这是实现您所要求的最接近的方式,而且我实际上已经看到使用类似这样的系统。