Java-使用多态或有界类型参数


17

假设我有这个类层次结构...

public abstract class Animal {
    public abstract void eat();
    public abstract void talk();
}
class Dog extends Animal {
    @Override
    public void eat() {
    }

    @Override
    public void talk() {
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
    }

    @Override
    public void talk() {
    }
}

然后我有...

public static <T extends Animal> void addAnimal(T animal) {
    animal.eat();
    animal.talk();
}

public static void addAnimalPoly(Animal animal) {
    animal.eat();
    animal.talk();
}

使用有界类型参数或多态性有什么区别?

以及何时使用其中一个?


1
这两个定义不能从类型参数中获得太多收益。但是,尝试编写addAnimals(List<Animal>)并添加猫的清单!
Kilian Foth,2014年

7
好吧,例如,如果您上面的每个方法也将返回某些内容,则使用泛型的方法可能会返回T,而另一个只能返回Animal。因此,对于使用这些方法的人,在第一种情况下,他会完全得到自己想要的东西:Dod dog = addAnimal(new Dog()); 而使用第二种方法时,他将被迫投下一只狗:Dog d =(Dog)addAnimalPoly(new Dog());
Shivan Dragon

我使用的大多数有界类型都是Java较差的泛型实现的墙纸(例如,List <T>与单独的T具有不同的方差规则)。在这种情况下,没有任何优势,它实际上是表达同一概念的两种方式,尽管正如@ShivanDragon所说,这的确意味着您在编译时使用了T而不是Animal。您只能在内部将其视为动物,但可以在外部将其作为T提供。
Phoshi 2014年

Answers:


14

这两个示例是等效的,并且实际上将编译为相同的字节码。

有两种方法可以将有界泛型类型添加到方法中,如您的第一个示例所示。

将type参数传递给另一个类型

这两个方法签名最终在字节码中相同,但是编译器强制执行类型安全性:

public static <T extends Animal> void addAnimals(Collection<T> animals)

public static void addAnimals(Collection<Animal> animals)

在第一种情况下,仅允许的Collection(或子类型)Animal。在第二种情况下,允许Collection具有泛型或子类型的(或子类型)Animal

例如,第一种方法允许以下内容,但第二种方法则不允许:

List<Cat> cats = new ArrayList<Cat>();
cats.add(new Cat());
addAnimals(cats);

原因是第二个只允许收集动物,而第一个只允许收集可分配给动物的任何对象(即亚型)。请注意,如果此列表是碰巧包含一只猫的动物列表,则任何一种方法都可以接受它:问题是集合的通用规范,而不是它实际包含的内容。

返回对象

另一个重要时刻是返回对象。让我们假设存在以下方法:

public static <T extends Animal> T feed(T animal) {
  animal.eat();
  return animal;
}

您将可以执行以下操作:

Cat c1 = new Cat();
Cat c2 = feed(c1);

尽管这是一个人为的示例,但在某些情况下它是有道理的。如果没有泛型,该方法将必须返回,Animal并且您需要添加类型转换以使其起作用(无论如何,这都是编译器在幕后添加到字节码中的内容)。


在第一种情况下,只允许Animal的Collection(或子类型)。在第二种情况下,允许使用具有Animal的通用类型或子类型的Collection(或子类型)。 ”-您是否想检查你的逻辑在那里?
基金莫妮卡的诉讼

3

使用泛型而不是向下转换。“向下转换”是不好的,从更一般的类型变为更具体的类型:

Animal a = hunter.captureOne();
Cat c = (Cat)a;  // ACK!!!!!! What if it's a Dog? ClassCastException!

...您相信那a是一只猫,但是编译器无法保证。它可能在运行时变成了狗。

这是使用泛型的地方:

public class <A> Hunter() {
    public A captureOne() { ... }
}

现在,您可以指定想要一个猫猎人:

Hunter<Cat> hunterC = new Hunter<Cat>();
Cat c = hunterC.captureOne();

Hunter<Dog> hunterD = new Hunter<Dog>();
Dog d = hunterD.captureOne();

现在,编译器可以保证 hunterC仅捕获猫,而hunterD仅捕获狗。

因此,如果您只想将特定类作为基类处理,则只需使用常规多态即可。向上投射是一件好事。但是,如果遇到需要将特定类作为自己的类型进行处理的情况,通常使用泛型。

或者,实际上,如果您发现必须向下转换,则使用泛型。

编辑:更一般的情况是当您要推迟决定处理哪种类型的决定时。因此,类型和值一样成为参数。

假设我想让我的Zoo班处理猫或海绵。我没有普通的超级班。但我仍然可以使用:

public class <T> Zoo() { ... }

Zoo<Sponge> spongeZoo = ...
Zoo<Cat> catZoo = ...

您锁定该锁定的程度取决于您要执行的操作;)


2

这个问题有些陈旧,但是关于何时使用多态与有界类型参数的关系似乎已经被忽略了。这个因素可能与问题中给出的示例相切,但我觉得与更笼统的“何时使用多态性与有界类型参数?”有关。

TL; DR

如果您发现您根据更好的判断将代码从子类移动到基类,由于无法以多态方式访问代码,则有界类型参数可能是一种潜在的解决方案。

完整答案

有界的类型参数可以为继承的成员变量公开具体的,非继承的子类方法。多态不能

通过扩展示例来详细说明:

public abstract class AnimalOwner<T extends Animal> {
   protected T pet;
   public abstract void rewardPet();
}

// Modify the dog class
class Dog extends Animal {
   // ...
   // This method is not inherited from anywhere!
   public void scratchBelly() {
      System.out.println("Belly: Scratched");
   }
}

class DogOwner extends AnimalOwner<Dog> {
   DogOwner(Dog dog) {
     this.pet = dog;
   }

   @Override
   public void rewardPet()
   {
      // ---- Note this call ----
      pet.scratchBelly();
   }
}

如果将抽象类AnimalOwner定义为具有a protected Animal pet;并选择多态,则编译器将pet.scratchBelly();在行上出错,告诉您该方法对于Animal是未定义的。


1

在您的示例中,您不(也不应该)使用有界类型。仅在必须时才使用有界类型参数,因为它们更容易理解。

在某些情况下,您将使用有界类型参数:

  • 集合参数

    class Zoo {
    
      private List<Animal> animals;
    
      public void add(Collection<? extends Animal> newAnimals) {
        animals.addAll(newAnimals);
      }
    }

    那你可以打电话

    List<Dog> dogs = ...
    zoo.add(dogs);

    zoo.add(dogs)如果没有<? extends Animal>,将无法编译,因为泛型不是协变的。

  • 子类化

    abstract class Warrior<T extends Weapon> {
    
      public abstract T getWeapon();
    }

    限制子类可以提供的类型。

您还可以使用多个边界<T extends A1 & A2 & A3>来确保类型是列表中所有类型的子类型。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.