Java - Gunakan parameter polimorfisme atau tipe terikat


17

Misalkan saya memiliki hierarki kelas ini ...

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

Dan kemudian saya ....

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

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

Apa perbedaannya ketika menggunakan parameter tipe terbatas atau polimorfisme?

Dan kapan harus menggunakan satu atau yang lain?


1
Kedua definisi ini tidak mendapatkan banyak keuntungan dari parameter tipe. Tetapi cobalah menulis addAnimals(List<Animal>)dan menambahkan Daftar Kucing!
Kilian Foth

7
Misalnya, jika masing-masing metode Anda di atas akan mengembalikan sesuatu juga, maka yang menggunakan obat generik dapat mengembalikan T, sedangkan yang lain hanya bisa mengembalikan Hewan. Jadi untuk orang yang menggunakan metode itu, dalam kasus pertama dia akan mendapatkan kembali persis apa yang dia inginkan: Dod dog = addAnimal (New Dog ()); sementara dengan metode ke-2, ia akan dipaksa untuk berperan untuk mendapatkan seekor anjing: Anjing d = (Anjing) addAnimalPoly (Anjing baru ());
Shivan Dragon

Mayoritas penggunaan tipe terikat saya adalah wallpapering atas implementasi generik Java yang buruk (Daftar <T> memiliki aturan varians yang berbeda untuk T saja, misalnya). Dalam hal ini tidak ada keuntungan, itu dasarnya dua cara untuk mengekspresikan konsep yang sama, meskipun sebagai @ShivanDragon menyatakan itu tidak berarti Anda memiliki T di compiletime bukan sebuah Animal. Anda hanya dapat memperlakukannya seperti binatang secara internal, tetapi Anda dapat menawarkannya sebagai T secara eksternal.
Phoshi

Jawaban:


14

Kedua contoh ini setara, dan pada kenyataannya akan dikompilasi dengan bytecode yang sama.

Ada dua cara yang menambahkan tipe generik terbatas ke metode seperti pada contoh pertama Anda akan melakukan apa saja.

Melewati parameter tipe ke tipe lain

Kedua tanda tangan metode ini pada akhirnya sama dengan kode byte, tetapi kompiler memberlakukan keamanan tipe:

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

public static void addAnimals(Collection<Animal> animals)

Dalam kasus pertama, hanya Collection(atau subtipe) dari Animalyang diizinkan. Dalam kasus kedua, a Collection(atau subtipe) dengan tipe generik Animalatau subtipe diperbolehkan.

Misalnya, yang berikut diperbolehkan dalam metode pertama tetapi tidak yang kedua:

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

Alasannya adalah bahwa yang kedua hanya memungkinkan koleksi hewan, sedangkan yang pertama memungkinkan koleksi benda apa pun yang ditugaskan untuk hewan (yaitu subtipe). Perhatikan bahwa jika daftar ini adalah daftar hewan yang mengandung kucing, metode mana pun akan menerimanya: masalahnya adalah spesifikasi umum koleksi, bukan apa yang sebenarnya dikandungnya.

Mengembalikan benda

Waktu lain yang penting adalah dengan mengembalikan objek. Mari kita asumsikan ada metode berikut:

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

Anda dapat melakukan hal berikut dengan itu:

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

Meskipun ini adalah contoh yang dibuat-buat, ada beberapa kasus yang masuk akal. Tanpa obat generik, metode ini harus kembali Animaldan Anda perlu menambahkan tipe casting untuk membuatnya berfungsi (yang merupakan kompiler yang menambahkan kode byte di belakang layar).


" Dalam kasus pertama, hanya Koleksi (atau subtipe) dari Hewan yang diizinkan. Dalam kasus kedua, Koleksi (atau subtipe) dengan jenis umum dari Hewan atau subtipe diperbolehkan. " - Apakah Anda ingin menggandakan periksa logika Anda di sana?
Dana Gugatan Monica

3

Gunakan obat generik alih-alih downcasting. "Downcasting" buruk, beralih dari tipe yang lebih umum ke yang lebih spesifik:

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

... Anda mempercayai itu aadalah kucing, tetapi kompiler tidak dapat menjamin hal itu. Mungkin berubah menjadi anjing saat runtime.

Di sinilah Anda akan menggunakan obat generik:

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

Sekarang Anda dapat menentukan bahwa Anda menginginkan pemburu kucing:

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

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

Sekarang kompiler dapat menjamin bahwa hunterC hanya akan menangkap kucing, dan hunterD hanya akan menangkap anjing.

Jadi gunakan polimorfisme biasa jika Anda hanya ingin menangani kelas-kelas tertentu sebagai tipe dasarnya. Mengabaikan adalah hal yang baik. Tetapi jika Anda berada dalam situasi di mana Anda perlu menangani kelas-kelas tertentu sebagai tipe mereka sendiri, gunakan generik secara umum.

Atau, sungguh, jika Anda harus tertunduk maka gunakan obat generik.

EDIT: kasus yang lebih umum adalah ketika Anda ingin menunda keputusan jenis apa yang harus ditangani. Jadi tipe menjadi parameter, dan juga nilainya.

Katakanlah saya ingin kelas Kebun Binatang saya menangani Kucing atau Spons. Saya tidak memiliki kelas super umum. Tapi saya masih bisa menggunakan:

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

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

sejauh mana Anda mengunci itu tergantung pada apa yang Anda coba lakukan;)


2

Pertanyaan ini sudah tua, tetapi faktor penting untuk dipertimbangkan tampaknya telah ditinggalkan mengenai kapan harus menggunakan parameter tipe polimorfisme vs terikat. Faktor ini mungkin sedikit bersinggungan dengan contoh yang diberikan dalam pertanyaan tetapi, saya merasa, sangat relevan dengan yang lebih umum "Kapan menggunakan parameter tipe polimorfisme vs terikat?"

TL; DR

Jika Anda pernah menemukan diri Anda memindahkan kode dari subkelas ke kelas dasar melawan penilaian Anda yang lebih baik, karena ketidakmampuan mengaksesnya dengan cara polimorfik, parameter tipe terikat dapat menjadi solusi potensial.

Jawaban Lengkap

Parameter tipe terikat dapat mengekspos metode subkelas yang tidak diwariskan untuk variabel anggota yang diwarisi. Polimorfisme tidak bisa

Untuk menguraikan dengan memperluas contoh Anda:

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

Jika kelas abstrak AnimalOwner didefinisikan memiliki protected Animal pet;dan memilih polimorfisme, kompiler akan melakukan kesalahan pada pet.scratchBelly();baris, memberi tahu Anda bahwa metode ini tidak ditentukan untuk Hewan.


1

Dalam contoh Anda, Anda tidak (dan tidak seharusnya) menggunakan tipe terikat. Hanya gunakan parameter tipe terbatas ketika Anda harus , karena mereka lebih membingungkan untuk dipahami.

Berikut adalah beberapa situasi di mana Anda akan menggunakan parameter tipe terbatas:

  • Parameter koleksi

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

    maka kamu bisa menelepon

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

    zoo.add(dogs)akan gagal untuk mengkompilasi tanpa <? extends Animal>, karena obat generik tidak kovarian.

  • Subklasifikasi

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

    untuk membatasi jenis subclass dapat menyediakan.

Anda juga dapat menggunakan beberapa batasan <T extends A1 & A2 & A3>untuk memastikan suatu jenis adalah subtipe dari semua jenis dalam daftar.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.