วิธีการสร้างอาร์เรย์ทั่วไปใน Java?


1090

เนื่องจากการใช้งานจาวา generics คุณไม่สามารถมีรหัสเช่นนี้:

public class GenSet<E> {
    private E a[];

    public GenSet() {
        a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation
    }
}

ฉันจะใช้สิ่งนี้ในขณะที่รักษาความปลอดภัยประเภทได้อย่างไร

ฉันเห็นวิธีแก้ไขปัญหาในฟอรัม Java ที่เป็นเช่นนี้:

import java.lang.reflect.Array;

class Stack<T> {
    public Stack(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    private final T[] array;
}

แต่ฉันไม่เข้าใจว่าเกิดอะไรขึ้น


14
คุณจำเป็นต้องใช้อาเรย์ที่นี่จริงเหรอ? แล้วการใช้คอลเล็กชันล่ะ?
matt b

12
ใช่ฉันคิดว่าคอลเล็กชั่นนั้นสง่างามมากสำหรับปัญหานี้ แต่นี่สำหรับการมอบหมายชั้นเรียนและพวกเขาจำเป็นต้องใช้ :(
tatsuhirosatou

3
ฉันไม่เข้าใจว่าทำไมฉันจึงต้องมีการสะท้อนที่นี่ไวยากรณ์ของ Java นั้นแปลก: เช่น java.util.HashMap ใหม่ <String, String> [10] ไม่ถูกต้อง ใหม่ java.util.HashMap <ยาวยาว> (10) ไม่ถูกต้อง ใหม่ยาว [] [10] ไม่ถูกต้องยาวใหม่ [10] [] ถูกต้อง สิ่งที่ทำให้การเขียนโปรแกรมที่สามารถเขียนโปรแกรม java นั้นยากขึ้นแล้วดูเหมือนว่า
ชายทองสัมฤทธิ์

คำตอบ:


703

ฉันต้องถามคำถามคืน: GenSet"ตรวจสอบ" ของคุณหรือ "ไม่ถูกตรวจสอบ" หรือไม่? นั่นหมายความว่าอย่างไร?

  • การตรวจสอบ : พิมพ์ strong GenSetรู้อย่างชัดเจนว่าสิ่งที่ประเภทของวัตถุมันมี (เช่นคอนสตรัคถูกอย่างชัดเจนเรียกว่ามีClass<E>ข้อโต้แย้งและวิธีการจะโยนยกเว้นเมื่อพวกเขาจะถูกส่งผ่านข้อโต้แย้งที่ไม่ได้เป็นชนิดE. Collections.checkedCollectionดู

    -> ในกรณีนั้นคุณควรเขียน:

    public class GenSet<E> {
    
        private E[] a;
    
        public GenSet(Class<E> c, int s) {
            // Use Array native method to create array
            // of a type only known at run time
            @SuppressWarnings("unchecked")
            final E[] a = (E[]) Array.newInstance(c, s);
            this.a = a;
        }
    
        E get(int i) {
            return a[i];
        }
    }
    
  • ไม่ได้ตรวจสอบ : พิมพ์อ่อนแอ ไม่มีการตรวจสอบชนิดในวัตถุใด ๆ ที่ส่งผ่านเป็นอาร์กิวเมนต์จริง

    -> ในกรณีนั้นคุณควรเขียน

    public class GenSet<E> {
    
        private Object[] a;
    
        public GenSet(int s) {
            a = new Object[s];
        }
    
        E get(int i) {
            @SuppressWarnings("unchecked")
            final E e = (E) a[i];
            return e;
        }
    }
    

    โปรดทราบว่าประเภทองค์ประกอบของอาร์เรย์ควรจะลบพารามิเตอร์ประเภท:

    public class GenSet<E extends Foo> { // E has an upper bound of Foo
    
        private Foo[] a; // E erases to Foo, so use Foo[]
    
        public GenSet(int s) {
            a = new Foo[s];
        }
    
        ...
    }
    

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


7
ตัวเลือกประสิทธิภาพที่ดีที่สุดคืออะไร ฉันต้องได้รับองค์ประกอบจากอาร์เรย์นี้ค่อนข้างบ่อย (ภายในวง) ดังนั้นการสะสมอาจช้ากว่า แต่สองอันนี้เร็วที่สุด?
user1111929

3
และถ้าประเภททั่วไปถูกล้อมรอบอาร์เรย์สำรองควรเป็นประเภทที่มีขอบเขต
Mordechai

5
@AaronDigulla เพียงชี้แจงว่าไม่ได้กำหนด แต่เริ่มต้นของตัวแปรท้องถิ่น คุณไม่สามารถใส่คำอธิบาย / แสดงออก
kennytm

1
@Varkhan มีวิธีการปรับขนาดอาร์เรย์เหล่านี้จากภายในการนำไปใช้ในชั้นเรียนหรือไม่ ตัวอย่างเช่นถ้าฉันต้องการปรับขนาดหลังจากล้นเช่น ArrayList ฉันค้นหาการใช้ ArrayList ที่มีObject[] EMPTY_ELEMENTDATA = {}สำหรับการจัดเก็บ ฉันสามารถใช้กลไกนี้เพื่อปรับขนาดโดยไม่ทราบประเภทโดยใช้ข้อมูลทั่วไปได้หรือไม่
JourneyMan

2
สำหรับผู้ที่ต้องการทำวิธีที่มีประเภททั่วไป (ซึ่งเป็นสิ่งที่ฉันกำลังมองหา) ใช้สิ่งนี้:public void <T> T[] newArray(Class<T> type, int length) { ... }
Daniel Kvist

225

คุณสามารถทำได้:

E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];

นี่เป็นหนึ่งในวิธีที่แนะนำในการใช้งานคอลเลกชันทั่วไปในEffective Java รายการที่ 26 ไม่มีข้อผิดพลาดประเภทไม่จำเป็นต้องโยนอาร์เรย์ซ้ำ ๆ อย่างไรก็ตามสิ่งนี้ทริกเกอร์คำเตือนเพราะอาจเป็นอันตรายและควรใช้ด้วยความระมัดระวัง ตามรายละเอียดในความคิดเห็นนี่Object[]คือการปลอมตัวเป็นE[]ประเภทของเราและสามารถทำให้เกิดข้อผิดพลาดที่ไม่คาดคิดหรือClassCastExceptionถ้าใช้ไม่ปลอดภัย

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


คุณควรมีความสุขกับการทำงานกับLists มากกว่าอาร์เรย์ถ้าคุณใช้ยาสามัญ แน่นอนว่าบางครั้งคุณไม่มีทางเลือก แต่การใช้เฟรมเวิร์กคอลเล็กชันนั้นแข็งแกร่งกว่ามาก


47
สิ่งนี้จะไม่ทำงานหากอาเรย์นั้นถือว่าเป็นอาเรย์ที่พิมพ์ทุกชนิดเช่นString[] s=b;ในtest()วิธีการด้านบน นั่นเป็นเพราะอาร์เรย์ของ E ไม่ได้เป็นของจริงมันคือ Object [] เรื่องนี้ถ้าคุณต้องการเช่นList<String>[]- คุณไม่สามารถใช้Object[]สำหรับการที่คุณต้องมีList[]เฉพาะ เหตุใดคุณจึงต้องใช้การสร้างอาเรย์คลาส <?> ที่สะท้อนออกมา
Lawrence Dol

8
มุม / กรณีปัญหาคือถ้าคุณต้องการที่จะทำตัวอย่างเช่นpublic E[] toArray() { return (E[])internalArray.clone(); }เมื่อinternalArrayมีการพิมพ์เป็นและดังนั้นจึงเป็นเรื่องจริงE[] Object[]สิ่งนี้ล้มเหลวขณะรันไทม์ที่มีข้อยกเว้นการพิมพ์เนื่องจากObject[]ไม่สามารถกำหนดให้กับอาเรย์ของประเภทใดก็ตามที่Eเกิดขึ้น
Lawrence Dol

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

3
มันค่อนข้างปลอดภัย ในE[] b = (E[])new Object[1];คุณสามารถเห็นได้ชัดเจนว่าการอ้างอิงเท่านั้นยังอาร์เรย์ที่สร้างขึ้นbและว่าประเภทของการเป็นb E[]ดังนั้นจึงไม่มีอันตรายจากการที่คุณเข้าถึงอาร์เรย์เดียวกันโดยไม่ตั้งใจผ่านตัวแปรที่แตกต่างกันของประเภทที่แตกต่างกัน แต่ถ้าคุณได้แล้วคุณจะต้องหวาดระแวงเกี่ยวกับวิธีที่คุณใช้Object[] a = new Object[1]; E[]b = (E[])a; a
Aaron McDaid

5
อย่างน้อยใน Java 1.6 สิ่งนี้จะสร้างคำเตือน: "การไม่เลือกเครื่องหมายจากวัตถุ [] ถึง T []"
Quantum7

61

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

import java.lang.reflect.Array;  

public class GenSet<E> {  
    private E[] a;  

    public GenSet(Class<E[]> clazz, int length) {  
        a = clazz.cast(Array.newInstance(clazz.getComponentType(), length));  
    }  

    public static void main(String[] args) {  
        GenSet<String> foo = new GenSet<String>(String[].class, 1);  
        String[] bar = foo.a;  
        foo.a[0] = "xyzzy";  
        String baz = foo.a[0];  
    }  
}

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

มันทำงานโดยการใช้ตัวอักษรระดับเป็นชนิดรันไทม์ราชสกุลตามที่กล่าวไว้ในJava สอน java.lang.Classตัวอักษรระดับได้รับการรักษาโดยรวบรวมเป็นกรณีของ .classเพื่อการใช้งานอย่างใดอย่างหนึ่งเพียงทำตามชื่อของชั้นเรียนกับที่ ดังนั้นString.classทำหน้าที่เป็นวัตถุที่เป็นตัวแทนของชั้นเรียนClass Stringนอกจากนี้ยังใช้งานได้สำหรับอินเทอร์เฟซ enums อาร์เรย์ใด ๆ ในมิติ (เช่นString[].class) ดั้งเดิม (เช่นint.class) และคำหลักvoid(เช่นvoid.class)

Classตัวเองเป็นทั่วไป (ประกาศเป็นClass<T>ที่Tยืนสำหรับประเภทที่ว่าClassวัตถุที่เป็นตัวแทน) ซึ่งหมายความว่าประเภทของการมีString.classClass<String>

ดังนั้นเมื่อใดก็ตามที่คุณเรียกใช้นวกรรมิกGenSetคุณจะส่งผ่านตัวอักษรคลาสสำหรับอาร์กิวเมนต์แรกที่แทนอาร์เรย์ของGenSetชนิดที่ประกาศของอินสแตนซ์ (เช่นString[].classสำหรับGenSet<String>) โปรดทราบว่าคุณจะไม่สามารถรับชุดข้อมูลพื้นฐานได้เนื่องจากไม่สามารถใช้ชุดดั้งเดิมสำหรับตัวแปรประเภทได้

ภายใน Constructor การเรียกเมธอดcastส่งคืนObjectอาร์กิวเมนต์ที่ส่งผ่านไปยังคลาสที่แสดงโดยClassวัตถุที่เมธอดถูกเรียก เรียกวิธีการคงnewInstanceอยู่ในjava.lang.reflect.Arrayผลตอบแทนที่เป็นObjectอาร์เรย์ของประเภทที่แสดงโดยที่Classวัตถุผ่านเป็นอาร์กิวเมนต์แรกและระยะเวลาที่ระบุโดยintผ่านเป็นอาร์กิวเมนต์ที่สอง โทรวิธีการที่getComponentTypeผลตอบแทนClassวัตถุที่เป็นตัวแทนของประเภทองค์ประกอบของอาร์เรย์ตัวแทนจากClassวัตถุที่วิธีการที่เรียกว่า (เช่นString.classสำหรับString[].class, nullถ้าClassวัตถุไม่ได้เป็นตัวแทนอาร์เรย์)

ประโยคสุดท้ายนั้นไม่ถูกต้องทั้งหมด การโทรString[].class.getComponentType()ส่งคืนClassวัตถุที่เป็นตัวแทนของคลาสStringแต่ชนิดของมันคือClass<?>ไม่ใช่Class<String>ซึ่งเป็นสาเหตุที่คุณไม่สามารถทำสิ่งต่อไปนี้

String foo = String[].class.getComponentType().cast("bar"); // won't compile

กันไปสำหรับทุกวิธีในการClassที่ส่งกลับClassวัตถุ

เกี่ยวกับความคิดเห็นของโจอาคิมซาวเออร์เกี่ยวกับคำตอบนี้ (ฉันไม่มีชื่อเสียงพอที่จะให้ความเห็นด้วยตนเอง) ตัวอย่างที่ใช้การส่งT[]จะส่งผลให้เกิดการเตือนเนื่องจากคอมไพเลอร์ไม่สามารถรับประกันความปลอดภัยประเภทในกรณีนั้น


แก้ไขเกี่ยวกับความคิดเห็นของ Ingo:

public static <T> T[] newArray(Class<T[]> type, int size) {
   return type.cast(Array.newInstance(type.getComponentType(), size));
}

5
มันไม่มีประโยชน์มันเป็นเพียงวิธีที่ซับซ้อนในการเขียน String ใหม่ [... ] แต่สิ่งที่จำเป็นจริงๆก็คือสิ่งที่เหมือนสาธารณะคงที่ <T> T [] newArray (ขนาด int) {... } และสิ่งนี้ไม่มีอยู่ใน java noir ที่สามารถจำลองด้วยการสะท้อนได้ - เหตุผลก็คือข้อมูลเกี่ยวกับวิธีการ ประเภททั่วไปนั้นอินสแตนซ์ไม่พร้อมใช้งานในขณะใช้งานจริง
Ingo

4
@Ingo คุณกำลังพูดถึงอะไร? รหัสของฉันสามารถใช้สร้างอาร์เรย์ได้ทุกประเภท
gdejohn

3
@ Charlatan: แน่นอน แต่ใหม่ได้ [] คำถามคือ: ใครจะรู้ประเภทและเมื่อ ดังนั้นหากคุณมีประเภททั่วไปคุณก็ไม่สามารถทำได้
Ingo

2
ฉันไม่สงสัยเลย จุดคือคุณไม่ได้รับวัตถุชั้นที่รันไทม์สำหรับประเภทเอ็กซ์ทั่วไป
Ingo

2
เกือบจะ ฉันยอมรับว่านี่เป็นมากกว่าสิ่งที่สามารถทำได้ด้วย [ใหม่] ในทางปฏิบัติสิ่งนี้มักจะทำงานได้ตลอดเวลา อย่างไรก็ตามมันยังคงเป็นไปไม่ได้ตัวอย่างเช่นการเขียนคลาสคอนเทนเนอร์ที่กำหนดพารามิเตอร์ด้วย E ซึ่งมีเมธอด E [] toArray () และแน่นอนว่าจะส่งกลับอาร์เรย์ E [] ที่แท้จริง รหัสของคุณสามารถใช้ได้เมื่อมี E-object อย่างน้อยหนึ่งรายการในคอลเลกชัน ดังนั้นวิธีการแก้ปัญหาทั่วไปจึงเป็นไปไม่ได้
Ingo

42

นี่เป็นคำตอบเดียวที่ปลอดภัยประเภท

E[] a;

a = newArray(size);

@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
    return Arrays.copyOf(array, length);
}

ฉันต้องค้นหามัน แต่ใช่อาร์กิวเมนต์ "length" ตัวที่สองที่Arrays#copyOf()เป็นอิสระจากความยาวของอาร์เรย์ที่ให้มาเป็นอาร์กิวเมนต์แรก นั่นเป็นเรื่องที่ฉลาดแม้ว่าจะจ่ายค่าโทรศัพท์ให้Math#min()และSystem#arrayCopy()ไม่จำเป็นต้องทำอย่างนี้ให้สำเร็จ docs.oracle.com/javase/7/docs/api/java/util/…
seh

8
สิ่งนี้ไม่ทำงานหากEเป็นตัวแปรประเภท varargs สร้างอาร์เรย์ของการลบออกของEเมื่อเป็นตัวแปรประเภททำให้มันไม่ได้แตกต่างกันมากจากE (E[])new Object[n]โปรดดูhttp://ideone.com/T8xF91 มันไม่ได้ปลอดภัยกว่าคำตอบอื่น ๆ
Radiodef

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

@Radiodef - เป็นที่ถกเถียงกันอยู่ว่าการห้ามการสร้าง array ทั่วไปเป็นความคิดที่ดี ภาษาจะปล่อยให้แบ็คดอร์ - vararg ต้องการการสร้างอาเรย์ทั่วไป new E[]มันเป็นสิ่งที่ดีถ้าเป็นภาษาที่ได้รับอนุญาต ปัญหาที่คุณพบในตัวอย่างของคุณเป็นปัญหาการลบทั่วไปไม่ใช่ปัญหาเฉพาะสำหรับคำถามนี้และคำตอบนี้
ZhongYu

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

33

หากต้องการขยายขนาดเพิ่มเติมให้เพิ่ม[]พารามิเตอร์ของและมิติลงในnewInstance()( Tคือพารามิเตอร์ประเภทclsคือ a Class<T>, d1ถึงd5จำนวนเต็ม):

T[] array = (T[])Array.newInstance(cls, d1);
T[][] array = (T[][])Array.newInstance(cls, d1, d2);
T[][][] array = (T[][][])Array.newInstance(cls, d1, d2, d3);
T[][][][] array = (T[][][][])Array.newInstance(cls, d1, d2, d3, d4);
T[][][][][] array = (T[][][][][])Array.newInstance(cls, d1, d2, d3, d4, d5);

ดูArray.newInstance()รายละเอียดที่


4
+1 มีคำถามเกี่ยวกับการสร้างอาเรย์หลายมิติที่ปิดเหมือนกันกับการโพสต์นี้ แต่ไม่มีคำตอบใดที่ได้กล่าวถึงเฉพาะ
พอล Bellora

1
@JordanC อาจจะ; แม้ว่ามันจะเป็นเหมือนกันในจิตวิญญาณเป็นstackoverflow.com/a/5671304/616460 ; ฉันจะคิดถึงวิธีที่ดีที่สุดในการจัดการในวันพรุ่งนี้ ฉันง่วงนอน.
Jason C

14

ใน Java 8 เราสามารถสร้าง array ทั่วไปได้โดยใช้แลมบ์ดาหรือการอ้างอิงเมธอด นี่คล้ายกับวิธีไตร่ตรอง (ผ่านไปClass) แต่ที่นี่เราไม่ได้ใช้การสะท้อนแสง

@FunctionalInterface
interface ArraySupplier<E> {
    E[] get(int length);
}

class GenericSet<E> {
    private final ArraySupplier<E> supplier;
    private E[] array;

