ทำไม Java enum ตัวอักษรไม่สามารถมีพารามิเตอร์ประเภททั่วไปได้?


148

Java enums ยอดเยี่ยม ดังนั้นยาชื่อสามัญ แน่นอนว่าเราทุกคนรู้ถึงข้อ จำกัด ของอันหลังเนื่องจากการลบประเภท แต่มีสิ่งหนึ่งที่ฉันไม่เข้าใจว่าทำไมฉันไม่สามารถสร้าง enum เช่นนี้:

public enum MyEnum<T> {
    LITERAL1<String>,
    LITERAL2<Integer>,
    LITERAL3<Object>;
}

พารามิเตอร์ประเภททั่วไป<T>ในทางกลับกันนั้นจะมีประโยชน์ในสถานที่ต่าง ๆ ลองนึกภาพพารามิเตอร์ประเภททั่วไปกับวิธีการ:

public <T> T getValue(MyEnum<T> param);

หรือแม้แต่ในชั้นเรียน Enum เอง:

public T convert(Object o);

ตัวอย่างที่ชัดเจนยิ่งขึ้น # 1

เนื่องจากตัวอย่างด้านบนอาจดูเป็นนามธรรมเกินไปสำหรับบางคนนี่เป็นตัวอย่างในชีวิตจริงมากขึ้นว่าทำไมฉันถึงต้องการทำสิ่งนี้ ในตัวอย่างนี้ฉันต้องการใช้

  • Enums เพราะจากนั้นฉันสามารถระบุชุดของคีย์คุณสมบัติที่ จำกัด ได้
  • ยาสามัญเพราะฉันสามารถมีความปลอดภัยประเภทระดับวิธีการสำหรับการจัดเก็บคุณสมบัติ
public interface MyProperties {
     public <T> void put(MyEnum<T> key, T value);
     public <T> T get(MyEnum<T> key);
}

ตัวอย่างที่ชัดเจนยิ่งขึ้น # 2

ฉันมีการแจกแจงของชนิดข้อมูล:

public interface DataType<T> {}

public enum SQLDataType<T> implements DataType<T> {
    TINYINT<Byte>,
    SMALLINT<Short>,
    INT<Integer>,
    BIGINT<Long>,
    CLOB<String>,
    VARCHAR<String>,
    ...
}

แต่ละตัวอักษร enum จะเห็นได้ชัดว่ามีคุณสมบัติเพิ่มเติมตามประเภททั่วไป<T>ในขณะที่ในเวลาเดียวกันเป็น enum (ไม่เปลี่ยนรูปซิงเกิลตันนับ ฯลฯ ฯลฯ )

คำถาม:

ไม่มีใครคิดเรื่องนี้บ้าง? นี่เป็นข้อ จำกัด ที่เกี่ยวข้องกับคอมไพเลอร์หรือไม่? พิจารณาข้อเท็จจริงที่ว่าคำหลัก " enum " นั้นถูกนำไปใช้เป็นน้ำตาล syntactic ซึ่งเป็นตัวแทนของรหัสที่สร้างขึ้นเพื่อ JVM ฉันไม่เข้าใจข้อ จำกัด นี้

ใครสามารถอธิบายสิ่งนี้ให้ฉันได้บ้าง ก่อนที่คุณจะตอบให้พิจารณาสิ่งนี้:

  • ฉันรู้ว่าประเภททั่วไปถูกลบ :-)
  • ฉันรู้ว่ามีวิธีแก้ปัญหาโดยใช้คลาสวัตถุ พวกเขากำลังแก้ไขปัญหา
  • ประเภททั่วไปส่งผลให้เกิดประเภทคอมไพเลอร์สร้างมันได้ทุกที่ (เช่นเมื่อเรียกวิธีการแปลง ()
  • ประเภททั่วไป <T> จะอยู่ใน enum ดังนั้นมันจึงผูกพันตามตัวอักษรของ enum แต่ละตัว ดังนั้นคอมไพเลอร์จะได้รู้ว่าประเภทใดที่จะนำไปใช้เมื่อเขียนบางอย่างString string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject);
  • เช่นเดียวกับพารามิเตอร์ประเภททั่วไปในT getvalue()วิธีการ คอมไพเลอร์สามารถใช้การหล่อประเภทเมื่อโทรString string = someClass.getValue(LITERAL1)

