อะไรคือสาเหตุที่ทำให้ Map.get (ปุ่มวัตถุ) ไม่ได้ (เต็ม) ทั่วไป


405

java.util.Map<K, V>อะไรคือเหตุผลที่อยู่เบื้องหลังการตัดสินใจที่จะไม่ได้เป็นวิธีการที่ได้รับทั่วไปอย่างเต็มที่ในอินเตอร์เฟซของ

เพื่อชี้แจงคำถามลายเซ็นของวิธีการคือ

V get(Object key)

แทน

V get(K key)

และฉันสงสัยว่าทำไม (แบบเดียวกันสำหรับremove, containsKey, containsValue)


3
คำถามที่คล้ายกันเกี่ยวกับคอลเลกชัน: stackoverflow.com/questions/104799/…
AlikElzin-kilaka


1
น่าอัศจรรย์ ฉันใช้งานจาวาตั้งแต่ 20 ปีขึ้นไปและวันนี้ฉันเข้าใจปัญหานี้แล้ว
GhostCat

คำตอบ:


260

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

แม้ว่ามันอาจจะเป็นความจริงโดยทั่วไปที่คลาสจำนวนมากได้equals()กำหนดไว้เพื่อให้วัตถุสามารถเท่ากับวัตถุของคลาสของตัวเองเท่านั้น ยกตัวอย่างเช่นข้อกำหนดสำหรับบอกว่าวัตถุทั้งสองรายการมีค่าเท่ากันถ้าหากพวกเขาทั้งสองรายการและมีเนื้อหาเดียวกันแม้ว่าพวกเขาจะใช้งานที่แตกต่างกันของList.equals() Listดังนั้นกลับไปตัวอย่างในคำถามนี้มาตามข้อกำหนดของวิธีการที่เป็นไปได้ที่จะมีMap<ArrayList, Something>และสำหรับฉันที่จะเรียกget()ด้วยLinkedListเป็นอาร์กิวเมนต์และควรดึงกุญแจสำคัญซึ่งเป็นรายการที่มีเนื้อหาเดียวกัน สิ่งนี้จะเป็นไปไม่ได้หากget()เป็นประเภททั่วไปและ จำกัด ประเภทอาร์กิวเมนต์


28
แล้วทำไมV Get(K k)ใน C #

134
คำถามก็คือถ้าคุณต้องการที่จะเรียกm.get(linkedList)ทำไมคุณไม่กำหนดm's ประเภทเป็นMap<List,Something>? ฉันไม่สามารถนึกถึง usecase ที่โทรได้m.get(HappensToBeEqual)โดยไม่ต้องเปลี่ยนMapประเภทเพื่อให้ได้ส่วนต่อประสานที่เหมาะสม
Elazar Leibovich

58
ว้าวข้อบกพร่องการออกแบบที่จริงจัง คุณจะไม่ได้รับคำเตือนของคอมไพเลอร์ แต่อย่างใด ฉันเห็นด้วยกับ Elazar ถ้านี่เป็นประโยชน์จริงๆซึ่งผมมีข้อสงสัยเกิดขึ้นบ่อย getByEquals (คีย์ Object) เสียงที่เหมาะสมมากขึ้น ...
mmm

37
การตัดสินใจครั้งนี้ดูเหมือนว่ามันถูกสร้างขึ้นบนพื้นฐานของความบริสุทธิ์ทางทฤษฎีมากกว่าการปฏิบัติจริง สำหรับประเพณีส่วนใหญ่นักพัฒนาค่อนข้างจะเห็นอาร์กิวเมนต์ที่ จำกัด โดยประเภทเทมเพลตมากกว่าที่จะให้มันไม่ จำกัด เพื่อรองรับกรณีขอบเช่นเดียวกับที่กล่าวถึงโดย newacct ในคำตอบของเขา การออกจากลายเซ็นที่ไม่ใช่เท็มเพลตทำให้เกิดปัญหามากกว่าที่จะแก้
Sam Goldberg