    GenericSet(ArraySupplier<E> supplier) {
        this.supplier = supplier;
        this.array    = supplier.get(10);
    }

    public static void main(String[] args) {
        GenericSet<String> ofString =
            new GenericSet<>(String[]::new);
        GenericSet<Double> ofDouble =
            new GenericSet<>(Double[]::new);
    }
}

<A> A[] Stream.toArray(IntFunction<A[]>)ตัวอย่างเช่นนี้จะถูกใช้โดย

สิ่งนี้สามารถทำได้ pre-Java 8 โดยใช้คลาสที่ไม่ระบุชื่อ แต่มันยุ่งยากกว่า


คุณไม่ต้องการอินเทอร์เฟซพิเศษแบบArraySupplierนี้คุณสามารถประกาศ Constructor เป็นGenSet(Supplier<E[]> supplier) { ...และเรียกมันด้วยบรรทัดเดียวกับที่คุณมี
Lii

4
@Lii เพื่อให้เป็นตัวอย่างของฉันมันจะเป็นIntFunction<E[]>แต่ใช่ว่าเป็นจริง
Radiodef

11

สิ่งนี้จะกล่าวถึงในบทที่ 5 (Generics) ของEffective Java, 2nd Edition , item 25 ... ชอบรายการไปยังอาร์เรย์

รหัสของคุณจะใช้งานได้แม้ว่าจะสร้างคำเตือนที่ไม่ถูกตรวจสอบ (ซึ่งคุณสามารถระงับได้ด้วยคำอธิบายประกอบต่อไปนี้:

@SuppressWarnings({"unchecked"})

อย่างไรก็ตามมันอาจจะดีกว่าถ้าใช้ List แทน Array

มีการอภิปรายที่น่าสนใจของข้อผิดพลาดนี้ / คุณลักษณะเป็นเว็บไซต์โครงการ OpenJDK


8

คุณไม่จำเป็นต้องผ่านอาร์กิวเมนต์คลาสไปยังตัวสร้าง ลองสิ่งนี้

public class GenSet<T> {
    private final T[] array;
    @SuppressWarnings("unchecked")
    public GenSet(int capacity, T... dummy) {
        if (dummy.length > 0)
            throw new IllegalArgumentException(
              "Do not provide values for dummy argument.");
        Class<?> c = dummy.getClass().getComponentType();
        array = (T[])Array.newInstance(c, capacity);
    }
    @Override
    public String toString() {
        return "GenSet of " + array.getClass().getComponentType().getName()
            + "[" + array.length + "]";
    }
}

และ

GenSet<Integer> intSet = new GenSet<>(3);
System.out.println(intSet);
System.out.println(new GenSet<String>(2));

ผลลัพธ์:

GenSet of java.lang.Integer[3]
GenSet of java.lang.String[2]

7

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

ประชาชนStack(Class<T> clazz,int capacity)คอนสตรัคคุณจะต้องผ่านวัตถุชั้นในเวลาทำงานซึ่งหมายความว่าข้อมูลชั้นเป็นใช้ได้ที่รันไทม์รหัสที่ต้องการมัน และClass<T>รูปแบบหมายความว่าคอมไพเลอร์จะตรวจสอบว่าคลาสอ็อบเจ็กต์ที่คุณส่งผ่านนั้นเป็นคลาสอ็อบเจ็กต์สำหรับประเภท T ไม่ใช่คลาสย่อยของ T ไม่ใช่ซุปเปอร์คลาสของ T แต่เป็น T แน่นอน

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


6

สวัสดีแม้ว่าเธรดจะตายไปแล้ว แต่ฉันต้องการดึงความสนใจของคุณมาที่:

Generics ใช้สำหรับการตรวจสอบชนิดในช่วงเวลารวบรวม:

  • ดังนั้นจุดประสงค์คือเพื่อตรวจสอบว่าสิ่งที่เกิดขึ้นคือสิ่งที่คุณต้องการ
  • สิ่งที่คุณส่งคืนคือสิ่งที่ผู้บริโภคต้องการ
  • ตรวจสอบสิ่งนี้:

ป้อนคำอธิบายรูปภาพที่นี่

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


6

แล้วโซลูชันนี้ล่ะ?

@SafeVarargs
public static <T> T[] toGenericArray(T ... elems) {
    return elems;
}

มันใช้งานได้และดูง่ายเกินไปที่จะเป็นจริง มีข้อเสียอะไรบ้าง?


3
เรียบร้อย แต่ใช้งานได้ก็ต่อเมื่อคุณเรียกมันว่า 'ด้วยตนเอง' นั่นคือผ่านองค์ประกอบแต่ละรายการ หากคุณไม่สามารถสร้างอินสแตนซ์ใหม่ได้T[]คุณจะไม่สามารถสร้าง a T[] elemsเพื่อส่งผ่านไปยังฟังก์ชันได้ และถ้าคุณทำได้คุณไม่จำเป็นต้องใช้ฟังก์ชั่น
orlade

5

ดูรหัสนี้ด้วย:

public static <T> T[] toArray(final List<T> obj) {
    if (obj == null || obj.isEmpty()) {
        return null;
    }
    final T t = obj.get(0);
    final T[] res = (T[]) Array.newInstance(t.getClass(), obj.size());
    for (int i = 0; i < obj.size(); i++) {
        res[i] = obj.get(i);
    }
    return res;
}

มันจะแปลงรายการของวัตถุชนิดใด ๆ ไปยังอาร์เรย์ประเภทเดียวกัน


ใช่คุณคืนค่า null ซึ่งไม่ใช่อาร์เรย์ว่างที่คาดไว้ มันเป็นสิ่งที่ดีที่สุดที่คุณสามารถทำได้ แต่ไม่เหมาะ
Kevin Cox

นอกจากนี้ยังสามารถล้มเหลวถ้าListมีมากกว่าหนึ่งชนิดของวัตถุในนั้นเช่นจะโยนtoArray(Arrays.asList("abc", new Object())) ArrayStoreException
Radiodef

ฉันใช้เวอร์ชั่นที่แยกส่วนแล้ว สิ่งแรกที่ฉันสามารถใช้งานได้ แต่เป็นที่ยอมรับว่าฉันไม่ได้ลองใช้วิธีแก้ปัญหาที่เกี่ยวข้องมากขึ้น เพื่อหลีกเลี่ยงการforวนซ้ำและอื่น ๆ ฉันใช้Arrays.fill(res, obj);ตั้งแต่ฉันต้องการค่าเดียวกันสำหรับแต่ละดัชนี
bbarker

5

ฉันพบวิธีที่ง่ายและรวดเร็วสำหรับฉันแล้ว โปรดทราบว่าฉันใช้สิ่งนี้กับ Java JDK 8 เท่านั้นฉันไม่ทราบว่าจะใช้งานได้กับเวอร์ชันก่อนหน้าหรือไม่

แม้ว่าเราจะไม่สามารถสร้างอาเรย์ทั่วไปของพารามิเตอร์ประเภทที่เฉพาะเจาะจงได้ แต่เราสามารถส่งอาเรย์ที่สร้างไปแล้วให้กับตัวสร้างคลาสทั่วไปได้

class GenArray <T> {
    private T theArray[]; // reference array

    // ...

    GenArray(T[] arr) {
        theArray = arr;
    }

    // Do whatever with the array...
}

ตอนนี้ใน main เราสามารถสร้าง array ได้ดังนี้

class GenArrayDemo {
    public static void main(String[] args) {
        int size = 10; // array size
        // Here we can instantiate the array of the type we want, say Character (no primitive types allowed in generics)
        Character[] ar = new Character[size];

        GenArray<Character> = new Character<>(ar); // create the generic Array

        // ...

    }
}

เพื่อความยืดหยุ่นมากขึ้นกับอาร์เรย์ของคุณคุณสามารถใช้รายการที่เชื่อมโยงเช่น ArrayList และวิธีอื่น ๆ ที่พบในคลาส Java.util.ArrayList


4

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


13
ตัวอย่างที่สอง (ใช้ Array.newInstance ()) เป็นประเภทที่ปลอดภัย สิ่งนี้เป็นไปได้เนื่องจากประเภท T ของวัตถุ Class จำเป็นต้องตรงกับ T ของอาร์เรย์ โดยพื้นฐานแล้วมันบังคับให้คุณต้องให้ข้อมูลที่ Java runtime ทิ้งให้กับ generics
Joachim Sauer


3

ฉันสร้างโค้ดขนาดสั้นนี้เพื่อยกระดับคลาสซึ่งส่งผ่านสำหรับยูทิลิตี้ทดสอบอัตโนมัติอย่างง่าย

Object attributeValue = null;
try {
    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }
    else if(!clazz.isInterface()){
        attributeValue = BeanUtils.instantiateClass(clazz);
    }
} catch (Exception e) {
    logger.debug("Cannot instanciate \"{}\"", new Object[]{clazz});
}

หมายเหตุกลุ่มนี้:

    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }

สำหรับอาร์เรย์เริ่มต้นที่Array.newInstance (ชั้นอาร์เรย์ขนาดของอาร์เรย์) คลาสสามารถเป็นได้ทั้งแบบดั้งเดิม (int.class) และวัตถุ (Integer.class)

BeanUtils เป็นส่วนหนึ่งของฤดูใบไม้ผลิ


3

ที่จริงแล้ววิธีที่ง่ายกว่าคือการสร้างอาร์เรย์ของวัตถุและส่งไปยังประเภทที่คุณต้องการเช่นตัวอย่างต่อไปนี้

T[] array = (T[])new Object[SIZE];

โดยที่SIZEเป็นค่าคงที่และTเป็นตัวระบุชนิด


1

ผู้บังคับที่แนะนำโดยคนอื่นไม่ได้ทำงานให้ฉันเลยยกเว้นการคัดเลือกนักแสดงที่ผิดกฎหมาย

อย่างไรก็ตามการส่งโดยนัยนี้ทำงานได้ดี:

Item<K>[] array = new Item[SIZE];

โดยที่ Item คือคลาสที่ฉันกำหนดซึ่งมีสมาชิก:

private K value;

วิธีนี้คุณจะได้รับอาร์เรย์ประเภท K (หากรายการมีค่าเท่านั้น) หรือประเภททั่วไปที่คุณต้องการกำหนดไว้ในรายการระดับ


1

ไม่มีใครตอบคำถามที่เกิดขึ้นในตัวอย่างที่คุณโพสต์

import java.lang.reflect.Array;

class Stack<T> {
    public Stack(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    private final T[] array;
}

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

ในทางกลับกันอาร์เรย์จะรู้ชนิดส่วนประกอบของมันที่รันไทม์

ตัวอย่างนี้แก้ปัญหาโดยการมีรหัสที่เรียกว่านวกรรมิก (ซึ่งรู้ชนิด) ส่งผ่านพารามิเตอร์ที่บอกให้ชั้นเรียนประเภทที่ต้องการ

ดังนั้นแอปพลิเคชันจะสร้างคลาสด้วยสิ่งที่ชอบ

Stack<foo> = new Stack<foo>(foo.class,50)

และคอนสตรัคเตอร์ตอนนี้รู้ (ณ รันไทม์) ว่าส่วนประกอบคืออะไรและสามารถใช้ข้อมูลนั้นเพื่อสร้างอาร์เรย์ผ่าน API การสะท้อนกลับได้

Array.newInstance(clazz, capacity);

ในที่สุดเราก็มีนักแสดงประเภทเพราะคอมไพเลอร์ไม่มีทางรู้ว่าอาร์เรย์ที่ถูกส่งกลับโดยArray#newInstance()เป็นประเภทที่ถูกต้อง (แม้ว่าเราจะรู้)

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


1

ฉันพบวิธีแก้ไขปัญหานี้

บรรทัดด้านล่างแสดงข้อผิดพลาดในการสร้างอาร์เรย์ทั่วไป

List<Person>[] personLists=new ArrayList<Person>()[10];

อย่างไรก็ตามถ้าฉันแค็ปซูลList<Person>ในชั้นเรียนแยกมันทำงานได้

import java.util.ArrayList;
import java.util.List;


public class PersonList {