3
ฉันไม่เข้าใจข้อ จำกัด นี้เช่นกัน ฉันเจอสิ่งนี้เมื่อไม่นานมานี้ที่ enum ของฉันมีประเภท "Comparable" ที่แตกต่างกันและกับ Generics เฉพาะประเภท Comparable เท่านั้นที่สามารถเปรียบเทียบได้โดยไม่ต้องมีคำเตือนที่ต้องระงับ (แม้ว่าในขณะรันไทม์ ฉันสามารถกำจัดคำเตือนเหล่านี้ได้โดยใช้ประเภทที่ถูกผูกไว้ใน enum เพื่อระบุประเภทที่เทียบเคียงได้รับการสนับสนุน แต่ฉันต้องเพิ่มหมายเหตุประกอบ SuppressWarnings - ไม่มีทางรอบ! ตั้งแต่การเปรียบเทียบจะโยนข้อยกเว้นการเรียนต่อไปมันก็โอเคฉันเดาว่า แต่ก็ยัง ...
MetroidFan2002

5
(+1) ฉันเพิ่งพยายามปิดช่องว่างความปลอดภัยประเภทในโครงการของฉันหยุดตายด้วยข้อ จำกัด โดยพลการนี้ เพียงแค่พิจารณาสิ่งนี้: เปลี่ยนenumเป็น "typesafe enum" สำนวนที่เราใช้ก่อนหน้า Java 1.5 ทันใดนั้นคุณสามารถกำหนดพารามิเตอร์สมาชิกของคุณได้ นั่นอาจเป็นสิ่งที่ฉันจะทำตอนนี้
Marko Topolnik

1
@EdwinDalorzo: อัปเดตคำถามด้วยตัวอย่างที่เป็นรูปธรรมจากjOOQซึ่งสิ่งนี้จะเป็นประโยชน์อย่างมากในอดีต
Lukas Eder

2
@ LukasEder ฉันเห็นจุดของคุณแล้ว ดูเหมือนว่าคุณสมบัติใหม่ที่ยอดเยี่ยม บางทีคุณควรแนะนำในรายการส่งเมลแบบหยอดเหรียญของฉันเห็นข้อเสนอที่น่าสนใจอื่น ๆ ใน enums แต่ไม่มีใครเหมือนคุณ
Edwin Dalorzo

1
เห็นด้วยอย่างสิ้นเชิง. Enum ที่ไม่มียาชื่อสามัญนั้นพิการ กรณีของคุณ # 1 เป็นของฉันเช่นกัน ถ้าฉันต้องการ enum ทั่วไปฉันจะเลิกใช้ JDK5 และนำไปใช้ในรูปแบบ Java 1.4 แบบธรรมดา วิธีนี้มีประโยชน์เพิ่มเติม : ฉันไม่ได้บังคับให้มีค่าคงที่ทั้งหมดในหนึ่งชั้นหรือแม้แต่แพ็คเกจ ดังนั้นรูปแบบตามคุณสมบัติจึงทำได้ดีกว่ามาก นี่คือที่สมบูรณ์แบบสำหรับการกำหนดค่าเช่น "enums" - ค่าคงที่จะถูกกระจายในแพคเกจตามความหมายเชิงตรรกะของพวกเขา (และถ้าฉันต้องการเห็นพวกเขาทั้งหมดฉันมีประเภท hieararchy แสดง)
TomášZáluský

คำตอบ:


50

นี้ขณะนี้ถูกกล่าวถึงในฐานะของJEP-301 Enums ตัวอย่างที่ให้ไว้ใน JEP คือซึ่งเป็นสิ่งที่ฉันกำลังมองหา:

enum Argument<X> { // declares generic enum
   STRING<String>(String.class), 
   INTEGER<Integer>(Integer.class), ... ;

   Class<X> clazz;

   Argument(Class<X> clazz) { this.clazz = clazz; }

   Class<X> getClazz() { return clazz; }
}

Class<String> cs = Argument.STRING.getClazz(); //uses sharper typing of enum constant

น่าเสียดายที่ JEP ยังคงดิ้นรนกับปัญหาที่สำคัญ: http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html


2
เมื่อวันที่ธันวาคม 2561 มีสัญญาณของชีวิตรอบ JEP 301 อีกครั้งแต่การข้ามการสนทนาทำให้ชัดเจนว่าปัญหายังอยู่ไกลจากการแก้ไข
Pont

11

คำตอบอยู่ในคำถาม:

เนื่องจากการลบประเภท

ไม่มีวิธีใดในสองวิธีนี้ที่เป็นไปได้เนื่องจากประเภทอาร์กิวเมนต์จะถูกลบ

public <T> T getValue(MyEnum<T> param);
public T convert(Object);

เมื่อต้องการตระหนักถึงวิธีการเหล่านั้นคุณสามารถสร้าง enum ของคุณเป็น:

public enum MyEnum {
    LITERAL1(String.class),
    LITERAL2(Integer.class),
    LITERAL3(Object.class);

    private Class<?> clazz;

    private MyEnum(Class<?> clazz) {
      this.clazz = clazz;
    }

    ...

}

2
การลบจะเกิดขึ้นในเวลารวบรวม แต่คอมไพเลอร์สามารถใช้ข้อมูลประเภททั่วไปสำหรับการตรวจสอบประเภท จากนั้น "แปลง" ประเภททั่วไปเป็นประเภทปลดเปลื้อง ฉันจะเรียบเรียงคำถามใหม่อีกครั้ง
Lukas Eder

2
ไม่แน่ใจว่าฉันติดตามทั้งหมด public T convert(Object);เอา ฉันเดาว่าวิธีนี้อาจทำให้กลุ่มของประเภทที่แตกต่างกันแคบลงเพื่อ <T> ซึ่งตัวอย่างเช่นสตริง การสร้างวัตถุ String เป็น runtime - ไม่มีสิ่งใดที่คอมไพเลอร์ ดังนั้นคุณจำเป็นต้องรู้ประเภทรันไทม์เช่น String.class หรือว่าฉันขาดอะไรไป?
Martin Algesten

6
ฉันคิดว่าคุณพลาดข้อเท็จจริงที่ว่าประเภททั่วไป <T> อยู่ใน enum (หรือคลาสที่สร้างขึ้น) อินสแตนซ์เดียวของ enum คือตัวอักษรซึ่งทั้งหมดจัดเตรียมการโยงชนิดทั่วไปคงที่ ดังนั้นจึงไม่มีความคลุมเครือเกี่ยวกับ T ในการแปลง T สาธารณะ (Object);
Lukas Eder

2
Aha! เข้าใจแล้ว คุณกำลังฉลาดกว่าค่ะ :)
มาร์ติน Algesten