14
@newacct:“ การพิมพ์อย่างสมบูรณ์แบบปลอดภัย” เป็นข้อเรียกร้องที่แข็งแกร่งสำหรับสิ่งก่อสร้างที่อาจล้มเหลวได้ในขณะใช้งานจริง อย่า จำกัด มุมมองของคุณให้แคบลงเพื่อแฮชแผนที่ที่ทำงานกับมันได้ TreeMapอาจล้มเหลวเมื่อคุณส่งวัตถุชนิดผิดไปยังgetวิธีการ แต่อาจผ่านเป็นครั้งคราวเช่นเมื่อแผนที่ว่างเปล่า และยิ่งเลวร้ายลงในกรณีที่มีการจัดมาวิธี (ซึ่งมีลายเซ็นทั่วไป!) อาจจะเรียกว่ามีข้อโต้แย้งของผิดประเภทโดยไม่มีการเตือนใด ๆ ที่ไม่ถูกตรวจสอบ นี่คือพฤติกรรมที่ใช้งานไม่ได้ Comparatorcompare
Holger

105

Kevin Bourrillion ผู้เขียนโค้ด Java ที่ยอดเยี่ยมที่ Google เขียนเกี่ยวกับปัญหานี้ในบล็อกโพสต์เมื่อไม่นานมานี้ (ยอมรับในบริบทSetแทนที่จะเป็นMap) ประโยคที่เกี่ยวข้องมากที่สุด:

วิธีการอย่างสม่ำเสมอของ Java Collections Framework (และ Google Collections Library ด้วย) ไม่ จำกัด ประเภทของพารามิเตอร์ยกเว้นเมื่อจำเป็นเพื่อป้องกันไม่ให้คอลเลกชันแตก

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


4
Apocalisp: นั่นไม่จริงสถานการณ์ยังคงเหมือนเดิม
Kevin Bourrillion

9
@ user102008 ไม่โพสต์ไม่ผิด แม้ว่าIntegera และ a Doubleจะไม่สามารถเทียบเท่ากันได้ แต่ก็ยังเป็นคำถามที่ยุติธรรมที่จะถามว่า a Set<? extends Number>มีค่าnew Integer(5)หรือไม่
Kevin Bourrillion

33
Set<? extends Foo>ฉันไม่เคยอยากครั้งเพื่อตรวจสอบการเป็นสมาชิกใน ฉันได้เปลี่ยนประเภทคีย์ของแผนที่บ่อยมากและรู้สึกหงุดหงิดที่คอมไพเลอร์ไม่สามารถค้นหาสถานที่ทั้งหมดที่จำเป็นต้องใช้รหัสในการอัปเดต ฉันไม่เชื่อจริง ๆ ว่านี่เป็นการแลกเปลี่ยนที่ถูกต้อง
Porculus

4
@ EarthEngine: มันถูกทำลายอยู่เสมอ นั่นคือจุดรวม - รหัสเสีย แต่คอมไพเลอร์ไม่สามารถจับได้
Jon Skeet

1
และมันก็ยังพังทลายและทำให้เราเป็นบั๊ก ... คำตอบที่ยอดเยี่ยม
GhostCat

28

สัญญาจึงแสดงออกมาดังนี้

หากแผนที่นี้มีการแมปจากคีย์ k ไปเป็นค่า v เช่นนั้น (key == null? k == null: key.equals (k) ) ดังนั้นเมธอดนี้จะส่งคืน v; มิฉะนั้นจะส่งคืน null (สามารถมีการแมปได้ไม่เกินหนึ่งรายการ)

(ความสำคัญของฉัน)

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


4
hashCode()นอกจากนี้ยังขึ้นอยู่กับ หากไม่มีการใช้งานที่เหมาะสมของ hashCode () การใช้งานอย่างดีequals()นั้นค่อนข้างไร้ประโยชน์ในกรณีนี้
rudolfson

5
โดยทั่วไปฉันคิดว่านี่จะช่วยให้คุณใช้ proxy ที่มีน้ำหนักเบาสำหรับคีย์ได้หากการสร้างคีย์ทั้งหมดนั้นไม่สามารถทำได้ - ตราบใดที่เท่ากับ () และ hashCode () นั้นถูกนำไปใช้อย่างถูกต้อง
Bill Michell

5
@rudolfson: เท่าที่ฉันรู้มีเพียง HashMap เท่านั้นที่พึ่งพารหัสแฮชเพื่อค้นหาที่เก็บข้อมูลที่ถูกต้อง ตัวอย่างเช่น TreeMap ใช้แผนผังการค้นหาแบบไบนารีและไม่สนใจ hashCode ()
Rob

4
พูดอย่างเคร่งครัดget()ไม่จำเป็นต้องโต้แย้งประเภทObjectเพื่อตอบสนองการติดต่อ ลองนึกภาพวิธีการรับถูก จำกัด ประเภทที่สำคัญK- สัญญาจะยังคงใช้ได้ แน่นอนว่าการใช้งานที่ประเภทเวลาคอมไพล์ไม่ใช่คลาสย่อยของKตอนนี้จะไม่สามารถคอมไพล์ได้ แต่นั่นไม่ได้ทำให้สัญญาหมดอายุเนื่องจากสัญญาโดยปริยายอภิปรายว่าเกิดอะไรขึ้นหากโค้ดคอมไพล์
BeeOnRope