    List<Person> people;

    public PersonList()
    {
        people=new ArrayList<Person>();
    }
}

คุณสามารถแสดงคนในคลาส PersonList ผ่านทะเยอทะยาน บรรทัดด้านล่างจะให้อาเรย์แก่คุณที่มีList<Person>องค์ประกอบทุกองค์ประกอบ ในคำอื่น ๆ List<Person>ของอาร์เรย์

PersonList[] personLists=new PersonList[10];

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


0

คุณสามารถสร้างอาร์เรย์วัตถุและส่งไปยัง E ได้ทุกที่ ใช่มันไม่สะอาดมากที่จะทำ แต่อย่างน้อยควรทำงาน


"เรากำลังมองหาคำตอบยาว ๆ ที่ให้คำอธิบายและบริบทไม่เพียง แต่ให้คำตอบแบบหนึ่งบรรทัดเท่านั้นให้อธิบายว่าทำไมคำตอบของคุณจึงถูกต้อง
gparyani

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

ยินดีต้อนรับสู่เจ็ดปีที่แล้วฉันคิดว่า
Esko

1
สิ่งนี้จะไม่ทำงานหากคุณพยายามที่จะส่งคืนอาเรย์จากรหัสทั่วไปไปยังผู้โทรที่ไม่ใช้ทั่วไป จะมี classcastexception ที่น่ากลัว
plugwash

0

ลองนี้

private int m = 0;
private int n = 0;
private Element<T>[][] elements = null;

public MatrixData(int m, int n)
{
    this.m = m;
    this.n = n;

    this.elements = new Element[m][n];
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            this.elements[i][j] = new Element<T>();
        }
    }
}

