Genel yöntemler ne zaman ve joker karakter ne zaman kullanılır?


122

OracleDocGenericMethod'dan jenerik yöntemler hakkında okuyorum . Joker karakterin ne zaman ve genel yöntemlerin ne zaman kullanılacağını söylediği zaman karşılaştırma konusunda kafam oldukça karışık. Belgeden alıntı yapmak.

interface Collection<E> {
    public boolean containsAll(Collection<?> c);
    public boolean addAll(Collection<? extends E> c);
}

Bunun yerine burada genel yöntemler kullanabilirdik:

interface Collection<E> {
    public <T> boolean containsAll(Collection<T> c);
    public <T extends E> boolean addAll(Collection<T> c);
    // Hey, type variables can have bounds too!
}

[…] Bu bize tür bağımsız değişkeninin polimorfizm için kullanıldığını gösterir; tek etkisi, çeşitli gerçek argüman türlerinin farklı çağrı sitelerinde kullanılmasına izin vermektir. Eğer durum buysa, joker karakterler kullanılmalıdır. Joker karakterler, burada ifade etmeye çalıştığımız şey olan esnek alt türlemeyi desteklemek için tasarlanmıştır.

Joker karakterin (Collection<? extends E> c);de bir tür polimorfizmi desteklediğini düşünmüyor muyuz ? Öyleyse neden jenerik yöntem kullanımı bunda iyi değil?

Devam ediyor, diyor ki,

Genel yöntemler, tür parametrelerinin bir veya daha fazla argümanın türleri arasında bir yönteme ve / veya dönüş türüne bağımlılıkları ifade etmek için kullanılmasına izin verir. Böyle bir bağımlılık yoksa jenerik bir yöntem kullanılmamalıdır.

Ne anlama geliyor?

Örneği sundular

class Collections {
    public static <T> void copy(List<T> dest, List<? extends T> src) {
    ...
}

[...]

Bu yöntem için imzayı joker karakter kullanmadan başka bir şekilde yazabilirdik:

class Collections {
    public static <T, S extends T> void copy(List<T> dest, List<S> src) {
    ...
}

Belge, ikinci bildirimi caydırıyor ve ilk sözdiziminin kullanımını teşvik ediyor mu? Birinci ve ikinci beyan arasındaki fark nedir? İkisi de aynı şeyi yapıyor gibi görünüyor?

Birisi bu alana ışık tutabilir mi?

Yanıtlar:


173

Joker karakterlerin ve tür parametrelerinin aynı şeyi yaptığı belirli yerler vardır. Ancak tip parametrelerini kullanmanız gereken belirli yerler de vardır.

  1. Farklı türdeki yöntem bağımsız değişkenleri üzerinde bir ilişki zorlamak istiyorsanız, bunu joker karakterlerle yapamazsınız, tür parametrelerini kullanmanız gerekir.

Yönteminizi örnek alarak, yönteme iletilen srcve destlistesinin copy()aynı parametreleştirilmiş türde olmasını sağlamak istediğinizi varsayalım , bunu aşağıdaki gibi tür parametreleri ile yapabilirsiniz:

public static <T extends Number> void copy(List<T> dest, List<T> src)

Burada, hem alması sağlanır, destve srcaynı parametreli türü var List. Yani, öğeleri kopyalamak için güvenli srciçin dest.

Ancak, joker karakter kullanmak için yöntemi değiştirmeye devam ederseniz:

public static void copy(List<? extends Number> dest, List<? extends Number> src)

beklendiği gibi çalışmayacak. 2 durumda, geçebilir List<Integer>ve List<Float>olarak destve src. Yani, öğeleri bir yerden bir srcyere taşımak destartık güvenli olmayacak. Böyle bir ilişkiye ihtiyacınız yoksa, tür parametrelerini hiç kullanmamakta özgürsünüz.

Joker karakterler ve tür parametreleri kullanmak arasındaki diğer bazı farklar şunlardır:

  • Yalnızca bir parametreleştirilmiş tür bağımsız değişkeniniz varsa, joker karakter kullanabilirsiniz, ancak tür parametresi de çalışacaktır.
  • Tür parametreleri birden çok sınırı destekler, joker karakterler desteklemez.
  • Joker karakterler hem üst hem de alt sınırları destekler, yazım parametreleri yalnızca üst sınırları destekler. Dolayısıyla, bir Listtürü alan Integerveya bu süper sınıf olan bir yöntem tanımlamak istiyorsanız , şunları yapabilirsiniz:

    public void print(List<? super Integer> list)  // OK

    ancak tür parametresini kullanamazsınız:

     public <T super Integer> void print(List<T> list)  // Won't compile

Referanslar:


1
Bu garip cevap. Neden kullanmanız gerektiğini açıklamıyor ?. Bunu `public static <T1 extends Number, T2 extends Number> void copy (List <T1> dest, List <T2> src) olarak yeniden yazabilirsiniz ve bu durumda neler olduğu açık hale gelir.
kan

@kan. İşte gerçek sorun bu. Aynı türü zorlamak için tür parametresini kullanabilirsiniz, ancak bunu joker karakterlerle yapamazsınız. Tür parametresi için iki farklı tür kullanmak farklı bir şeydir.
Rohit Jain

1
@benz. Bir kullanım Listtürü parametresinde alt sınır tanımlayamazsınız . List<T super Integer>geçerli değil ve derlenmeyecek.
Rohit Jain

2
@benz. Rica ederim :) Sonunda yazdığım bağlantıya gitmenizi şiddetle tavsiye ederim. Jenerikler hakkında alabileceğiniz en iyi kaynak budur.
Rohit Jain

3
@ jorgen.ringen <T extends X & Y>-> birden çok sınır.
Rohit Jain

12

Aşağıdaki 2 SinglyLinkQueue'yu birleştirmek istediğimiz, James Gosling'in 4. baskısının Java Programlamasından aşağıdaki örneği düşünün:

public static <T1, T2 extends T1> void merge(SinglyLinkQueue<T1> d, SinglyLinkQueue<T2> s){
    // merge s element into d
}

public static <T> void merge(SinglyLinkQueue<T> d, SinglyLinkQueue<? extends T> s){
        // merge s element into d
}

Yukarıdaki yöntemlerin her ikisi de aynı işlevselliğe sahiptir. Peki hangisi tercih edilir? Cevap 2. Yazarın kendi sözleriyle:

"Genel kural, mümkün olduğunda joker karakterler kullanmaktır, çünkü joker karakterli kod genellikle birden fazla tür parametresi olan koddan daha okunabilirdir. Bir tür değişkenine ihtiyacınız olup olmadığına karar verirken, bu tür değişkeninin iki veya daha fazla parametreyi ilişkilendirmek için kullanılıp kullanılmadığını kendinize sorun, veya bir parametre türünü dönüş türüyle ilişkilendirmek için. Cevap hayırsa, bir joker karakter yeterli olmalıdır. "

Not: Kitapta yalnızca ikinci yöntem verilir ve parametre adı 'T' yerine S'dir. İlk yöntem kitapta yok.


Bir kitabın
alıntısına oy verdim

9

İlk sorunuzda: Parametrenin türü ile yöntemin dönüş türü arasında bir ilişki varsa o zaman bir jenerik kullanın anlamına gelir.

Örneğin:

public <T> T giveMeMaximum(Collection<T> items);
public <T> Collection<T> applyFilter(Collection<T> items);

Burada, T'nin bir kısmını belirli bir kritere göre çıkarıyorsunuz. LongYöntemleriniz T ise geri dönecek Longve Collection<Long>; gerçek dönüş türü parametre türüne bağlıdır, bu nedenle genel türlerin kullanılması yararlıdır ve tavsiye edilir.

Durum böyle olmadığında, joker karakter türlerini kullanabilirsiniz:

public int count(Collection<?> items);
public boolean containsDuplicate(Collection<?> items);

Bu iki örnekte, koleksiyonlardaki öğelerin türü ne olursa olsun, iade türleri intve olacaktır boolean.

Örneklerinizde:

interface Collection<E> {
    public boolean containsAll(Collection<?> c);
    public boolean addAll(Collection<? extends E> c);
}

bu iki işlev, koleksiyonlardaki öğelerin türü ne olursa olsun bir boole döndürür. İkinci durumda, E'nin bir alt sınıfının örnekleriyle sınırlıdır.

İkinci soru:

class Collections {
    public static <T> void copy(List<T> dest, List<? extends T> src) {
    ...
}

Bu ilk kod, heterojen List<? extends T> srcbir parametre olarak geçmenize izin verir . Bu liste, tümü temel T sınıfını genişlettiği sürece farklı sınıflardan birden çok öğe içerebilir.

olsaydı:

interface Fruit{}

ve

class Apple implements Fruit{}
class Pear implements Fruit{}
class Tomato implements Fruit{}

yapabilirsin

List<? extends Fruit> basket = new ArrayList<? extends Fruit>();
basket.add(new Apple());
basket.add(new Pear());
basket.add(new Tomato());
List<Fruit> fridge = new ArrayList<Fruit>(); 

Collections.copy(fridge, basket);// works 

Diğer yandan

class Collections {
    public static <T, S extends T> void copy(List<T> dest, List<S> src) {
    ...
}

List<S> srcT'nin bir alt sınıfı olan belirli bir S sınıfından olma kısıtlaması . Liste, T'yi de uygulasalar bile, yalnızca bir sınıfın öğelerini (bu örnekte S) içerebilir ve başka bir sınıf içeremez. Önceki örneğimi kullanamazsınız ama şunları yapabilirsiniz:

List<Apple> basket = new ArrayList<Apple>();
basket.add(new Apple());
basket.add(new Apple());
basket.add(new Apple());
List<Fruit> fridge = new ArrayList<Fruit>();

Collections.copy(fridge, basket); /* works since the basket is defined as a List of apples and not a list of some fruits. */

1
List<? extends Fruit> basket = new ArrayList<? extends Fruit>();Geçerli bir sözdizimi değil. ArrayList'i sınırlar olmadan başlatmanız gerekir.
Arnold Pistorius

Sepet Armutların bir listesi olabileceğinden, yukarıdaki örnekte sepete bir Elma eklenemez. Yanlış örnek AFAIK. Ve aynı zamanda derlemez.
Khanna111

1
@ArnoldPistorius Bu kafamı karıştırdı. ArrayList'in API belgelerini kontrol ettim ve imzalı bir kurucu var ArrayList(Collection<? extends E> c). Bunu neden söylediğini bana açıklayabilir misin?
Kurapika

@Kurapika Eski bir Java sürümü kullanıyor olabilir miyim? Yorum neredeyse 3 yıl önce yayınlandı.
Arnold Pistorius

2

Joker karakter yöntemi de geneldir - onu bazı türler ile çağırabilirsiniz.

<T>Söz diziminin, bir tip değişken adı tanımlar. Bir tür değişkeninin herhangi bir kullanımı varsa (örneğin, yöntem uygulamasında veya başka tür için bir kısıtlama olarak), onu adlandırmak mantıklıdır, aksi takdirde ?anonim değişken olarak kullanabilirsiniz . Yani, kısayol gibi görünüyor.

Dahası, ?bir alan tanımladığınızda sözdiziminden kaçınılamaz:

class NumberContainer
{
 Set<? extends Number> numbers;
}

3
Bunun bir yorum olmaması gerekiyor mu?
Buhake Sindi

@BuhakeSindi Üzgünüm, belirsiz olan nedir? Neden -1? Sanırım soruyu yanıtlıyor.
kan

2

Sorunuzu tek tek cevaplamaya çalışacağım.

Joker karakterin (Collection<? extends E> c);de bir tür polimorfizmi desteklediğini düşünmüyor muyuz ?

Hayır. Nedeni, sınırlı joker karakterin tanımlanmış bir parametre türüne sahip olmamasıdır. Bilinmeyen. Tüm "bildiği", "kapsama" nın E(tanımlanmış ne olursa olsun) bir tip olduğudur. Bu nedenle, sağlanan değerin sınırlı türle eşleşip eşleşmediğini doğrulayamaz ve gerekçelendiremez.

Bu nedenle, joker karakterlerde polimorfik davranışlara sahip olmak mantıklı değildir.

Belge, ikinci bildirimi caydırıyor ve ilk sözdiziminin kullanımını teşvik ediyor mu? Birinci ve ikinci beyan arasındaki fark nedir? İkisi de aynı şeyi yapıyor gibi görünüyor?

Her Tzaman sınırlı olduğu gibi bu durumda ilk seçenek daha iyidir ve sourcekesinlikle alt sınıfların değerlerine (bilinmeyenlere ait) sahip olacaktır T.

Öyleyse, tüm numara listesini kopyalamak istediğinizi varsayalım, ilk seçenek

Collections.copy(List<Number> dest, List<? extends Number> src);

srcEsas olarak, kabul edebilir List<Double>, List<Float>parametreli türü bulunan bir üst sınır olarak, vb dest.

2. seçenek Ssizi kopyalamak istediğiniz her tür için bağlamaya zorlayacaktır.

//For double 
Collections.copy(List<Number> dest, List<Double> src); //Double extends Number.

//For int
Collections.copy(List<Number> dest, List<Integer> src); //Integer extends Number.

As S, bağlama gerektiren parametreli bir türdür.

Umarım bu yardımcı olur.


Son paragrafınızda ne demek istediğinizi açıklayabilir misiniz
benz

2. seçeneği belirten, sizi birini bağlamaya zorlayacaktır ...... bunun üzerinde ayrıntılara girebilir misiniz
benz

<S extends T>devletler Sbir alt sınıfı olan bir parametreli türüdür T, bu yüzden bir alt sınıfı olan bir parametreli türü (hayır joker) gerektirir T.
Buhake Sindi

2

Burada listelenmeyen diğer bir fark.

static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
    for (T o : a) {
        c.add(o); // correct
    }
}

Ancak aşağıdakiler derleme zamanı hatasıyla sonuçlanacaktır.

static <T> void fromArrayToCollection(T[] a, Collection<?> c) {
    for (T o : a) {
        c.add(o); // compile time error
    }
}

0

Anladığım kadarıyla, joker karaktere kesinlikle ihtiyaç duyulduğunda yalnızca bir kullanım durumu vardır (yani, açık tür parametreleri kullanarak ifade edemeyeceğiniz bir şeyi ifade edebilir). Bu, bir alt sınır belirlemeniz gereken zamandır.

Bununla birlikte, joker karakterler, bahsettiğiniz belgedeki aşağıdaki ifadelerde açıklandığı gibi, daha kısa kod yazmaya hizmet eder:

Genel yöntemler, tür parametrelerinin bir veya daha fazla argümanın türleri arasında bir yönteme ve / veya dönüş türüne bağımlılıkları ifade etmek için kullanılmasına izin verir. Böyle bir bağımlılık yoksa jenerik bir yöntem kullanılmamalıdır.

[...]

Joker karakterlerin kullanılması, açık tür parametrelerini bildirmekten daha açık ve daha özlüdür ve bu nedenle mümkün olduğunda tercih edilmelidir.

[...]

Joker karakterlerin, alan türleri, yerel değişkenler ve diziler olarak yöntem imzalarının dışında kullanılabilme avantajı da vardır.


0

Temelde -> Joker Karakterler, Genel Olmayan bir yöntemin parametre / bağımsız değişken düzeyinde jenerikleri uygular. Not. Varsayılan olarak genericMethod içinde de gerçekleştirilebilir, ancak burada yerine? T'nin kendisini kullanabiliriz.

paket jenerikleri;

public class DemoWildCard {


    public static void main(String[] args) {
        DemoWildCard obj = new DemoWildCard();

        obj.display(new Person<Integer>());
        obj.display(new Person<String>());

    }

    void display(Person<?> person) {
        //allows person of Integer,String or anything
        //This cannnot be done if we use T, because in that case we have to make this method itself generic
        System.out.println(person);
    }

}

class Person<T>{

}

SO joker karakterinin bunun gibi kendine özgü kullanım alanları vardır.

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.