16

มันเป็นกฎของ Postel ที่ว่า "จงระมัดระวังในสิ่งที่คุณทำมีอิสระในสิ่งที่คุณยอมรับจากผู้อื่น"

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

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


4
แล้วทำไมV Get(K k)ใน C #

1
มันV Get(K k)อยู่ใน C # เพราะมันสมเหตุสมผลแล้ว ความแตกต่างระหว่างแนวทาง Java และ. NET เป็นเพียงผู้บล็อกสิ่งที่ไม่ตรงกัน ใน C # เป็นคอมไพเลอร์ใน Java เป็นคอลเลกชัน ฉันโกรธเกี่ยวกับชั้นเรียนคอลเลกชันที่ไม่สอดคล้องกันของ. NET นาน ๆ ครั้ง แต่Get()และการRemove()ยอมรับประเภทการจับคู่จะป้องกันคุณจากการส่งค่าที่ไม่ถูกต้องโดยไม่ตั้งใจ
Wormbo

26
มันเป็นการใช้ผิดกฎของ Postel เป็นคนใจกว้างในสิ่งที่คุณยอมรับจากคนอื่น แต่อย่าเสรีนิยมมากเกินไป API ที่งี่เง่านี้หมายความว่าคุณไม่สามารถบอกความแตกต่างระหว่าง "ไม่ได้อยู่ในคอลเลกชัน" และ "คุณพิมพ์ผิดแบบคงที่" โปรแกรมเมอร์ที่สูญหายหลายพันชั่วโมงสามารถป้องกันได้ด้วยการรับ: K -> บูลีน
ผู้พิพากษาจิต

1
contains : K -> booleanแน่นอนว่าควรจะได้รับ
ผู้พิพากษาจิต

4
พอเป็นเรื่องที่ผิด
Alnitak

13

ฉันคิดว่าหัวข้อ Generics Tutorial นี้อธิบายถึงสถานการณ์ (สิ่งที่ฉันให้ความสำคัญ):

"คุณต้องตรวจสอบให้แน่ใจว่า API ทั่วไปไม่ได้ถูก จำกัด อย่างเกินจริงมันต้องดำเนินการต่อเพื่อสนับสนุนสัญญาเดิมของ API ลองพิจารณาตัวอย่างจาก java.util.Collection อีกครั้ง API ก่อนทั่วไปดูเหมือนว่า:

interface Collection { 
  public boolean containsAll(Collection c);
  ...
}

ความพยายามที่ไร้เดียงสาในการสร้างมันคือ:

interface Collection<E> { 
  public boolean containsAll(Collection<E> c);
  ...
}

แม้ว่าวิธีนี้จะปลอดภัย แต่ก็ไม่เป็นไปตามสัญญาเดิมของ API เมธอด containAll () ทำงานกับการรวบรวมขาเข้าชนิดใดก็ได้ มันจะประสบความสำเร็จก็ต่อเมื่อคอลเลกชันที่เข้ามามีจริงๆของ E เท่านั้น แต่:

  • ประเภทคงที่ของคอลเลกชันที่เข้ามาอาจแตกต่างกันอาจเป็นเพราะผู้โทรไม่ทราบประเภทที่ถูกต้องของคอลเลกชันที่ถูกส่งผ่านหรืออาจเป็นเพราะมันเป็นคอลเลกชัน <S> ที่ S เป็นชนิดย่อยของ E
  • มันถูกต้องตามกฎหมายอย่างสมบูรณ์แบบในการโทรที่มีAllAll () ด้วยชุดของประเภทที่แตกต่างกัน รูทีนควรทำงานส่งคืนค่าเท็จ "

2
ทำไมไม่เป็นcontainsAll( Collection< ? extends E > c )เช่นนั้น
ผู้พิพากษาจิต

1
@JudgeMental แม้ว่าจะไม่ได้รับเป็นตัวอย่างข้างต้นก็ยังเป็นสิ่งจำเป็นที่จะช่วยให้containsAllมีCollection<S>ที่Sเป็นsupertypeEของ containsAll( Collection< ? extends E > c )นี้จะไม่ได้รับอนุญาตว่ามันเป็น นอกจากเป็นจะระบุไว้อย่างชัดเจนในตัวอย่างก็ถูกต้องตามกฎหมายที่จะผ่านคอลเลกชันของรูปแบบที่แตกต่างกัน (มีค่าตอบแทนแล้วจะfalse)
davmac

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

