Java Numaralandırma tanımı


151

Java jeneriklerini oldukça iyi anladığımı düşündüm, ama sonra java.lang.Enum'da aşağıdakilere rastladım:

class Enum<E extends Enum<E>>

Birisi bu tür parametrenin nasıl yorumlanacağını açıklayabilir mi? Benzer bir tip parametresinin nerede kullanılabileceğine dair başka örnekler sağlamak için bonus puanlar.



Bu sorunun daha iyi yanıtları var: stackoverflow.com/a/3068001/2413303
EpicPandaForce

Yanıtlar:


105

Bu, enum için tür bağımsız değişkeninin, kendisinin aynı tür bağımsız değişkenine sahip bir enumdan türetmesi gerektiği anlamına gelir. Bu nasıl olabilir? Type bağımsız değişkenini yeni türün kendisi yaparak. Yani StatusCode adlı bir numaralandırma varsa, buna eşdeğer olurdu:

public class StatusCode extends Enum<StatusCode>

Şimdi kısıtlamaları kontrol ederseniz, var Enum<StatusCode>- yani E=StatusCode. Kontrol edelim: Euzanıyor Enum<StatusCode>mu? Evet! Biz iyiyiz.

Kendinize bunun amacının ne olduğunu soruyor olabilirsiniz :) Eh, bu, Enum için API'nin kendisine başvurabileceği anlamına gelir - örneğin, bunu Enum<E>uyguladığını söyleyebilmek Comparable<E>. Temel sınıf karşılaştırmaları (numaralandırmalar durumunda) yapabilir, ancak yalnızca doğru türdeki numaralandırmaları birbirleriyle karşılaştırdığından emin olabilir. (DÜZENLEME: Neredeyse - alttaki düzenlemeye bakın.)

ProtocolBuffers benim C # port benzer bir şey kullandım. "Mesajlar" (değişmez) ve "yapıcılar" (değişebilir, bir mesaj oluşturmak için kullanılır) vardır ve bunlar bir tür çift olarak gelir. İlgili arayüzler:

public interface IBuilder<TMessage, TBuilder>
  where TMessage : IMessage<TMessage, TBuilder> 
  where TBuilder : IBuilder<TMessage, TBuilder>

public interface IMessage<TMessage, TBuilder>
  where TMessage : IMessage<TMessage, TBuilder> 
  where TBuilder : IBuilder<TMessage, TBuilder>

Bu, bir mesajdan uygun bir oluşturucu alabileceğiniz (örneğin bir mesajın kopyasını alıp bazı bitleri değiştirmek için) ve bir oluşturucudan oluşturmayı bitirdiğinizde uygun bir mesaj alabileceğiniz anlamına gelir. API kullanıcılarının iyi bir iş olmasına rağmen aslında bunu önemsemelerine gerek yok - korkunç derecede karmaşık ve bulunduğu yere ulaşmak için birkaç iterasyon aldı.

DÜZENLEME: Bunun, kendisinin iyi olduğu, ancak aynı tür olmayan bir tür bağımsız değişkeni kullanan tek türler oluşturmanızı engellemediğini unutmayın. Amaç, sizi yanlış davadan korumak yerine doğru davada fayda sağlamaktır .

Dolayısıyla Enum, Java'da "özel olarak" ele alınmadıysa, (yorumlarda belirtildiği gibi) aşağıdaki türleri oluşturabilirsiniz:

public class First extends Enum<First> {}
public class Second extends Enum<First> {}

Seconduygulamak Comparable<First>yerine Comparable<Second>... ama Firstkendisi iyi olurdu.


1
@artsrc: Neden hem kurucuda hem de mesajda jenerik olması gerektiğini açıkça hatırlayamıyorum. Gerçi ihtiyacım olmasaydı o rotadan aşağı
gitmezdim

1
@SayemAhmed: Evet, türleri karıştırmanın bu yönünü engellemiyor . Bununla ilgili bir not ekleyeceğim.
Jon Skeet

1
"ProtocolBuffers benim C # port benzer bir şey kullandım." Ancak bu farklıdır çünkü inşaatçılar, type parametresi türünü döndüren örnek yöntemlere sahiptir. Enumtype parametresi türünü döndüren örnek yöntemleri yoktur.
newacct

1
@JonSkeet: Enum sınıflarının her zaman otomatik olarak üretildiği göz önüne alındığında, bunun class Enum<E>her durumda yeterli olduğunu iddia ediyorum . Ve Generics'te daha kısıtlayıcı bir sınır kullanmalısınız, ancak tip güvenliğini sağlamak için gerçekten gerekliyse.
newacct

1
@JonSkeet: Ayrıca, Enumalt sınıfları hep autogenerated edilmedi, bunu gerekir tek nedeni class Enum<E extends Enum<?>>üzerinde class Enum<E>erişime yeteneğidir ordinaliçin compareTo(). Bununla birlikte, düşünürseniz, iki farklı türdeki sıralamayı sıralamaları aracılığıyla karşılaştırmanıza izin vermek, dil açısından mantıklı değildir. Bu nedenle, Enum.compareTo()bunun kullanımı ordinalsadece Enumotomatik olarak üretilen alt sınıflar bağlamında anlamlıdır . El ile alt sınıf olabilir Enum, compareTomuhtemelen olması gerekir abstract.
newacct