1
ฉันเดาว่ามันจะลงมาที่ชั้นเรียนเพื่อการประเมินค่า Enum ในลักษณะที่คล้ายคลึงกับทุกอย่าง ในจาวาแม้ว่าสิ่งที่ดูเหมือนจะคงที่พวกเขาไม่ได้ พิจารณาpublic final static String FOO;ด้วยบล็อกแบบสแตติกstatic { FOO = "bar"; }- ยังมีการประเมินค่าคงที่แม้เมื่อทราบในเวลารวบรวม
Martin Algesten

5

เพราะคุณทำไม่ได้ อย่างจริงจัง. สามารถเพิ่มลงในข้อมูลจำเพาะภาษาได้ มันยังไม่เคย มันจะเพิ่มความซับซ้อนบางอย่าง ประโยชน์ที่ได้รับจากต้นทุนหมายความว่าไม่ใช่ลำดับความสำคัญสูง

ปรับปรุง: ปัจจุบันการเพิ่มภาษาภายใต้JEP 301: ปรับปรุง Enums


คุณสามารถอธิบายรายละเอียดเกี่ยวกับโรคแทรกซ้อนได้หรือไม่?
Mr_and_Mrs_D

4
ฉันกำลังคิดเกี่ยวกับเรื่องนี้มาสองสามวันแล้วและฉันก็ยังไม่พบอาการแทรกซ้อน
Marko Topolnik

