นิยาม Java Enum


151

ฉันคิดว่าฉันเข้าใจ Java generics ค่อนข้างดี แต่แล้วฉันก็พบสิ่งต่อไปนี้ใน java.lang.Enum:

class Enum<E extends Enum<E>>

มีคนอธิบายวิธีตีความพารามิเตอร์ประเภทนี้ได้ไหม คะแนนโบนัสสำหรับการให้ตัวอย่างอื่น ๆ ที่สามารถใช้พารามิเตอร์ประเภทที่คล้ายคลึงกันได้


9
นี่คือคำอธิบายที่ฉันชอบที่สุด: Groking Enum (aka Enum & lt; E ขยาย Enum & lt; E >>)
Alan Moore

คำถามนี้มีคำตอบที่ดีกว่า: stackoverflow.com/a/3068001/2413303
EpicPandaForce

คำตอบ:


105

หมายความว่าอาร์กิวเมนต์ชนิดสำหรับ enum ต้องสืบทอดมาจาก enum ซึ่งมีอาร์กิวเมนต์ชนิดเดียวกัน สิ่งนี้จะเกิดขึ้นได้อย่างไร? โดยทำให้อาร์กิวเมนต์ประเภทเป็นชนิดใหม่เอง ดังนั้นถ้าฉันได้รับ enum เรียกว่า StatusCode มันจะเทียบเท่ากับ:

public class StatusCode extends Enum<StatusCode>

ตอนนี้ถ้าคุณตรวจสอบข้อ จำกัด เราก็มีEnum<StatusCode>เช่นE=StatusCodeนั้น ตรวจสอบกัน: EขยายEnum<StatusCode>หรือไม่ ใช่ เราไม่เป็นไร

คุณอาจจะถามตัวเองว่าประเด็นนี้คืออะไร :) ก็หมายความว่า API สำหรับ Enum สามารถอ้างถึงตัวเองได้ - ตัวอย่างเช่นการพูดถึงEnum<E>การดำเนินการComparable<E>นั้น คลาสฐานสามารถทำการเปรียบเทียบ (ในกรณีของ enums) แต่สามารถตรวจสอบให้แน่ใจว่าจะเปรียบเทียบ enums ชนิดที่เหมาะสมกับแต่ละอื่น ๆ เท่านั้น (แก้ไข: ก็เกือบ - ดูการแก้ไขที่ด้านล่าง)

ฉันใช้สิ่งที่คล้ายกันใน C # พอร์ตของ ProtocolBuffers มี "ข้อความ" (ไม่เปลี่ยนรูป) และ "ผู้สร้าง" (ไม่แน่นอนที่ใช้ในการสร้างข้อความ) - และพวกเขามาเป็นคู่ประเภท อินเทอร์เฟซที่เกี่ยวข้องคือ:

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>

ซึ่งหมายความว่าจากข้อความที่คุณสามารถรับตัวสร้างที่เหมาะสม (เช่นการคัดลอกข้อความและเปลี่ยนบิต) และจากผู้สร้างคุณสามารถรับข้อความที่เหมาะสมเมื่อคุณสร้างมันเสร็จแล้ว มันเป็นงานที่ดีสำหรับผู้ใช้งาน API ที่ไม่จำเป็นต้องสนใจเรื่องนี้จริง ๆ - มันซับซ้อนอย่างน่ากลัวและใช้การวนซ้ำหลายครั้งเพื่อไปยังที่ที่มันอยู่

แก้ไข: โปรดทราบว่าสิ่งนี้จะไม่หยุดคุณจากการสร้างประเภทคี่ซึ่งใช้อาร์กิวเมนต์ประเภทที่ตัวเองก็โอเค แต่ที่ไม่ได้เป็นประเภทเดียวกัน มีวัตถุประสงค์เพื่อให้ผลประโยชน์ในกรณีที่เหมาะสมมากกว่าปกป้องคุณจากกรณีที่ไม่ถูกต้อง

ดังนั้นหากEnumไม่ได้จัดการ "พิเศษ" ใน Java คุณสามารถ (ตามที่ระบุไว้ในความคิดเห็น) สร้างประเภทต่อไปนี้:

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

Secondจะใช้งานComparable<First>มากกว่าComparable<Second>... แต่Firstจะดีกว่า