6

เหตุผลคือการ จำกัด จะถูกกำหนดโดยequalsและhashCodeซึ่งเป็นวิธีการObjectและทั้งสองใช้Objectพารามิเตอร์ นี่เป็นข้อบกพร่องในช่วงต้นของการออกแบบในไลบรารีมาตรฐานของ Java ควบคู่ไปกับข้อ จำกัด ในระบบการพิมพ์ของ Java มันบังคับอะไรที่อาศัยเท่าเทียมและ hashCode Objectที่จะใช้

วิธีเดียวที่จะมีประเภทปลอดภัยตารางแฮชและความเท่าเทียมกันใน Java คือการหลบหนีObject.equalsและObject.hashCodeและใช้แทนทั่วไป ฟังก์ชั่น Javaมาพร้อมกับการเรียนประเภทเพียงจุดประสงค์นี้: และHash<A> Equal<A>เสื้อคลุมสำหรับHashMap<K, V>ให้ไว้ที่ใช้Hash<K>และEqual<K>ในตัวสร้างของมัน คลาสนี้getและcontainsเมธอดจึงรับอาร์กิวเมนต์ชนิดKทั่วไป

ตัวอย่าง:

HashMap<String, Integer> h =
  new HashMap<String, Integer>(Equal.stringEqual, Hash.stringHash);

h.add("one", 1);

h.get("one"); // All good

h.get(Integer.valueOf(1)); // Compiler error

4
สิ่งนี้ในตัวมันเองไม่ได้ป้องกันประเภทของ 'รับ' จากการถูกประกาศเป็น "V get (คีย์ K)" เพราะ 'Object' นั้นเป็นบรรพบุรุษของ K เสมอดังนั้น "key.hashCode ()" จะยังคงใช้ได้
finnw

1
ในขณะที่มันไม่ได้ป้องกันฉันคิดว่ามันอธิบายได้ หากพวกเขาเปลี่ยนวิธีการเท่ากับเพื่อบังคับให้เกิดความเท่าเทียมกันในชั้นเรียนแน่นอนพวกเขาไม่สามารถบอกผู้คนได้ว่ากลไกพื้นฐานสำหรับการค้นหาวัตถุในแผนที่นั้นใช้ equals () และ hashmap () เมื่อต้นแบบต้นแบบของวิธีการเหล่านั้นเข้ากันไม่ได้
cgp

5

ความเข้ากันได้

ก่อนที่จะมียาชื่อสามัญมีเพียงรับ (Object o)

หากพวกเขาเปลี่ยนวิธีการนี้เพื่อรับ (<K> o) มันอาจบังคับให้มีการบำรุงรักษาโค้ดจำนวนมากไปยังผู้ใช้ java เพียงเพื่อให้คอมไพล์โค้ดทำงานได้อีกครั้ง

พวกเขาอาจได้แนะนำวิธีเพิ่มเติมให้พูดว่า get_checked (<K> o) และเลิกใช้วิธีการ get () แบบเก่าดังนั้นจึงมีเส้นทางการเปลี่ยนที่อ่อนโยนกว่า แต่ด้วยเหตุผลบางอย่างนี่ไม่ได้ทำ (สถานการณ์ที่เราอยู่ในตอนนี้คือคุณต้องติดตั้งเครื่องมือเช่น findBugs เพื่อตรวจสอบความเข้ากันได้ของประเภทระหว่างอาร์กิวเมนต์ get () และประเภทคีย์ที่ประกาศไว้ <K> ของแผนที่)

ข้อโต้แย้งที่เกี่ยวข้องกับความหมายของ. equals () นั้นเป็นการหลอกลวงฉันคิดว่า (ในทางเทคนิคพวกมันถูกต้อง แต่ฉันก็ยังคิดว่าพวกมันเป็นการหลอกลวงไม่มีนักออกแบบในใจที่เหมาะสมของเขาที่จะทำให้ o1.equals (o2) เป็นจริงถ้า o1 และ o2 ไม่มี superclass ใด ๆ ร่วมกัน)


4

มีอีกเหตุผลหนึ่งที่หนักกว่าก็ไม่สามารถทำได้ในทางเทคนิคเพราะมันทำให้เกิดแผนที่