4

มีวิธีอื่นใน ENUM ที่ไม่สามารถใช้งานได้ สิ่งที่จะMyEnum.values()กลับมา?

เกี่ยวกับอะไร MyEnum.valueOf(String name)อะไร

สำหรับ valueOf หากคุณคิดว่าคอมไพเลอร์สามารถสร้างวิธีการทั่วไปเช่น

ค่าคง MyEnum สาธารณะคงที่ (ชื่อสตริง);

เพื่อที่จะเรียกมันว่ามันMyEnum<String> myStringEnum = MyEnum.value("some string property")ไม่ทำงานเช่นกัน ตัวอย่างเช่นถ้าคุณโทรMyEnum<Int> myIntEnum = MyEnum.<Int>value("some string property")? มันเป็นไปไม่ได้ที่จะใช้วิธีการนั้นให้ทำงานอย่างถูกต้องตัวอย่างเช่นการโยน exception หรือ return null เมื่อคุณเรียกมันว่าเป็นMyEnum.<Int>value("some double property")เพราะการลบประเภท


2
ทำไมพวกเขาถึงไม่ทำงาน พวกเขาต้องการใช้สัญลักษณ์แทน ... : MyEnum<?>[] values()และMyEnum<?> valueOf(...)
Lukas Eder

1
แต่คุณไม่สามารถทำการมอบหมายเช่นนี้ได้MyEnum<Int> blabla = valueOf("some double property");เนื่องจากประเภทไม่รองรับ นอกจากนี้คุณต้องการในกรณีที่จะได้รับโมฆะเพราะคุณต้องการกลับ MyEnum <Int> ซึ่งไม่ได้มีอยู่สำหรับชื่อคุณสมบัติสองครั้งและคุณไม่สามารถทำให้วิธีการที่ทำงานอย่างถูกต้องเพราะการลบออก
user1944408

นอกจากนี้หากคุณวนลูปผ่านค่า () คุณจะต้องใช้ MyEnum <?> ซึ่งไม่ใช่สิ่งที่คุณต้องการโดยปกติเพราะตัวอย่างเช่นคุณไม่สามารถวนซ้ำผ่านคุณสมบัติ Int เท่านั้น นอกจากนี้คุณจะต้องทำการคัดเลือกนักแสดงมากมายที่คุณต้องการหลีกเลี่ยงฉันขอแนะนำให้สร้าง enums ที่แตกต่างกันสำหรับทุกประเภทหรือทำคลาสของคุณเองด้วยอินสแตนซ์ ...
user1944408

ฉันเดาว่าคุณคงไม่มีทั้งคู่ โดยปกติแล้วฉันใช้วิธี "enum" ของตัวเอง มันEnumมีคุณสมบัติที่มีประโยชน์อื่น ๆ มากมายและสิ่งนี้จะเป็นตัวเลือกและมีประโยชน์มากเช่นกัน ...
Lukas Eder

0

ตรงไปตรงนี้ดูเหมือนจะเป็นทางออกในการค้นหาปัญหามากกว่าสิ่งใด

จุดประสงค์ทั้งหมดของ java enum คือสร้างแบบจำลองการแจกแจงชนิดอินสแตนซ์ที่แบ่งปันคุณสมบัติที่คล้ายกันในลักษณะที่ให้ความสม่ำเสมอและความมีชีวิตชีวาเหนือกว่าการเป็นตัวแทนของสตริงหรือจำนวนเต็มเปรียบเทียบกัน

นำตัวอย่างของ enum หนังสือเรียน สิ่งนี้ไม่มีประโยชน์หรือสอดคล้องกันมาก:

public enum Planet<T>{
    Earth<Planet>,
    Venus<String>,
    Mars<Long>
    ...etc.
}