ฉันไม่สามารถเรียกใช้รหัสของคุณได้Elementคลาสของคุณมาจากไหน

0

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

public class Whatever<Thing>{
    private class Holder<OtherThing>{
        OtherThing thing;
    }
    public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}

3
มันใช้งานไม่ได้จริง new Holder<Thing>[10]เป็นการสร้างอาร์เรย์ทั่วไป
Radiodef

0

อาจไม่เกี่ยวข้องกับคำถามนี้ แต่ในขณะที่ฉันได้รับgeneric array creationข้อผิดพลาดในการใช้ ""

Tuple<Long,String>[] tupleArray = new Tuple<Long,String>[10];

ฉันหางานต่อไปนี้ (และทำงานให้ฉัน) ด้วย @SuppressWarnings({"unchecked"}):

 Tuple<Long, String>[] tupleArray = new Tuple[10];

ใช่สิ่งนี้ไม่เกี่ยวข้องกันมากนัก แต่มีรากฐานมาจากปัญหาเดียวกัน (การลบความแปรปรวนของอาร์เรย์) ต่อไปนี้เป็นตัวอย่างของการโพสต์เกี่ยวกับการสร้างอาร์เรย์ของพารามิเตอร์ชนิด: stackoverflow.com/questions/9542076//
Paul Bellora