1
@artsrc: ฉันจำไม่ได้ทันทีว่าทำไมมันต้องเป็นแบบทั่วไปทั้งในตัวสร้างและข้อความ ผมค่อนข้างมั่นใจว่าฉันจะไม่ได้ไปลงเส้นทางว่าถ้าผมไม่ได้ต้องการมันแม้ว่า :)
จอนสกีต

1
@SayemAhmed: ใช่มันไม่ได้ป้องกันไม่ว่าแง่มุมของการผสมขึ้นประเภท ฉันจะเพิ่มบันทึกเกี่ยวกับสิ่งนี้
Jon Skeet

1
"ฉันใช้สิ่งที่คล้ายกันใน C # พอร์ตของ ProtocolBuffers" แต่นั่นต่างกันเพราะผู้สร้างมีวิธีการอินสแตนซ์ที่คืนค่าพารามิเตอร์ประเภท Enumไม่มีวิธีการใด ๆ ที่ส่งคืนประเภทพารามิเตอร์ประเภท
newacct

1
@ JonSkeet: เนื่องจากคลาส enum นั้นสร้างอัตโนมัติอยู่เสมอฉันอ้างว่าclass Enum<E>เพียงพอในทุกกรณี และใน Generics คุณควรใช้ขอบเขตที่ จำกัด มากขึ้นหากจำเป็นจริงๆเพื่อความปลอดภัยของประเภท
newacct

1
@JonSkeet: นอกจากนี้ถ้าEnumsubclasses ไม่ได้ autogenerated เสมอเหตุผลเดียวที่คุณจะต้องclass Enum<E extends Enum<?>>มากกว่าclass Enum<E>ความสามารถในการเข้าถึงสำหรับordinal compareTo()อย่างไรก็ตามถ้าคุณคิดเกี่ยวกับมันมันไม่สมเหตุสมผลจากมุมมองของภาษาเพื่อให้คุณสามารถเปรียบเทียบ enums สองประเภทที่แตกต่างกันผ่านทางอันดับของพวกเขา ดังนั้นการใช้งานของEnum.compareTo()ที่ใช้ordinalเพียงทำให้รู้สึกในบริบทของEnumคลาสย่อยที่ถูกสร้างขึ้นโดยอัตโนมัติ หากคุณสามารถด้วยตนเอง subclass Enum, อาจจะต้องมีการcompareTo abstract
newacct

27

ต่อไปนี้เป็นคำอธิบายรุ่นที่แก้ไขจากหนังสือJava Generics and Collections : เราได้Enumประกาศแล้ว

enum Season { WINTER, SPRING, SUMMER, FALL }

ซึ่งจะขยายไปยังชั้นเรียน

final class Season extends ...

โดยที่...จะเป็นชั้นฐานอย่างใดพารามิเตอร์สำหรับ Enums เรามาดูกันดีกว่าว่ามีอะไรบ้าง ดีหนึ่งของความต้องการสำหรับการก็คือว่ามันควรใช้Season Comparable<Season>ดังนั้นเราจะต้องการ

Season extends ... implements Comparable<Season>

คุณจะใช้อะไรเพื่อ...ให้สิ่งนี้ใช้ได้ ระบุว่ามันจะต้องเป็นพารามิเตอร์ของEnumตัวเลือกเดียวคือEnum<Season>เพื่อให้คุณสามารถ:

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

ดังนั้นEnumจะ parameterised Seasonประเภทเช่น นามธรรมจากSeasonและคุณจะได้รับว่าพารามิเตอร์ของEnumเป็นประเภทใดที่ตอบสนอง

 E extends Enum<E>

Maurice Naftalin (ผู้เขียนร่วม, Java Generics and Collections)


1
@newacct ตกลงฉันได้รับแล้ว: คุณต้องการให้ enum ทั้งหมดเป็นตัวอย่างของ Enum <E> ใช่ไหม (เพราะถ้าพวกมันเป็นอินสแตนซ์ของประเภทย่อย Enum ข้อโต้แย้งด้านบนจึงนำมาใช้) แต่แล้วคุณจะไม่มีประเภทที่ปลอดภัยสำหรับ enums อีกต่อไปดังนั้นเสียจุดที่มี enums เลย
Maurice Naftalin

1
@newacct คุณไม่ต้องการที่จะยืนยันSeasonการดำเนินการที่Comparable<Season>?
Maurice Naftalin

