เหตุใดฉันจึงต้องสนใจว่า Java ไม่ได้มีการ reified generics


102

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

เห็นได้ชัดว่าเนื่องจากชนิดดิบเป็นสิ่งที่อนุญาตใน Java (และการตรวจสอบที่ไม่ปลอดภัย) จึงเป็นไปได้ที่จะล้มล้าง generics และลงเอยด้วยสิ่งList<Integer>นั้น (ตัวอย่าง) มีStrings อยู่จริง สิ่งนี้ชัดเจนว่าเป็นไปไม่ได้คือข้อมูลประเภท reified; แต่ต้องมีมากกว่านี้ !

ผู้คนสามารถโพสต์ตัวอย่างของสิ่งที่พวกเขาอยากทำจริงๆได้หรือไม่มี reified generics ฉันหมายถึงเห็นได้ชัดว่าคุณสามารถหาประเภทของ a Listat runtime ได้ แต่คุณจะทำอย่างไรกับมัน?

public <T> void foo(List<T> l) {
   if (l.getGenericType() == Integer.class) {
       //yeah baby! err, what now?

แก้ไข : การอัปเดตอย่างรวดเร็วสำหรับสิ่งนี้เนื่องจากคำตอบดูเหมือนส่วนใหญ่จะเกี่ยวข้องกับความจำเป็นในการส่งClassเป็นพารามิเตอร์ (ตัวอย่างEnumSet.noneOf(TimeUnit.class)) ฉันถูกมองมากขึ้นสำหรับบางสิ่งบางอย่างตามสายของที่นี้ก็เป็นไปไม่ได้ ตัวอย่างเช่น:

List<?> l1 = api.gimmeAList();
List<?> l2 = api.gimmeAnotherList();

if (l1.getGenericType().isAssignableFrom(l2.getGenericType())) {
    l1.addAll(l2); //why on earth would I be doing this anyway?

นี่หมายความว่าคุณสามารถรับคลาสของประเภททั่วไปที่รันไทม์ได้หรือไม่? (ถ้าเป็นเช่นนั้นฉันมีตัวอย่าง!)
James B

ฉันคิดว่าความต้องการส่วนใหญ่เกี่ยวกับยาชื่อสามัญที่นำกลับมาใช้ใหม่ได้นั้นมาจากคนที่ใช้ยาชื่อสามัญเป็นหลักกับคอลเล็กชันและต้องการให้คอลเล็กชันเหล่านั้นทำงานเหมือนอาร์เรย์มากกว่า
kdgregory

2
คำถามที่น่าสนใจยิ่งกว่า (สำหรับฉัน): ต้องใช้อะไรบ้างในการนำรูปแบบ C ++ มาใช้ใน Java ดูเหมือนว่าทำได้อย่างแน่นอนในรันไทม์ แต่จะทำลาย classloaders ที่มีอยู่ทั้งหมด (เพราะfindClass()จะต้องละเว้นการกำหนดพารามิเตอร์ แต่defineClass()ทำไม่ได้) และอย่างที่เราทราบกันดีว่าพลังที่ยึดเหนี่ยวความเข้ากันได้กลับมายิ่งใหญ่
kdgregory

1
ที่จริงJava ไม่ให้ยาชื่อสามัญ reified ในทางที่ ฉันให้รายละเอียดเพิ่มเติมในเธรด SO นี้: stackoverflow.com/questions/879855/…
Richard Gomes

JavaOne Keynoteบ่งชี้ว่า Java 9 จะให้การสนับสนุนทำให้เป็นจริง
Rangi Keen

คำตอบ:


81

จากไม่กี่ครั้งที่ฉันเจอ "ความต้องการ" นี้ในที่สุดมันก็ทำให้โครงสร้างนี้เดือด:

public class Foo<T> {

    private T t;

    public Foo() {
        this.t = new T(); // Help?
    }

}

สิ่งนี้ใช้ได้ใน C # โดยสมมติว่าTมีตัวสร้างเริ่มต้น คุณยังสามารถได้รับประเภทรันไทม์โดยและได้รับการก่อสร้างโดยtypeof(T)Type.GetConstructor()

โซลูชัน Java ทั่วไปจะส่งผ่านClass<T>อาร์กิวเมนต์ as

public class Foo<T> {

    private T t;

    public Foo(Class<T> cls) throws Exception {
        this.t = cls.newInstance();
    }

}

(ไม่จำเป็นต้องส่งผ่านเป็นอาร์กิวเมนต์ตัวสร้างเนื่องจากอาร์กิวเมนต์วิธีการก็ใช้ได้เช่นกันข้างต้นเป็นเพียงตัวอย่างเช่นกัน try-catchถูกละไว้ด้วยความกะทัดรัด)

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


5
ใช้งานได้ (ตัวอย่าง) ใน C #? คุณรู้ได้อย่างไรว่า T มีตัวสร้างเริ่มต้น?
Thomas Jung

4
ใช่มันทำ และนี่เป็นเพียงตัวอย่างพื้นฐาน (สมมติว่าเราทำงานกับ javabeans) ประเด็นทั้งหมดก็คือด้วย Java คุณไม่สามารถรับคลาสระหว่างรันไทม์โดยT.classหรือT.getClass()เพื่อให้คุณสามารถเข้าถึงฟิลด์ตัวสร้างและวิธีการทั้งหมดของมัน ทำให้ไม่สามารถก่อสร้างได้
BalusC

3
สิ่งนี้ดูเหมือนจะอ่อนแอมากสำหรับฉันในฐานะ "ปัญหาใหญ่" โดยเฉพาะอย่างยิ่งเนื่องจากสิ่งนี้น่าจะเป็นประโยชน์เมื่อใช้ร่วมกับการสะท้อนที่เปราะบางรอบ ๆ ตัวสร้าง / พารามิเตอร์เป็นต้น
oxbow_lakes

33
มันรวบรวมใน C # โดยที่คุณประกาศประเภทเป็น: คลาสสาธารณะ Foo <T> โดยที่ T: new () ซึ่งจะ จำกัด ชนิดของ T ที่ถูกต้องให้มีตัวสร้างที่ไม่มีพารามิเตอร์
Martin Harris

3
@Colin คุณสามารถบอก java ได้ว่าประเภททั่วไปที่กำหนดนั้นจำเป็นต้องใช้เพื่อขยายคลาสหรืออินเทอร์เฟซมากกว่าหนึ่งคลาส ดูส่วน "หลายขอบเขต" ของdocs.oracle.com/javase/tutorial/java/generics/bounded.html (แน่นอนคุณไม่สามารถบอกได้ว่าออบเจ็กต์จะเป็นหนึ่งในชุดเฉพาะที่ไม่แชร์เมธอดที่สามารถนำมาทำเป็นอินเทอร์เฟซซึ่งสามารถนำไปใช้งานได้บ้างใน C ++ โดยใช้ความเชี่ยวชาญเทมเพลต)
JAB

99

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

public void my_method(List<String> input) { ... }
public void my_method(List<Integer> input) { ... }

5
ไม่จำเป็นอย่างยิ่งที่จะต้องทำให้เกิดใหม่เพื่อให้สามารถทำเช่นนั้นได้ การเลือกเมธอดจะกระทำในเวลาคอมไพล์เมื่อข้อมูลชนิดเวลาคอมไพล์พร้อมใช้งาน
Tom Hawtin - แท

26
@ ทอม: สิ่งนี้ไม่ได้รวบรวมเนื่องจากการลบประเภท ทั้งสองได้รับการรวบรวมเป็นpublic void my_method(List input) {}. อย่างไรก็ตามฉันไม่เคยพบกับความต้องการนี้เพียงเพราะพวกเขาจะไม่มีชื่อเดียวกัน ถ้าพวกเขามีชื่อเดียวกันฉันจะถามว่าถ้าpublic <T extends Object> void my_method(List<T> input) {}ไม่ใช่ความคิดที่ดีกว่า
BalusC

1
อืมฉันมักจะหลีกเลี่ยงการโอเวอร์โหลดด้วยจำนวนพารามิเตอร์ที่เท่ากันทั้งหมดและชอบบางอย่างเช่นmyStringsMethod(List<String> input)และmyIntegersMethod(List<Integer> input)แม้ว่าจะมีการโอเวอร์โหลดสำหรับกรณีเช่นนี้ก็เป็นไปได้ใน Java
Fabian Steeg

4
@Fabian: ซึ่งหมายความว่าคุณต้องมีรหัสแยกต่างหากและป้องกันไม่ให้ประเภทของข้อดีที่คุณได้รับจาก<algorithm>C ++
David Thornley

ฉันสับสนนี่ก็เหมือนกับคะแนนที่สองของฉัน แต่ฉันได้รับ 2 downvotes หรือไม่ ใครสนใจที่จะสอนฉัน?
rsp

34

ประเภทความปลอดภัยอยู่ในใจ การดาวน์แคสต์เป็นประเภทพาราเมตริกจะไม่ปลอดภัยเสมอไปหากไม่มีการแก้ไขซ้ำ:

List<String> myFriends = new ArrayList();
myFriends.add("Alice");
getSession().put("friends", myFriends);
// later, elsewhere
List<Friend> myFriends = (List<Friend>) getSession().get("friends");
myFriends.add(new Friend("Bob")); // works like a charm!
// and so...
List<String> myFriends = (List<String>) getSession().get("friends");
for (String friend : myFriends) print(friend); // ClassCastException, wtf!? 

นอกจากนี้สิ่งที่เป็นนามธรรมจะรั่วไหลน้อยลง - อย่างน้อยสิ่งที่อาจสนใจข้อมูลรันไทม์เกี่ยวกับพารามิเตอร์ประเภทของพวกเขา วันนี้หากคุณต้องการข้อมูลรันไทม์ประเภทใดประเภทหนึ่งของพารามิเตอร์ทั่วไปคุณต้องส่งผ่านClassไปด้วย ด้วยวิธีนี้อินเทอร์เฟซภายนอกของคุณขึ้นอยู่กับการใช้งานของคุณ (ไม่ว่าคุณจะใช้ RTTI เกี่ยวกับพารามิเตอร์ของคุณหรือไม่ก็ตาม)


1
ใช่ - ฉันมีวิธีแก้ปัญหานี้ในการสร้างParametrizedListที่คัดลอกข้อมูลในประเภทการตรวจสอบการรวบรวมแหล่งที่มา ค่อนข้างเหมือนCollections.checkedListแต่สามารถเริ่มต้นด้วยคอลเลคชันได้
oxbow_lakes

@tackline - ก็มีบางส่วนที่รั่วไหลน้อยลง หากคุณต้องการเข้าถึงประเภทข้อมูลเมตาในการนำไปใช้งานอินเทอร์เฟซภายนอกจะแจ้งให้คุณทราบเนื่องจากไคลเอ็นต์จำเป็นต้องส่งคลาสอ็อบเจ็กต์ให้คุณ
gustafc

... หมายความว่าด้วย reified generics คุณสามารถเพิ่มสิ่งต่างๆเช่นT.class.getAnnotation(MyAnnotation.class)( Tประเภททั่วไปอยู่ที่ไหน) โดยไม่ต้องเปลี่ยนอินเทอร์เฟซภายนอก
gustafc

@gustafc: ถ้าคุณคิดว่าเทมเพลต C ++ ให้ความปลอดภัยในการพิมพ์ที่สมบูรณ์โปรดอ่านสิ่งนี้: kdgregory.com/index.php?page=java.generics.cpp
kdgregory

@kdgregory: ฉันไม่เคยบอกว่า C ++ เป็นประเภทที่ปลอดภัย 100% - เพียงแค่นั้นการลบจะทำลายความปลอดภัยประเภท ในขณะที่คุณพูดกับตัวเองว่า "C ++ ปรากฎว่ามีรูปแบบการลบประเภทของตัวเองซึ่งเรียกว่าตัวชี้รูปแบบ C" แต่ Java จะทำการร่ายแบบไดนามิกเท่านั้น (ไม่ใช่การตีความซ้ำ) ดังนั้นการ reification จะเสียบทั้งหมดนี้ในระบบ type
gustafc

26

คุณจะสามารถสร้างอาร์เรย์ทั่วไปในโค้ดของคุณได้

public <T> static void DoStuff() {
    T[] myArray = new T[42]; // No can do
}

Object คืออะไร? อาร์เรย์ของออบเจ็กต์ก็คืออาร์เรย์ของการอ้างอิงอยู่ดี มันไม่เหมือนกับว่าข้อมูลออบเจ็กต์นั่งอยู่บนสแต็ก - ทั้งหมดอยู่ในฮีป
Ran Biron

19
ประเภทความปลอดภัย ฉันสามารถใส่อะไรก็ได้ที่ฉันต้องการใน Object [] แต่มีเพียง Strings ใน String [] เท่านั้น
ผลัด

3
Ran: โดยไม่ต้องเหน็บแนม: คุณอาจชอบใช้ภาษาสคริปต์แทน Java จากนั้นคุณจะมีความยืดหยุ่นของตัวแปรที่ไม่ได้พิมพ์ได้ทุกที่!
แกะบิน

2
อาร์เรย์เป็นโควาเรียร์ (และด้วยเหตุนี้จึงไม่ใช่ typesafe) ในทั้งสองภาษา String[] strings = new String[1]; Object[] objects = strings; objects[0] = new Object();รวบรวมได้ดีทั้งสองภาษา รัน notsofine
Martijn

16

นี่เป็นคำถามเก่ามีคำตอบมากมาย แต่ฉันคิดว่าคำตอบที่มีอยู่นั้นไม่เป็นที่สนใจ

"reified" หมายถึงจริงและมักจะหมายถึงสิ่งที่ตรงกันข้ามกับการลบประเภท

ปัญหาใหญ่ที่เกี่ยวข้องกับ Java Generics:

  • ข้อกำหนดการชกมวยที่น่ากลัวนี้และตัดการเชื่อมต่อระหว่างประเภทดั้งเดิมและประเภทอ้างอิง สิ่งนี้ไม่เกี่ยวข้องโดยตรงกับการแก้ไขซ้ำหรือการลบประเภท C # / Scala แก้ไขสิ่งนี้
  • ไม่มีประเภทตนเอง JavaFX 8 ต้องลบ "ตัวสร้าง" ด้วยเหตุนี้ ไม่มีอะไรเกี่ยวข้องกับการลบประเภท Scala แก้ไขสิ่งนี้ไม่แน่ใจเกี่ยวกับ C #
  • ไม่มีความแปรปรวนประเภทด้านการประกาศ C # 4.0 / Scala มีสิ่งนี้ ไม่มีอะไรเกี่ยวข้องกับการลบประเภท
  • ไม่สามารถโอเวอร์โหลดvoid method(List<A> l)และmethod(List<B> l). เนื่องจากการลบประเภท แต่มีน้อยมาก
  • ไม่รองรับการสะท้อนประเภทรันไทม์ นี่คือหัวใจของการลบประเภท หากคุณชอบคอมไพเลอร์ขั้นสูงที่ตรวจสอบและพิสูจน์ตรรกะของโปรแกรมของคุณได้มากที่สุดในเวลาคอมไพล์คุณควรใช้การสะท้อนให้น้อยที่สุดและการลบประเภทนี้ไม่ควรรบกวนคุณ หากคุณชอบการเขียนโปรแกรมประเภทไดนามิกที่ไม่เป็นระเบียบและเป็นระเบียบมากขึ้นและไม่สนใจมากนักเกี่ยวกับคอมไพเลอร์ที่พิสูจน์ว่าตรรกะของคุณถูกต้องมากที่สุดเท่าที่จะเป็นไปได้คุณต้องการการสะท้อนที่ดีขึ้นและการแก้ไขการลบประเภทเป็นสิ่งสำคัญ

1
ส่วนใหญ่ฉันพบว่ามันยากสำหรับกรณีการทำให้เป็นอนุกรม คุณมักจะต้องการที่จะแยกแยะประเภทของสิ่งทั่วไปที่ได้รับการต่ออนุกรมกัน แต่คุณหยุดสั้นเพราะการลบประเภท มันทำให้ยากที่จะทำอะไรแบบนี้deserialize(thingy, List<Integer>.class)
Cogman

3
ผมคิดว่านี่คือคำตอบที่ดีที่สุด โดยเฉพาะส่วนที่อธิบายถึงปัญหาที่เกิดขึ้นจริงเนื่องจากการลบประเภทและสิ่งที่เป็นเพียงปัญหาการออกแบบภาษา Java สิ่งแรกที่ฉันบอกผู้คนที่เริ่มลบเสียงหอนคือ Scala และ Haskell กำลังทำงานตามประเภทการลบด้วยเช่นกัน
aemxdp

13

การทำให้เป็นอนุกรมจะตรงไปตรงมามากขึ้นด้วยการทำให้ใหม่ สิ่งที่เราต้องการคือ

deserialize(thingy, List<Integer>.class);

สิ่งที่เราต้องทำคือ

deserialize(thing, new TypeReference<List<Integer>>(){});

ดูน่าเกลียดและทำงานอย่างสนุกสนาน

นอกจากนี้ยังมีบางกรณีที่จะช่วยได้มากเช่น

public <T> void doThings(List<T> thingy) {
    if (T instanceof Q)
      doCrazyness();
  }

สิ่งเหล่านี้ไม่ได้กัดบ่อย แต่จะกัดเมื่อเกิดขึ้น


นี้. เป็นพันเท่านี้ ทุกครั้งที่ฉันพยายามเขียนโค้ด deserialization ใน Java ฉันใช้เวลาทั้งวันคร่ำครวญว่าฉันไม่ได้ทำงานอย่างอื่น
พื้นฐาน

11

การเปิดรับ Java Geneircs ของฉันค่อนข้าง จำกัด และนอกเหนือจากประเด็นที่คำตอบอื่น ๆ ได้กล่าวไปแล้วยังมีสถานการณ์ที่อธิบายไว้ในหนังสือJava Generics and Collectionsโดย Maurice Naftalin และ Philip Walder ซึ่ง generics reified มีประโยชน์

เนื่องจากประเภทต่างๆไม่สามารถ reifiable จึงไม่สามารถมีข้อยกเว้นที่กำหนดพารามิเตอร์ได้

ตัวอย่างเช่นการประกาศแบบฟอร์มด้านล่างไม่ถูกต้อง

class ParametericException<T> extends Exception // compile error

เนื่องจากคำสั่งcatchตรวจสอบว่าข้อยกเว้นที่โยนตรงกับประเภทที่กำหนดหรือไม่ การตรวจสอบนี้เหมือนกับการตรวจสอบที่ดำเนินการโดยการทดสอบอินสแตนซ์และเนื่องจากประเภทไม่สามารถแก้ไขซ้ำได้รูปแบบของคำสั่งข้างต้นจึงไม่ถูกต้อง

หากรหัสข้างต้นถูกต้องการจัดการข้อยกเว้นในลักษณะด้านล่างจะเป็นไปได้:

try {
     throw new ParametericException<Integer>(42);
} catch (ParametericException<Integer> e) { // compile error
  ...
}

หนังสือเล่มนี้ยังระบุด้วยว่าหากมีการกำหนด Java generics คล้ายกับที่กำหนดแม่แบบ C ++ (ส่วนขยาย) อาจนำไปสู่การใช้งานที่มีประสิทธิภาพมากขึ้นเนื่องจากมีโอกาสมากขึ้นในการเพิ่มประสิทธิภาพ แต่ไม่ได้ให้คำอธิบายใด ๆ มากไปกว่านี้ดังนั้นคำอธิบาย (คำชี้แนะ) จากผู้รู้จะเป็นประโยชน์


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

@oxbow_lakes: ขออภัยความรู้เกี่ยวกับ Java Generics ของฉันมีค่อนข้าง จำกัด และฉันกำลังพยายามปรับปรุงอยู่ ดังนั้นตอนนี้ฉันไม่สามารถนึกถึงตัวอย่างใด ๆ ที่ข้อยกเว้นพาราเมตริกจะมีประโยชน์ จะลองคิดดูนะครับ. ขอบคุณ.
ผ้าปูโต๊ะ

สามารถทำหน้าที่แทนการสืบทอดประเภทข้อยกเว้นหลายประเภท
ทำบุญ

การปรับปรุงประสิทธิภาพคือในปัจจุบันพารามิเตอร์ type ต้องสืบทอดจาก Object โดยต้องใช้ Boxing ของประเภทดั้งเดิมซึ่งกำหนดค่าใช้จ่ายในการดำเนินการและหน่วยความจำ
ทำบุญ

8

อาร์เรย์อาจเล่นได้ดีกว่ากับ generics มากหากมีการ reified


2
แน่นอน แต่พวกเขาจะยังคงมีปัญหา List<String>ไม่ใช่List<Object>.
Tom Hawtin - แท

เห็นด้วย - แต่สำหรับไพรมารีเท่านั้น (จำนวนเต็มยาว ฯลฯ ) สำหรับวัตถุ "ปกติ" นี่ก็เหมือนกัน เนื่องจากดึกดำบรรพ์ไม่สามารถเป็นประเภทที่กำหนดพารามิเตอร์ได้ (ปัญหาที่ร้ายแรงกว่านั้นอย่างน้อยก็ IMHO) ฉันจึงไม่เห็นว่านี่เป็นความเจ็บปวดอย่างแท้จริง
Ran Biron

3
ปัญหาเกี่ยวกับอาร์เรย์คือความแปรปรวนร่วมไม่เกี่ยวข้องกับการทำให้เป็นใหม่
recurse

5

ฉันมีกระดาษห่อหุ้มที่นำเสนอผลลัพธ์ jdbc เป็นตัววนซ้ำ (หมายความว่าฉันสามารถทดสอบการดำเนินการที่มาจากฐานข้อมูลได้ง่ายขึ้นมากผ่านการฉีดขึ้นต่อกัน)

API ดูเหมือน Iterator<T>ว่า T คือบางประเภทที่สามารถสร้างได้โดยใช้สตริงในตัวสร้างเท่านั้น จากนั้น Iterator จะดูสตริงที่ถูกส่งคืนจากเคียวรี sql จากนั้นพยายามจับคู่กับตัวสร้างประเภท T

ในปัจจุบันวิธีการใช้ generics ฉันต้องผ่านคลาสของวัตถุที่ฉันจะสร้างจากชุดผลลัพธ์ของฉันด้วย ถ้าฉันเข้าใจถูกต้องหากมีการ reified generics ฉันสามารถเรียก T.getClass () เพื่อรับตัวสร้างจากนั้นไม่ต้องส่งผลลัพธ์ของ Class.newInstance () ซึ่งจะดูดีกว่า

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


แต่การผ่านชั้นเรียนดูเหมือนจะปลอดภัยกว่าสำหรับฉัน ไม่ว่าในกรณีใดการสร้างอินสแตนซ์อย่างสะท้อนจากการสืบค้นฐานข้อมูลนั้นมีความเปราะบางอย่างมากต่อการเปลี่ยนแปลงเช่นการปรับโครงสร้างใหม่ (ทั้งในรหัสและฐานข้อมูลของคุณ) ฉันเดาว่าฉันกำลังมองหาสิ่งต่าง ๆ ที่มันเป็นไปไม่ได้ที่จะให้ชั้นเรียน
oxbow_lakes

5

สิ่งที่ดีอย่างหนึ่งคือการหลีกเลี่ยงการชกมวยสำหรับประเภทดั้งเดิม (มูลค่า) สิ่งนี้ค่อนข้างเกี่ยวข้องกับการร้องเรียนอาร์เรย์ที่ผู้อื่นยกขึ้นมาและในกรณีที่มีข้อ จำกัด ในการใช้หน่วยความจำอาจสร้างความแตกต่างอย่างมีนัยสำคัญได้

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


2
โดยพื้นฐานแล้วสิ่งนี้ทำให้สามารถทำได้new T[]โดยที่ T เป็นประเภทดั้งเดิม!
oxbow_lakes

2

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

ความเห็นของฉันคือยาชื่อสามัญเป็นเพียงสิ่งพิเศษที่ช่วยประหยัดการหล่อซ้ำซ้อนได้มาก


นี่คือสิ่งที่แน่นอน generics อยู่ในชวา ในภาษา C # และภาษาอื่น ๆ เป็นเครื่องมือที่ทรงพลัง
ขั้นพื้นฐาน

1

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

    public interface Service<KEY,VALUE> {
           VALUE get(KEY key);
    }

    public class PersonService implements Service<Long, Person>,
        Service<String, Person> //Can not do!!

0

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

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

(หมายเหตุ: ฉันอาจทำผิดพลาดที่นี่ แต่ googling เกี่ยวกับ "generics varargs" ข้างต้นดูเหมือนจะเป็นสิ่งที่คุณคาดหวังสิ่งที่ทำให้ปัญหานี้เป็นปัญหาในทางปฏิบัติคือการใช้ Class ฉันคิดว่าผู้โทรดูเหมือน ระวังน้อยลง :()


ตัวอย่างเช่นฉันใช้กระบวนทัศน์ที่ใช้คลาสอ็อบเจกต์เป็นกุญแจสำคัญในแผนที่ (มันซับซ้อนกว่าแผนที่ธรรมดา - แต่ในแนวความคิดนั่นคือสิ่งที่เกิดขึ้น)

เช่นใช้งานได้ดีใน Java Generics (ตัวอย่างเล็กน้อย):

public <T extends Component> Set<UUID> getEntitiesPossessingComponent( Class<T> componentType)
    {
        // find the entities that are mapped (somehow) from that class. Very type-safe
    }

เช่นโดยไม่ต้อง reification ใน Java Generics อันนี้ยอมรับอ็อบเจ็กต์ "Class" ใด ๆ และเป็นเพียงส่วนขยายเล็ก ๆ ของรหัสก่อนหน้านี้:

public <T extends Component> Set<UUID> getEntitiesPossessingComponents( Class<T>... componentType )
    {
        // find the entities that are mapped (somehow) to ALL of those classes
    }

วิธีการข้างต้นจะต้องเขียนหลายพันครั้งในแต่ละโครงการดังนั้นความเป็นไปได้ที่จะเกิดข้อผิดพลาดจากมนุษย์จึงมีสูง การแก้ไขข้อผิดพลาดเป็นการพิสูจน์ว่า "ไม่สนุก" ตอนนี้ฉันกำลังพยายามหาทางเลือกอื่น แต่อย่าตั้งความหวังไว้มาก

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