0

ฉันสงสัยว่ารหัสนี้จะสร้างอาร์เรย์ทั่วไปที่มีประสิทธิภาพหรือไม่

public T [] createArray(int desiredSize){
    ArrayList<T> builder = new ArrayList<T>();
    for(int x=0;x<desiredSize;x++){
        builder.add(null);
    }
    return builder.toArray(zeroArray());
}

//zeroArray should, in theory, create a zero-sized array of T
//when it is not given any parameters.

private T [] zeroArray(T... i){
    return i;
}

แก้ไข: บางทีวิธีอื่นในการสร้างอาเรย์ถ้าขนาดที่คุณต้องการเป็นที่รู้จักและมีขนาดเล็กจะเพียงแค่ป้อนจำนวน "null" ที่ต้องการลงในคำสั่ง zeroArray?

แม้ว่าจะเห็นได้ชัดว่ามันไม่ได้มีความหลากหลายเท่ากับการใช้รหัส createArray


ไม่มันไม่ทำงาน varargs สร้างการลบTเมื่อTเป็นตัวแปรประเภทคือส่งกลับzeroArray Object[]ดูhttp://ideone.com/T8xF91
Radiodef

0

คุณสามารถใช้นักแสดง:

public class GenSet<Item> {
    private Item[] a;