2
@newacct ดูคำจำกัดความของ Enum เพื่อเปรียบเทียบหนึ่งอินสแตนซ์กับอีกมันจะต้องเปรียบเทียบลำดับของพวกเขา ดังนั้นอาร์กิวเมนต์ของcompareToเมธอดจะต้องถูกประกาศเป็นEnumประเภทย่อยมิฉะนั้นคอมไพเลอร์จะ (ถูกต้อง) บอกว่ามันไม่มีลำดับ
Maurice Naftalin

2
@MauriceNaftalin: หาก Java ไม่ได้ห้ามการแบ่งคลาสย่อยด้วยตนเองEnumอาจเป็นไปได้class OneEnum extends Enum<AnotherEnum>{}ว่าจะมีการEnumประกาศอย่างไร มันจะไม่ทำให้รู้สึกมากเพื่อให้สามารถเปรียบเทียบประเภทหนึ่งของ enum อีกด้วยดังนั้นแล้วEnum's compareToจะไม่ทำให้รู้สึกตามที่ประกาศอยู่แล้ว ขอบเขตไม่ได้ให้ความช่วยเหลือใด ๆ
newacct

2
@MauriceNaftalin: ถ้าลำดับเป็นเหตุผลก็public class Enum<E extends Enum<?>>จะพอเพียง
newacct

6

สิ่งนี้สามารถแสดงให้เห็นได้จากตัวอย่างง่าย ๆ และเทคนิคที่สามารถนำมาใช้ในการเรียกวิธีการแบบโซ่สำหรับคลาสย่อย ในตัวอย่างด้านล่างsetNameจะส่งคืนการNodeโยงดังนั้นจะไม่ทำงานสำหรับ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
}

ดังนั้นเราสามารถอ้างอิงคลาสย่อยในการประกาศทั่วไปเพื่อให้Cityตอนนี้ส่งคืนชนิดที่ถูกต้อง:

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!
    }
}

โซลูชันของคุณมีเพี้ยนไม่ จำกัด : return (CHILD) this;พิจารณาเพิ่มเมธอด getThis (): protected CHILD getThis() { return this; } ดูที่: angelikalanger.com/GenericsFAQ/FAQSections/…
Roland

@Roland ขอบคุณสำหรับลิงค์ฉันยืมความคิดจากมัน คุณช่วยอธิบายฉันหรืออธิบายบทความที่อธิบายได้ว่าทำไมนี่จึงเป็นข้อปฏิบัติที่ไม่ดีในกรณีนี้? วิธีการในลิงค์นั้นต้องใช้การพิมพ์มากกว่านี้และนี่คือเหตุผลหลักที่ฉันหลีกเลี่ยง ฉันไม่เคยเห็นข้อผิดพลาดการส่งในกรณีนี้ + ฉันรู้ว่ามีข้อผิดพลาดในการส่งที่ไม่สามารถหลีกเลี่ยงได้ - นั่นคือเมื่อมีการเก็บวัตถุหลายประเภทไว้ในคอลเลกชันเดียวกัน ดังนั้นถ้าไม่ได้ตรวจสอบ casts ไม่สำคัญและการออกแบบค่อนข้างซับซ้อน ( Node<T>ไม่ใช่กรณี) ฉันไม่สนใจพวกเขาเพื่อประหยัดเวลา
Andrey Chaschev

การแก้ไขของคุณไม่แตกต่างจากก่อนหน้านอกเหนือจากการเพิ่มน้ำตาล syntactic ให้พิจารณาว่ารหัสต่อไปนี้จะรวบรวม แต่โยนข้อผิดพลาดเวลาทำงาน: `Node <City> node = new Node <City> () .setName (" node ") setSquare (1); `ถ้าคุณดูโค้ดจาวาไบต์คุณจะเห็นว่าเนื่องจากการลบประเภทข้อความreturn (SELF) this;จะถูกคอมไพล์ในคำสั่งreturn this;ดังนั้นคุณสามารถปล่อยมันทิ้งได้
Roland

@Roland ขอบคุณนี่คือสิ่งที่ฉันต้องการ - จะอัปเดตตัวอย่างเมื่อฉันว่าง
Andrey Chaschev

ลิงค์ต่อไปนี้ก็เป็นสิ่งที่ดีเช่น: angelikalanger.com/GenericsFAQ/FAQSections/…
Roland