เหตุใดฉันจึงต้องการให้ดาวเคราะห์ที่แตกต่างของฉันมีการแปลงประเภททั่วไปต่างกัน มันแก้ปัญหาอะไรได้บ้าง? มันแสดงให้เห็นถึงความซับซ้อนของความหมายภาษา? หากฉันต้องการพฤติกรรมนี้เป็นเครื่องมือที่ดีที่สุดในการบรรลุเป้าหมาย

นอกจากนี้คุณจะจัดการกับการแปลงที่ซับซ้อนได้อย่างไร

สำหรับอินสแตนซ์

public enum BadIdea<T>{
   INSTANCE1<Long>,
   INSTANCE2<MyComplexClass>;
}

มันง่ายพอที่String Integerจะให้ชื่อหรือลำดับ แต่ยาชื่อสามัญจะอนุญาตให้คุณจัดหาประเภทใดก็ได้ คุณจะจัดการการแปลงเป็นMyComplexClassอย่างไร ทีนี้คุณสร้างโครงสร้างสองอย่างด้วยการบังคับให้คอมไพเลอร์รู้ว่ามีเซตย่อยจำนวนหนึ่งที่สามารถจัดให้กับ enums ทั่วไปและแนะนำความสับสนเพิ่มเติมให้กับแนวคิด (Generics) ที่ดูเหมือนจะหลบหลีกโปรแกรมเมอร์จำนวนมาก


17
การนึกถึงตัวอย่างสองสามตัวอย่างที่ไม่เป็นประโยชน์เป็นข้อโต้แย้งที่น่ากลัวว่าจะไม่มีประโยชน์
Elias Vasylenko

1
ตัวอย่างสนับสนุนประเด็น ตัวอย่าง enum เป็นประเภทย่อยของประเภท (ดีและเรียบง่าย) และที่รวมถึง generics เป็นกระป๋องของเวิร์มมันแง่ของความซับซ้อนเพื่อประโยชน์ชัดเจนมาก หากคุณกำลังจะลงคะแนนฉันแล้วคุณควรลงคะแนน Tom Hawtin ด้านล่างที่พูดในสิ่งเดียวกันด้วยคำพูดที่ไม่มากนัก
nsfyn55

1
@ nsfyn55 Enums เป็นหนึ่งในคุณสมบัติทางภาษาที่ซับซ้อนและมหัศจรรย์ที่สุดของ Java ไม่มีคุณสมบัติภาษาอื่นที่ใช้วิธีสร้างแบบสแตติกอัตโนมัติ นอกจากนี้แต่ละประเภท enum แล้วเป็นตัวอย่างของประเภททั่วไป
Marko Topolnik

1
@ nsfyn55 เป้าหมายการออกแบบคือการทำให้สมาชิก enum มีพลังและยืดหยุ่นซึ่งเป็นสาเหตุที่พวกเขาสนับสนุนคุณสมบัติขั้นสูงเช่นวิธีการกำหนดอินสแตนซ์ที่กำหนดเองและแม้แต่ตัวแปรอินสแตนซ์ที่ไม่แน่นอน พวกเขาได้รับการออกแบบให้มีพฤติกรรมเฉพาะสำหรับสมาชิกแต่ละคนเพื่อให้พวกเขาสามารถเข้าร่วมเป็นผู้ทำงานร่วมกันในสถานการณ์การใช้งานที่ซับซ้อน ส่วนใหญ่ Java enum ได้รับการออกแบบมาเพื่อให้การแจกแจงแบบทั่วไปใช้สำนวนที่ปลอดภัยแต่การขาดพารามิเตอร์ประเภทน่าเสียดายที่ทำให้เป้าหมายนั้นตกต่ำลง ฉันค่อนข้างมั่นใจว่ามีเหตุผลที่เฉพาะเจาะจงสำหรับสิ่งนั้น
Marko Topolnik

1
ผมไม่ได้สังเกตการพูดถึงของคุณ contravariance ... Java ไม่สนับสนุนมันเพียง แต่มันใช้สถานที่ซึ่งทำให้เทอะทะบิต interface Converter<IN, OUT> { OUT convert(IN in); } <E> Set<E> convertListToSet(List<E> in, Converter<? super List<E>, ? extends Set<E>> converter) { return converter.convert(in); }เราต้องทำงานด้วยตนเองทุกครั้งที่ใช้งานประเภทใดและผลิตแล้วและระบุขอบเขตตามนั้น
Marko Topolnik