    public GenSet(int s) {
        a = (Item[]) new Object[s];
    }
}

หากคุณกำลังจะแนะนำสิ่งนี้คุณต้องอธิบายข้อ จำกัด ของมัน อย่าเปิดเผยaนอกห้องเรียน!
Radiodef

0

ฉันพบวิธีแก้ปัญหาที่ไม่เหมือนใครเพื่อหลีกเลี่ยงการเริ่มต้นอาร์เรย์ทั่วไป สิ่งที่คุณต้องทำคือสร้างคลาสที่ใช้ในตัวแปร T ทั่วไปเช่น:

class GenericInvoker <T> {
    T variable;
    public GenericInvoker(T variable){
        this.variable = variable;
    }
}

และในคลาสอาเรย์ของคุณให้เริ่มต้นดังนี้:

GenericInvoker<T>[] array;
public MyArray(){
    array = new GenericInvoker[];
}

การเริ่มต้น a new Generic Invoker[]จะทำให้เกิดปัญหากับการไม่เลือก แต่จริง ๆ แล้วไม่ควรมีปัญหาใด ๆ

ในการรับจากอาเรย์คุณควรเรียกอาเรย์ [i] .variable ดังนี้:

public T get(int index){
    return array[index].variable;
}

ส่วนที่เหลือเช่นการปรับขนาดอาร์เรย์สามารถทำได้ด้วย Arrays.copyOf () ดังนี้:

