Java - Polimorfizm veya sınırlı tip parametreleri kullanın


17

Diyelim ki bu sınıf hiyerarşisine sahibim ...

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

Ve sonra ...

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

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

Sınırlı tip parametreleri veya polimorfizm kullanmanın farkı nedir?

Ve ne zaman biri veya diğeri kullanılır?


1
Bu iki tanım tip parametrelerinden fazla kazanç sağlamaz. Ama addAnimals(List<Animal>)bir Kedi Listesi yazmayı ve eklemeyi deneyin !
Kilian Foth

7
Örneğin, yukarıdaki yöntemlerinizin her biri de bir şey döndürürse, jenerik ilaç kullanan biri T, diğeri sadece Hayvan'ı döndürebilir. Bu yöntemleri kullanan kişi için, ilk durumda tam olarak ne istediğini geri alacaktı: Dod dog = addAnimal (new Dog ()); 2. yöntemde ise, bir köpek almak için rol yapmak zorunda kalacaktı: Köpek d = (Köpek) addAnimalPoly (yeni Köpek ());
Shivan Dragon

Sınırlı türleri kullanımımın büyük bir kısmı Java'nın jeneriklerin zayıf uygulaması üzerine duvar kağıdı yapmaktır (Liste <T>, örneğin yalnızca T için farklı varyans kurallarına sahiptir). Bu durumda hiçbir avantajı yoktur, aslında aynı kavramı ifade etmenin iki yolu vardır, ancak @ShivanDragon'un belirttiği gibi , derleme sırasında bir Hayvan yerine bir T'ye sahip olduğunuz anlamına gelir. Sadece dahili olarak bir hayvan gibi davranabilirsiniz, ancak harici olarak bir T olarak sunabilirsiniz.
Phoshi

Yanıtlar:


14

Bu iki örnek eşdeğerdir ve aslında aynı bayt koduyla derlenir.

İlk örneğinizdeki gibi bir yönteme sınırlı bir genel tür eklemenin her şeyi yapmasının iki yolu vardır.

Type parametresini başka bir türe geçirme

Bu iki yöntem imzası bayt kodunda aynı olur, ancak derleyici tür güvenliğini zorunlu kılar:

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

public static void addAnimals(Collection<Animal> animals)

İlk durumda, sadece bir Collection(veya alt tip) Animal'e izin verilir. İkinci durumda, Collectiongenel tipte veya alt tipte bir (veya alt tip) Animalizin verilir.

Örneğin, ilk yöntemde aşağıdakilere izin verilir ancak ikincisinde izin verilmez:

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

Bunun nedeni, ikincisinin sadece hayvanların toplanmasına izin vermesi, birincisi ise hayvana atanabilen herhangi bir nesnenin (yani alt tipler) toplanmasına izin vermesidir. Bu liste bir kedi içeren hayvanların bir listesi olsaydı, her iki yöntemin de kabul edeceğini unutmayın: sorun, koleksiyonun aslında içerdiği değil genel jenerasyonudur.

Nesneleri döndürme

Diğer önemli zaman dönen nesnelerle. Aşağıdaki yöntemin var olduğunu varsayalım:

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

Onunla aşağıdakileri yapabilirsiniz:

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

Bu tartışmalı bir örnek olsa da, mantıklı olduğu durumlar vardır. Jenerikler olmadan, yöntemin geri dönmesi Animalgerekir ve çalışmasını sağlamak için tür dökümü eklemeniz gerekir (derleyici yine de sahne arkasındaki bayt koduna ekler).


" İlk durumda, yalnızca bir Hayvan Koleksiyonuna (veya alt tipine) izin verilir. İkinci durumda, genel bir Hayvan türü veya alt tipine sahip bir Koleksiyona (veya alt tipine) izin verilir. " - orada mantığını kontrol et?
Monica'nın Davası

3

Alt çizgi yerine jenerikler kullanın. "Aşağıya vurma" kötüdür, daha genel bir türden daha spesifik bir türe gider:

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

... abunun bir kedi olduğuna inanıyorsunuz , ancak derleyici bunu garanti edemez. Çalışma zamanında bir köpek olabilir.

Jenerik ilaçları burada kullanabilirsiniz:

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

Şimdi bir kedi avcısı istediğinizi belirtebilirsiniz:

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

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

Şimdi derleyici hunterC'nin sadece kedileri yakalayacağını garanti edebilir ve hunterD sadece köpekleri yakalar.

Bu nedenle, temel sınıflar olarak belirli sınıfları işlemek istiyorsanız normal polimorfizm kullanın. Upcasting iyi bir şeydir. Ancak, belirli sınıfları kendi türü olarak ele almanız gereken bir duruma girerseniz, genel olarak jenerikleri kullanın.

Ya da gerçekten, mahzun bulmanız gerekiyorsa jenerikler kullanın.

DÜZENLEME: Daha genel durum, ne tür türlerin ele alınacağına karar vermek istediğiniz zamandır. Böylece türler , değerlerin yanı sıra bir parametre haline gelir.

Hayvanat Bahçesi sınıfımın Kediler veya Süngerleri idare etmesini istiyorum. Ortak bir süper sınıfım yok. Ama yine de kullanabilirim:

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

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

bunu kilitlemenin derecesi ne yapmaya çalıştığınıza bağlıdır;)


2

Bu soru eskidir, ancak polimorfizme karşı sınırlı tip parametrelerinin ne zaman kullanılacağı konusunda dikkate alınması gereken önemli bir faktör göz ardı edilmiştir. Bu faktör, soruda verilen örneğe biraz teğet olabilir, ancak daha genel "Polimorfizm ve sınırlı tip parametreleri ne zaman kullanılır?"

TL; DR

Kendinizi daha iyi bir muhakemenize karşı bir alt sınıftan bir temel sınıfa taşıdığınızı görürseniz, polimorfik bir şekilde erişememesi nedeniyle sınırlı tip parametreleri potansiyel bir çözüm olabilir.

Tam Yanıt

Sınırlı tip parametreleri, devralınan bir üye değişken için somut, devralınmamış alt sınıf yöntemlerini ortaya çıkarabilir. Çok biçimlilik yapamaz

Örneğinizi genişleterek ayrıntılandırmak için:

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

Soyut sınıf AnimalOwner bir protected Animal pet;ve polimorfizme sahip olarak tanımlanmışsa , derleyici pet.scratchBelly();satırda hata verir ve bu yöntemin Hayvan için tanımsız olduğunu söyler.


1

Örneğinizde, sınırlı tür kullanmıyorsunuz (kullanmamalısınız). Sınırlı tip parametrelerini sadece anlamak zorunda olduğunuzda kullanmanız gerektiğinde kullanın .

Sınırlı tür parametrelerini kullanacağınız bazı durumlar şunlardır:

  • Koleksiyon parametreleri

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

    o zaman arayabilirsin

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

    zoo.add(dogs)olmadan derleme başarısız olur <? extends Animal>, çünkü jenerikler kovaryant değildir.

  • sınıflara

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

    sınırlamak için alt sınıf sağlayabilir.

<T extends A1 & A2 & A3>Bir türün listedeki tüm türlerin bir alt türü olduğundan emin olmak için birden çok sınır da kullanabilirsiniz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.