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

Какая разница при использовании параметров ограниченного типа или полиморфизма?

И когда использовать один или другой?

HugoMelo
источник
1
Эти два определения мало выигрывают от параметров типа. Но попробуйте написать addAnimals(List<Animal>)и добавить список кошек!
Килиан Фот
7
Ну, например, если каждый из вышеперечисленных методов будет возвращать что-то, а тот, который использует дженерики, может вернуть T, а другой - только Animal. Так что для человека, использующего эти методы, в первом случае он получит именно то, что хотел: Dod dog = addAnimal (new Dog ()); в то время как во втором методе его заставили бы разыграть, чтобы получить собаку: Dog d = (Dog) addAnimalPoly (new Dog ());
Shivan Dragon
Большая часть моего использования ограниченных типов связана с плохой реализацией обобщений в Java (например, List <T> имеет правила дисперсии, отличные от T). В этом случае нет никаких преимуществ, это по существу два способа выражения той же концепции, хотя , как говорится , @ShivanDragon это действительно значит, что Т в compiletime вместо животного. Вы можете относиться к нему как к животному только внутри, но вы можете предложить его как букву T извне.
Фоши

Ответы:

14

Эти два примера эквивалентны и фактически компилируются в один и тот же байт-код.

Существует два способа добавления ограниченного универсального типа в метод, как в первом примере.

Передача параметра типа другому типу

Эти две сигнатуры метода в конечном итоге совпадают в байтовом коде, но компилятор обеспечивает безопасность типов:

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

public static void addAnimals(Collection<Animal> animals)

В первом случае допускается только Collection(или подтип) of 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. Во втором случае разрешена Коллекция (или подтип) с универсальным типом Animal или подтипом. » - Хотите дважды проверь свою логику там?
Фонд Моника иск
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 - только собак.

Так что просто используйте обычный полиморфизм, если вы просто хотите обрабатывать определенные классы как их базовый тип. Апкастинг это хорошая вещь. Но если вы попадаете в ситуацию, когда вам нужно обрабатывать определенные классы как их собственный тип, в общем, используйте дженерики.

Или, на самом деле, если вы обнаружите, что вам нужно унизить, используйте дженерики.

РЕДАКТИРОВАТЬ: более общий случай, когда вы хотите отложить решение о том, какие типы обрабатывать. Таким образом, типы становятся параметром, а также значениями.

Скажем, я хочу, чтобы мой класс в зоопарке работал с кошками или губками. У меня нет общего суперкласса. Но я все еще могу использовать:

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 был определен так, чтобы иметь protected Animal pet;и выбирать полиморфизм, компилятор выдаст ошибку в pet.scratchBelly();строке, сообщив вам, что этот метод не определен для Animal.

loaner9
источник
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>чтобы гарантировать, что тип является подтипом всех типов в списке.


источник