public void resize(int newSize){
    array = Arrays.copyOf(array, newSize);
}

และฟังก์ชั่นเพิ่มสามารถเพิ่มได้เช่น:

public boolean add(T element){
    // the variable size below is equal to how many times the add function has been called 
    // and is used to keep track of where to put the next variable in the array
    arrays[size] = new GenericInvoker(element);
    size++;
}

1
คำถามคือเกี่ยวกับการสร้างอาร์เรย์ของประเภทของพารามิเตอร์ประเภททั่วไปTไม่ใช่อาร์เรย์ของประเภทพารามิเตอร์บางประเภท
Sotirios Delimanolis

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

สิ่งที่งาน ? มันเป็นงานที่แตกต่างอย่างแท้จริง: อาร์เรย์ของประเภทพารามิเตอร์เทียบกับอาร์เรย์ของพารามิเตอร์ประเภททั่วไป
Sotirios Delimanolis

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

ฉันเห็นด้วยกับ Sotiros มีสองวิธีในการคิดถึงคำตอบ อาจเป็นคำตอบสำหรับคำถามอื่นหรือเป็นความพยายามในการสรุปคำถาม ทั้งคู่ผิด / ไม่ช่วยเหลือ ผู้ที่กำลังมองหาคำแนะนำเกี่ยวกับวิธีการใช้คลาส "อาร์เรย์ทั่วไป" จะ / หยุดอ่านเมื่อพวกเขาอ่านชื่อคำถาม และเมื่อพวกเขาพบคำถามที่มี 30 คำตอบพวกเขาไม่น่าจะเลื่อนไปจนจบและอ่านคำตอบการลงคะแนนเป็นศูนย์จากผู้มาใหม่ดังนั้น
Stephen C

0

ตาม vnportnoy ไวยากรณ์

GenSet<Integer> intSet[] = new GenSet[3];

สร้างอาร์เรย์ของการอ้างอิงเป็นโมฆะเพื่อเติมเต็มเป็น

for (int i = 0; i < 3; i++)
{
   intSet[i] = new GenSet<Integer>();
}

ซึ่งเป็นประเภทที่ปลอดภัย


-1
private E a[];
private int size;

public GenSet(int elem)
{
    size = elem;
    a = (E[]) new E[size];
}

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

-1

การสร้างอาร์เรย์ทั่วไปไม่ได้รับอนุญาตใน java แต่คุณสามารถทำได้

class Stack<T> {
private final T[] array;
public Stack(int capacity) {
    array = (T[]) new Object[capacity];
 }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.