27

Aşağıdaki kitaptan açıklama değiştirilmiş bir versiyonu , Java Jenerik ve Koleksiyonlar : Biz var Enumdeklare

enum Season { WINTER, SPRING, SUMMER, FALL }

bir sınıfa genişletilecek

final class Season extends ...

...Enum'lar için bir şekilde parametrelendirilen temel sınıf nerede olacak. Bunun ne olması gerektiğine bakalım. Bunun için şartlardan biri de Seasonuygulanması gerektiğidir Comparable<Season>. Yani ihtiyacımız olacak

Season extends ... implements Comparable<Season>

Bunun ...çalışmasına izin vermek için ne kullanabilirsiniz ? Bunun bir parametrelendirmesi olması gerektiği göz önüne alındığında Enum, tek seçenek şudur Enum<Season>:

Season extends Enum<Season>
Enum<Season> implements Comparable<Season>

Yani Enumgibi türlerde parametrelendirilir Season. Dan Özet Seasonve parametre olsun Enumher türlü tatmin olmasıdır

 E extends Enum<E>

Maurice Naftalin (ortak yazar, Java Generics and Collections)


1
@newacct Tamam, şimdi anladım: Tüm numaralandırmaların Enum <E> örnekleri olmasını istersiniz değil mi? (Çünkü bunlar bir Enum alt tipinin örnekleri ise, yukarıdaki argüman geçerlidir.) Ancak o zaman artık tür güvenli olan numaralandırmalarınız olmayacaktır, bu nedenle numaralandırmalara sahip olma noktasını kaybedersiniz.
Maurice Naftalin

1
@newacct Bunun Seasonuygulanması konusunda ısrar etmek istemiyor musunuz Comparable<Season>?
Maurice Naftalin

2
@newacct Enum tanımına bakın. Bir örneği diğeriyle karşılaştırmak için sıralarını karşılaştırmak zorundadır. Bu nedenle, compareToyöntemin argümanı bir alt tür olarak bildirilmiş olmalı Enumveya derleyici (doğru) bir sıraya sahip olmadığını söyleyecektir.
Maurice Naftalin

2
@MauriceNaftalin: Java manuel olarak alt sınıflamayı yasaklamamışsa Enum, şu anda class OneEnum extends Enum<AnotherEnum>{}nasıl Enumbildirilmiş olsa bile, sahip olmak mümkün olacaktır . O kadar sonra, diğeriyle enum bir tür karşılaştırma yapabilmek için çok mantıklı olmaz Enum's compareTozaten deklare olarak anlamlı olmaz. Sınırlar buna herhangi bir yardım sağlamaz.
newacct

2
@MauriceNaftalin: Eğer sıra sıralı public class Enum<E extends Enum<?>>olsaydı , o zaman da yeterli olurdu.
newacct

6

Bu, basit bir örnek ve alt sınıflar için zincirleme yöntem çağrılarını uygulamak için kullanılabilecek bir teknikle açıklanabilir. Aşağıdaki örnekte setName, bir Nodezincirleme şunlar için çalışmaz City:

class Node {
    String name;

    Node setName(String name) {
        this.name = name;
        return this;
    }
}

class City extends Node {
    int square;

    City setSquare(int square) {
        this.square = square;
        return this;
    }
}

public static void main(String[] args) {
    City city = new City()
        .setName("LA")
        .setSquare(100);    // won't compile, setName() returns Node
}

Böylece, genel bir bildirimde bir alt sınıfa başvurabiliriz, böylece Cityşimdi doğru türü döndürür:

abstract class Node<SELF extends Node<SELF>>{
    String name;

    SELF setName(String name) {
        this.name = name;
        return self();
    }

    protected abstract SELF self();
}

class City extends Node<City> {
    int square;

    City setSquare(int square) {
        this.square = square;
        return self();
    }

    @Override
    protected City self() {
        return this;
    }

    public static void main(String[] args) {
       City city = new City()
            .setName("LA")
            .setSquare(100);                 // ok!
    }
}

Çözümünüzün denetlenmemiş bir yayını var: return (CHILD) this;Bir getThis () yöntemi eklemeyi düşünün: protected CHILD getThis() { return this; } Bkz: angelikalanger.com/GenericsFAQ/FAQSections/…
Roland

@Roland bir bağlantı için teşekkürler, ondan bir fikir ödünç aldım. Bana açıklayabilir misiniz veya bu özel durumda bunun neden kötü bir uygulama olduğunu açıklayan bir makaleye yönlendirebilir misiniz? Bağlantıda yöntem daha fazla yazmayı gerektirir ve bu neden bundan kaçınıyorum ana argüman. Bu durumda hiç döküm hatası görmedim + Bazı kaçınılmaz döküm hataları olduğunu biliyorum - yani bir koleksiyon aynı tür birden çok türde nesneleri saklar. Dolayısıyla, kontrol edilmemiş dökümler kritik değilse ve tasarım biraz karmaşıksa ( Node<T>durum böyle değildir), zaman kazanmak için onları görmezden geliyorum.
Andrey Chaschev

