为什么会有人投射而不是返回子类型

时间:2016-01-18 01:17:58

标签: java inheritance subtype

当覆盖一个继承的方法时,有没有理由不将该类型声明为其原始类型的子类型? (动物而不是兔子见下文)

这是一个兔子的例子。

public class Junk2 {

    public static abstract class Animal {
        public abstract String makeSound();
    }

    public static class Bunny extends Animal {

        @Override
        public String makeSound() {
            return "hop hop";
        }

    }

    public static abstract class AnimalHome {

        private final Animal animal;

        public AnimalHome(Animal animal) {
            this.animal = animal;
        }

        public Animal getAnimal() {
            return animal;
        }

    }

    /**
     * I am a bad bunny-hole, you don't know I am giving you a bunny
     *
     */
    public static class BadBunnyHole extends AnimalHome {

        public BadBunnyHole(Animal animal) {
            super(animal);
        }
    }

    /**
     * I am a good bunny-hole, I'm still an AnimalHome because
     * Bunny is a sub-type of Animal
     *
     */
    public static class GoodBunnyHole extends AnimalHome {

        private final Bunny bunny;

        public GoodBunnyHole(Bunny bunny) {
            super(bunny);
            this.bunny = bunny;
        }

        public Bunny getAnimal() {
            return bunny;
        }

    }

    public static void main(String[] args) {
        Bunny bunny = new Bunny();
        GoodBunnyHole goodHole = new GoodBunnyHole(bunny);
        BadBunnyHole badHole = new BadBunnyHole(bunny);

        Bunny bunnyBack1 = goodHole.getAnimal();
        Bunny bunnyBack2 = (Bunny)badHole.getAnimal(); // look now I need to cast, I'm a crap BunnyHole

        System.out.println(bunnyBack1.makeSound());
        System.out.println(bunnyBack2.makeSound());
        System.out.println(bunnyBack1 == bunnyBack2);
    }

}

我们可以看到,由于坏兔子洞需要施放以将动物变回兔子。我认为这真的很糟糕,但我现在看到了这一点,并想知道是否有这样的理由。这对我来说很有意义,因为将makseSound的返回类型声明为Object而不是String。

可能最好的兔子洞就是这样的

public static abstract class AnimalHome<T extends Animal> {

        private final T animal;

        public AnimalHome(T animal) {
            this.animal = animal;
        }

        public T getAnimal() {
            return animal;
        }

    }

    public static class BestBunnyHole extends AnimalHome<Bunny> {

        public BestBunnyHole(Bunny animal) {
            super(animal);
        }
    }

但无论如何,我现在无法在我的代码库中这样做。但我的问题仍然是为什么BadBunnyHole?我甚至看到过这个可怕的事情:

public static class BadBunnyHole extends AnimalHome {

        public BadBunnyHole(Animal animal) {
            super(animal);
        }

        public Bunny getBunny() {
            return (Bunny) getAnimal();
        }

}

对我来说,像这样的演员闻起来像是一个OO设计缺陷,但是Java有什么理由这样做吗?有没有时间你不能在不破坏界面的情况下将返回类型声明为子类型?或者如果一个人正在使用反射库,可能还有一些不明原因?

2 个答案:

答案 0 :(得分:1)

在这种情况下使用泛型将更可取,但真正的原因是这会破坏并且你要求演员是由于父类的事实对它应该返回的内容知之甚少。

getAnimal()定义的唯一位置是父级,唯一可以安全返回的类型是Animal。接下来,您被迫施放的原因是由于Animal不能保证为Bunny(尽管由于继承而反转为真)。

你通常这样做的原因是你被迫。代码以不灵活的方式编写,使得类型无法推断。这里唯一的另一件事是,如果你正在进行演员表,你必须确保你正确投射,或者ClassCastException可以发生。

举个例子:

Bunny bunnyBack2 = (Bunny) new Object();

这是有效的,但会导致如上所述的CCE。

同样,在这里使用泛型类型会更安全,因为您可以更多地依靠编译器来排序类型和强制转换,而不是开发人员必须进行任何类型的转换。

答案 1 :(得分:1)

  

覆盖继承的方法时,有没有理由不这样做   声明类型是其原始类型的子类型吗?

如果覆盖超类方法的方法总是返回超类中声明的返回类型的子类型,那么没有理由不将子类型声明为子类中的返回类型

  

我们可以看到,对于坏的兔子洞,人们需要施展转身   动物回到兔子。我觉得这很糟糕但是我看到了   目前很多,并想知道是否有理由这样做。

我认为很多人只是不知道在覆盖方法时可以使返回类型更窄。

  

我甚至看到过这个可怕的事情:

public static class BadBunnyHole extends AnimalHome {

    public BadBunnyHole(Animal animal) {
        super(animal);
    }

    public Bunny getBunny() {
        return (Bunny) getAnimal();
    }

这取决于如何重写方法和字段。在这种情况下,如果您打算拥有AnimalHome的子类,那么在超类中存储动物可能不是一个好主意。使用在每个子类中实现的抽象方法abstract创建超类getAnimal()并让每个子类具有正确类型的动物的存储(字段)更好。与您的GoodBunnyHole示例一样,只有AnimalHome是这样的:

public static abstract class AnimalHome {
    public abstract Animal getAnimal();
}
  

对我来说,像这样的铸造闻起来像OO设计缺陷,但是在那里   Java中的任何理由都这样做?有没有时间你不能宣布   返回类型一个子类型而不破坏接口?或者可能   如果一个人正在使用反射库,那么有些模糊的原因?

自从Java 5以来“只有”这样做是可能的。从我的非代表性经验来看,很多人仍然不知道这是可能的。

另见Can overridden methods differ in return type?