3

คุณไม่ใช่คนเดียวที่สงสัยว่านั่นหมายถึงอะไร ดูบล็อก Chaotic Javaบล็อกวุ่นวาย

“ ถ้าคลาสขยายคลาสนี้ควรผ่านพารามิเตอร์ E ขอบเขตของพารามิเตอร์ E นั้นมีไว้สำหรับคลาสที่ขยายคลาสนี้ด้วยพารามิเตอร์ E เดียวกัน”


1

โพสต์นี้ได้ชี้แจงให้ฉันทั้งหมดเกี่ยวกับ 'ประเภททั่วไปแบบเรียกซ้ำ' ทั้งหมด ฉันแค่อยากจะเพิ่มอีกกรณีที่จำเป็นต้องใช้โครงสร้างเฉพาะนี้

สมมติว่าคุณมีโหนดทั่วไปในกราฟทั่วไป:

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

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

    public Collection<T> getNeighbor();
}

จากนั้นคุณสามารถมีกราฟประเภทพิเศษ:

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

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

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

มันยังช่วยให้ฉันสามารถสร้างclass Foo extends Node<City>ที่ไม่เกี่ยวข้องกับเมืองฟู
newacct

1
แน่นอนและนั่นผิด ฉันไม่คิดอย่างนั้น สัญญาพื้นฐานที่ได้รับจาก Node <City> ยังคงได้รับเกียรติมีเพียงคลาสย่อย Foo ของคุณที่มีประโยชน์น้อยกว่าเนื่องจากคุณเริ่มทำงานกับ Foos แต่นำเมืองออกจาก ADT อาจมีกรณีการใช้งานสำหรับสิ่งนี้ แต่ส่วนใหญ่จะง่ายกว่าและมีประโยชน์มากกว่าเพียงแค่ทำให้พารามิเตอร์ทั่วไปเหมือนกับคลาสย่อย แต่อย่างใดผู้ออกแบบมีทางเลือกที่
mdma

@mdma: ฉันเห็นด้วย แล้วขอบเขตที่ใช้จะมีประโยชน์class Node<T>อะไรมากกว่า?
newacct

1
@ nozebacle: ตัวอย่างของคุณไม่แสดงให้เห็นว่า "จำเป็นต้องมีโครงสร้างเฉพาะนี้" class Node<T>สอดคล้องกับตัวอย่างของคุณอย่างสมบูรณ์
newacct

1

หากคุณดูEnumซอร์สโค้ดมันมีดังต่อไปนี้:

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

สิ่งแรกสิ่งที่E extends Enum<E>หมายถึงอะไร? มันหมายถึงพารามิเตอร์ประเภทเป็นสิ่งที่ยื่นออกมาจาก Enum และไม่ได้ parametrized กับประเภทดิบ (มันเป็นพารามิเตอร์ตัวเอง)

สิ่งนี้เกี่ยวข้องถ้าคุณมี enum

public enum MyEnum {
    THING1,
    THING2;
}

ซึ่งถ้าฉันรู้ถูกต้องแปลเป็น

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

ดังนั้นนี่หมายความว่า MyEnum ได้รับวิธีการดังต่อไปนี้:

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

และที่สำคัญยิ่งกว่านั้นคือ

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

สิ่งนี้ทำให้การgetDeclaringClass()ร่ายไปยังClass<T>วัตถุที่เหมาะสม

ตัวอย่างที่ชัดเจนกว่าคือตัวอย่างที่ฉันตอบในคำถามนี้ซึ่งคุณไม่สามารถหลีกเลี่ยงโครงสร้างนี้ได้หากคุณต้องการระบุขอบเขตทั่วไป


ไม่มีสิ่งใดที่คุณแสดงในcompareToหรือgetDeclaringClassต้องการextends Enum<E>ขอบเขต
newacct

0

ตามวิกิพีเดียรูปแบบนี้เรียกว่ารูปแบบเทมเพลตที่เกิดซ้ำอย่างแน่นอน โดยพื้นฐานแล้วการใช้รูปแบบ CRTP เราสามารถอ้างถึง subclass type ได้อย่างง่ายดายโดยไม่ต้องใช้ชนิดการหล่อซึ่งหมายความว่าโดยการใช้ pattern เราสามารถเลียนแบบฟังก์ชันเสมือนได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.