Java มีการก่อสร้างทั่วไป polymorphic <? extends SomeClass>เช่น <AnySubclassOfSomeClass>ทำเครื่องหมายอ้างอิงดังกล่าวสามารถชี้ไปที่พิมพ์ลงนามกับ แต่โพลีมอร์ฟิคทั่วไปทำให้การอ้างอิงนั้นเป็นแบบอ่านอย่างเดียว คอมไพเลอร์อนุญาตให้คุณใช้ประเภททั่วไปเฉพาะเป็นวิธีการส่งคืนประเภท (เช่น getters แบบง่าย) แต่บล็อกการใช้วิธีการที่ประเภททั่วไปเป็นอาร์กิวเมนต์ (เช่น setters สามัญ) หมายความว่าถ้าคุณเขียนMap<? extends KeyType, ValueType>คอมไพเลอร์ไม่อนุญาตให้คุณเรียกวิธีการget(<? extends KeyType>)และแผนที่จะไร้ประโยชน์ get(Object)ทางออกเดียวที่จะทำให้วิธีการนี้ไม่ทั่วไป:


ทำไมวิธีการตั้งค่าจึงพิมพ์อย่างยิ่งแล้ว
Sentenza

ถ้าคุณหมายถึง 'put': The put () วิธีการเปลี่ยนแปลงแผนที่และมันจะไม่สามารถใช้ได้กับ generics เช่น <? ขยาย SomeClass> ถ้าคุณเรียกมันว่าคุณได้รับการรวบรวม แผนที่ดังกล่าวจะเป็น "แบบอ่านอย่างเดียว"
Owheee

1

เข้ากันได้ย้อนหลังฉันเดา Map(หรือHashMap) get(Object)ยังคงต้องสนับสนุน


13
แต่สามารถสร้างอาร์กิวเมนต์เดียวกันสำหรับput(ซึ่ง จำกัด ประเภททั่วไป) คุณได้รับความเข้ากันได้ย้อนหลังโดยใช้ชนิด raw ยาชื่อสามัญคือ "การเลือกใช้"
Thilo

โดยส่วนตัวแล้วฉันคิดว่าเหตุผลที่เป็นไปได้มากที่สุดสำหรับการตัดสินใจออกแบบนี้คือความเข้ากันได้แบบย้อนหลัง
geekdenz

1

ฉันดูที่นี่และคิดว่าทำไมพวกเขาถึงทำแบบนี้ ฉันไม่คิดว่าคำตอบใด ๆ ที่มีอยู่อธิบายว่าทำไมพวกเขาไม่สามารถสร้างอินเทอร์เฟซทั่วไปใหม่ให้ยอมรับเฉพาะคีย์ที่เหมาะสมเท่านั้น เหตุผลที่แท้จริงคือแม้ว่าพวกเขาจะแนะนำ generics พวกเขาไม่ได้สร้างอินเทอร์เฟซใหม่ อินเทอร์เฟซของแผนที่เป็นแผนที่ที่ไม่ใช่แบบทั่วไปแบบเดิมที่ทำหน้าที่เป็นทั้งแบบทั่วไปและไม่ใช่แบบทั่วไป วิธีนี้หากคุณมีวิธีการที่ยอมรับแผนที่ที่ไม่ใช่แบบทั่วไปคุณสามารถผ่านมันได้Map<String, Customer>และมันก็ยังใช้ได้ ในขณะเดียวกันสัญญารับวัตถุก็รับดังนั้นอินเทอร์เฟซใหม่ควรสนับสนุนสัญญานี้ด้วย

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


0

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

แต่ฉันพบเคล็ดลับการแก้ปัญหาชั่วคราว / น่าเกลียดสำหรับการตรวจสอบเวลาในการรวบรวม: สร้างส่วนต่อประสานแผนที่ด้วยการพิมพ์ขออย่างยิ่ง, มี, บรรจุ, ลบ ... และนำไปใช้กับแพ็คเกจ java.util ของโครงการของคุณ

คุณจะได้รับข้อผิดพลาดในการคอมไพล์เพียงสำหรับการโทร get (), ... ด้วยประเภทที่ไม่ถูกต้องทุกอย่างอื่นที่ดูเหมือนว่าตกลงสำหรับคอมไพเลอร์ (อย่างน้อยภายใน eclipse kepler)

อย่าลืมลบอินเตอร์เฟสนี้หลังจากตรวจสอบบิลด์ของคุณเนื่องจากนี่ไม่ใช่สิ่งที่คุณต้องการในรันไทม์

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