Düzenlemeniz, biraz sözdizimsel şeker eklemenin yanı sıra öncekinden farklı değil, şu kodun gerçekten derleneceğini ancak bir çalışma zamanı hatası atacağını düşünün: `Düğüm <Şehir> düğüm = yeni Düğüm <Şehir> () .setName (" düğüm "). setSquare (1); `Java bayt koduna bakarsanız, tür silme nedeniyle ifadenin return (SELF) this;derlendiğini return this;göreceksiniz, böylece sadece dışarıda bırakabilirsiniz.
Roland

@Roland teşekkürler, ihtiyacım olan şey bu - özgür olduğumda örneği güncelleyecek.
Andrey Chaschev

Aşağıdaki bağlantı da iyidir: angelikalanger.com/GenericsFAQ/FAQSections/…
Roland

3

Bunun ne anlama geldiğini merak eden tek kişi sen değilsin; bkz. Kaotik Java blogu .

“Bir sınıf bu sınıfı genişletirse, E parametresini geçmelidir. E parametresinin sınırları, bu sınıfı aynı E parametresiyle genişleten bir sınıf içindir”.


1

Bu yazı bana 'yinelemeli jenerik tipler' sorununu açıklığa kavuşturdu. Bu özel yapının gerekli olduğu başka bir durum daha eklemek istedim.

Genel bir grafikte genel düğümleriniz olduğunu varsayalım:

public abstract class Node<T extends Node<T>>
{
    public void addNeighbor(T);

    public void addNeighbors(Collection<? extends T> nodes);

    public Collection<T> getNeighbor();
}

O zaman özel tip grafiklere sahip olabilirsiniz:

public class City extends Node<City>
{
    public void addNeighbor(City){...}

    public void addNeighbors(Collection<? extends City> nodes){...}

    public Collection<City> getNeighbor(){...}
}

Yine de class Foo extends Node<City>Foo'nun City ile ilgisi olmayan bir yer yaratmama izin veriyor .
newacct

1
Elbette, bu yanlış mı? Ben öyle düşünmüyorum. <City> Düğümü tarafından sağlanan temel sözleşme hala onurlandırıldı, sadece Foo alt sınıfınız Foos ile çalışmaya başladığınız, ancak Şehirleri ADT'den çıkardığınız için daha az kullanışlı. Bunun için bir kullanım durumu olabilir, ancak genel parametreyi alt sınıfla aynı yapmak için daha basit ve daha kullanışlı olabilir. Ama her iki durumda da, tasarımcı bu seçeneğe sahip.
Mdma

@mdma: Katılıyorum. Öyleyse, sınır sadece ne işe yarar class Node<T>?
newacct

1
@nozebacle: Örneğiniz "bu özel yapının gerekli olduğunu" göstermiyor. class Node<T>örneğinizle tamamen tutarlıdır.
newacct

1

EnumKaynak koduna bakarsanız , aşağıdakilere sahiptir:

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {

    public final int compareTo(E o) {
        Enum<?> other = (Enum<?>)o;
        Enum<E> self = this;
        if (self.getClass() != other.getClass() && // optimization
            self.getDeclaringClass() != other.getDeclaringClass())
            throw new ClassCastException();
        return self.ordinal - other.ordinal;
    }

    @SuppressWarnings("unchecked")
    public final Class<E> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }

    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    } 
}

İlk önce, ne anlama E extends Enum<E>geliyor? Bu, type parametresinin Enum'dan uzanan bir şey olduğu ve ham bir türle (kendi başına parametrelendirildiği) parametrelenmediği anlamına gelir.

Bir numaralandırmanız varsa bu önemlidir

public enum MyEnum {
    THING1,
    THING2;
}

ki eğer doğru bilirsem,

public final class MyEnum extends Enum<MyEnum> {
    public static final MyEnum THING1 = new MyEnum();
    public static final MyEnum THING2 = new MyEnum();
}

Bu, MyEnum'un aşağıdaki yöntemleri aldığı anlamına gelir:

public final int compareTo(MyEnum o) {
    Enum<?> other = (Enum<?>)o;
    Enum<MyEnum> self = this;
    if (self.getClass() != other.getClass() && // optimization
        self.getDeclaringClass() != other.getDeclaringClass())
        throw new ClassCastException();
    return self.ordinal - other.ordinal;
}

Ve daha da önemlisi,

    @SuppressWarnings("unchecked")
    public final Class<MyEnum> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<MyEnum>)clazz : (Class<MyEnum>)zuper;
    }

Bu getDeclaringClass(), uygun Class<T>nesneye döküm yapar .

Daha açık bir örnek, genel bir sınır belirtmek istiyorsanız bu yapıdan kaçınamayacağınız bu soruya cevap verdiğim örnektir .


Gösterdiğiniz compareToveya sınır getDeclaringClassgerektiren hiçbir şey yok extends Enum<E>.
newacct

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.