-2

กลายเป็น "enum" เป็นตัวย่อสำหรับการแจงนับ มันเป็นแค่ชุดของค่าคงที่ที่มีชื่อที่ยืนแทนเลขลำดับเพื่อให้โค้ดอ่านง่ายขึ้น

ฉันไม่เห็นความหมายที่ตั้งใจไว้ของค่าคงที่ที่กำหนดพารามิเตอร์ชนิดอาจเป็นได้


1
ในjava.lang.String: public static final Comparator<String> CASE_INSENSITIVE_ORDER. เห็นไหม :-)
Lukas Eder

4
คุณบอกว่าคุณไม่เห็นความหมายของค่าคงที่ประเภทพารามิเตอร์ที่อาจเป็นได้ ดังนั้นฉันแสดงให้คุณเห็นค่าคงที่ประเภทพารามิเตอร์ (ไม่ใช่ตัวชี้ฟังก์ชั่น)
Lukas Eder

ไม่แน่นอนเนื่องจากภาษาจาวาไม่ทราบว่าเป็นตัวชี้ฟังก์ชั่น ยังไม่ใช่ค่าคงที่ที่เป็นพารามิเตอร์ แต่เป็นประเภท และพารามิเตอร์ประเภทนั้นคงที่ไม่ใช่ตัวแปรประเภท
Ingo

แต่ไวยากรณ์ enum เป็นเพียงน้ำตาลประโยค ข้างล่างพวกเขาเหมือนCASE_INSENSITIVE_ORDER... ถ้าComparator<T>เป็น enum ทำไมไม่มีตัวอักษรที่เกี่ยวข้องกับการผูกไว้<T>?
Lukas Eder

ถ้า Comparator <T> เป็น enum แน่นอนว่าเราสามารถมีสิ่งแปลก ๆ ได้ทุกประเภท บางทีบางอย่างเช่น Integer <T> แต่มันไม่ใช่
Ingo

-3

ฉันคิดว่าเพราะโดยทั่วไปแล้ว Enums ไม่สามารถเข้าร่วมได้

คุณจะตั้งค่าคลาส T ที่ใดถ้า JVM อนุญาตให้คุณทำเช่นนั้น

การแจงนับเป็นข้อมูลที่ควรจะเหมือนกันเสมอหรืออย่างน้อยที่สุดก็จะไม่เปลี่ยนแปลงแบบ dinamically

ใหม่ MyEnum <> ()?

วิธีการต่อไปนี้อาจมีประโยชน์

public enum MyEnum{

    LITERAL1("s"),
    LITERAL2("a"),
    LITERAL3(2);

    private Object o;

    private MyEnum(Object o) {
        this.o = o;
    }

    public Object getO() {
        return o;
    }

    public void setO(Object o) {
        this.o = o;
    }   
}

8
ไม่แน่ใจว่าผมชอบวิธีการในsetO() enumผมคิดว่าคง enums และกับผมว่าหมายถึงการเปลี่ยนรูป ดังนั้นแม้ว่าเป็นไปได้ฉันจะไม่ทำ
Martin Algesten

2
Enums จะทำการติดตั้งในโค้ดที่สร้างโดยคอมไพเลอร์ แต่ละตัวอักษรถูกสร้างขึ้นจริงโดยการเรียกตัวสร้างส่วนตัว ดังนั้นจึงมีโอกาสที่จะส่งประเภททั่วไปไปยังตัวสร้างในเวลารวบรวม
Lukas Eder

2
ตัวติดตั้งบน enums เป็นเรื่องปกติอย่างสมบูรณ์ โดยเฉพาะอย่างยิ่งถ้าคุณกำลังใช้ enum เพื่อสร้างซิงเกิลตัน (รายการที่ 3 Java ที่มีประสิทธิภาพโดย Joshua Bloch)
Preston
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.