Làm thế nào để tạo một mảng chung trong Java?


1090

Do việc triển khai các tổng quát Java, bạn không thể có mã như thế này:

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

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

Làm thế nào tôi có thể thực hiện điều này trong khi duy trì an toàn loại?

Tôi thấy một giải pháp trên các diễn đàn Java như thế này:

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

Nhưng tôi thực sự không hiểu chuyện gì đang xảy ra.


14
Bạn có thực sự cần phải sử dụng một mảng ở đây? Còn việc sử dụng Bộ sưu tập thì sao?
matt b

12
Vâng, tôi cũng nghĩ rằng các bộ sưu tập thanh lịch hơn cho vấn đề này. Nhưng đây là một bài tập cho lớp và chúng được yêu cầu :(
tatsuhirosatou

3
Tôi không hiểu tại sao tôi cần một phản ánh ở đây. Ngữ pháp là lạ: như java.util.HashMap <String, String> [10] không hợp lệ. java.util.HashMap <dài, dài> (10) mới không hợp lệ. mới dài [] [10] không hợp lệ, mới dài [10] [] là hợp lệ. Điều đó làm cho việc viết một chương trình có thể viết chương trình java trở nên khó khăn hơn.
người đàn ông bằng đồng

Câu trả lời:


703

Tôi phải hỏi lại một câu hỏi: GenSet"được kiểm tra" hay "không được kiểm tra" của bạn? Điều đó nghĩa là gì?

  • Đã kiểm tra : gõ mạnh . GenSetbiết rõ loại đối tượng mà nó chứa (nghĩa là hàm tạo của nó được gọi rõ ràng bằng một Class<E>đối số và các phương thức sẽ đưa ra một ngoại lệ khi chúng được truyền các đối số không thuộc loại E. Xem Collections.checkedCollection.

    -> trong trường hợp đó, bạn nên viết:

    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];
        }
    }
  • Bỏ chọn : gõ yếu . Không có kiểm tra kiểu nào thực sự được thực hiện trên bất kỳ đối tượng nào được truyền dưới dạng đối số.

    -> trong trường hợp đó, bạn nên viết

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

    Lưu ý rằng loại thành phần của mảng phải là độ xóa của tham số loại:

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

Tất cả các kết quả này từ một điểm yếu đã biết và có chủ ý của tính tổng quát trong Java: nó được triển khai bằng cách xóa, vì vậy các lớp "chung" không biết chúng được tạo ra với loại đối số nào trong thời gian chạy và do đó không thể cung cấp loại an toàn trừ khi một số cơ chế rõ ràng (kiểm tra kiểu) được thực hiện.


7
Điều gì sẽ là hiệu suất-khôn ngoan là lựa chọn tốt nhất? Tôi cần nhận được các phần tử từ mảng này khá thường xuyên (trong một vòng lặp). Vì vậy, một bộ sưu tập có thể chậm hơn, nhưng cái nào trong hai cái này là nhanh nhất?
dùng1111929

3
Và nếu loại chung bị giới hạn, mảng sao lưu phải là loại giới hạn.
Mordechai

5
@AaronDigulla Chỉ cần làm rõ đó không phải là gán, mà là khởi tạo một biến cục bộ. Bạn không thể chú thích một biểu thức / tuyên bố.
kennytm

1
@Varkhan Có cách nào để thay đổi kích thước các mảng này từ bên trong lớp thực hiện. Ví dụ: nếu tôi muốn thay đổi kích thước sau khi tràn như ArrayList. Tôi đã tra cứu triển khai ArrayList mà họ có Object[] EMPTY_ELEMENTDATA = {}để lưu trữ. Tôi có thể sử dụng cơ chế này để thay đổi kích thước mà không biết loại sử dụng thuốc generic không?
Journeyman

2
Đối với những người muốn tạo một phương thức với một kiểu chung (đó là những gì tôi đang tìm kiếm), hãy sử dụng phương thức này:public void <T> T[] newArray(Class<T> type, int length) { ... }
Daniel Kvist

225

Bạn có thể làm được việc này:

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

Đây là một trong những cách được đề xuất để triển khai một bộ sưu tập chung trong Java hiệu quả; Mục 26 . Không có lỗi loại, không cần phải truyền mảng nhiều lần. Tuy nhiên, điều này gây ra một cảnh báo vì nó có khả năng nguy hiểm và nên được sử dụng thận trọng. Như đã nêu chi tiết trong các nhận xét, Object[]hiện tại đây đang giả dạng là E[]loại của chúng tôi và có thể gây ra lỗi hoặc lỗi không mong muốn ClassCastExceptionnếu sử dụng không an toàn.

Theo nguyên tắc thông thường, hành vi này là an toàn miễn là mảng truyền được sử dụng bên trong (ví dụ để sao lưu cấu trúc dữ liệu) và không được trả lại hoặc tiếp xúc với mã máy khách. Nếu bạn cần trả về một mảng kiểu chung cho mã khác, Arraylớp phản chiếu mà bạn đề cập là cách phù hợp.


Đáng nói là bất cứ khi nào có thể, bạn sẽ có thời gian làm việc vui vẻ hơn nhiều Listso với mảng nếu bạn đang sử dụng thuốc generic. Chắc chắn đôi khi bạn không có lựa chọn nào khác, nhưng sử dụng khung bộ sưu tập mạnh mẽ hơn nhiều.


47
Điều này sẽ không hoạt động nếu mảng được coi là một kiểu gõ của bất kỳ loại nào, chẳng hạn như String[] s=b;trong test()phương thức trên . Đó là bởi vì mảng E không thực sự, đó là Object []. Điều này quan trọng nếu bạn muốn, ví dụ: a List<String>[]- bạn không thể sử dụng một thứ Object[]đó, bạn phải có một cách List[]cụ thể. Đó là lý do tại sao bạn cần sử dụng tạo mảng <?> Phản ánh.
Lawrence Dol

8
Trường hợp góc / vấn đề là nếu bạn muốn làm, ví dụ, public E[] toArray() { return (E[])internalArray.clone(); }khi internalArrayđược gõ là E[], và do đó thực sự là một Object[]. Điều này thất bại trong thời gian chạy với một ngoại lệ kiểu kiểu vì Object[]không thể gán cho một mảng của bất kỳ kiểu nào Exảy ra.
Lawrence Dol

17
Về cơ bản, cách tiếp cận này sẽ hoạt động miễn là bạn không trả về mảng hoặc vượt qua nó hoặc lưu trữ nó ở một nơi nào đó bên ngoài lớp yêu cầu một mảng thuộc một loại nhất định. Miễn là bạn ở trong lớp thì bạn vẫn ổn vì E bị xóa. Điều đó "nguy hiểm" bởi vì nếu bạn cố gắng trả lại hoặc một cái gì đó, bạn sẽ không nhận được cảnh báo rằng nó không an toàn. Nhưng nếu bạn cẩn thận thì nó hoạt động.
newacct

3
Nó khá an toàn. Trong E[] b = (E[])new Object[1];bạn có thể thấy rõ rằng tham khảo đến các mảng tạo là bvà rằng loại bE[]. Do đó, không có nguy cơ bạn vô tình truy cập vào cùng một mảng thông qua một biến khác nhau của một loại khác. Nếu thay vào đó, bạn đã có Object[] a = new Object[1]; E[]b = (E[])a; thì bạn sẽ cần phải hoang tưởng về cách bạn sử dụng a.
Aaron McDaid

5
Ít nhất là trong Java 1.6, điều này tạo ra một cảnh báo: "Bỏ chọn được chọn từ Object [] đến T []"
Quantum7

61

Đây là cách sử dụng thuốc generic để có được một mảng chính xác loại bạn đang tìm kiếm trong khi bảo vệ an toàn loại (trái ngược với các câu trả lời khác, sẽ cung cấp cho bạn một Objectmảng hoặc dẫn đến cảnh báo tại thời điểm biên dịch):

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

Nó biên dịch mà không có cảnh báo, và như bạn có thể thấy main, đối với bất kỳ loại nào bạn khai báo một thể hiện GenSetnhư, bạn có thể gán acho một mảng của loại đó và bạn có thể gán một phần tử từ amột biến của loại đó, nghĩa là mảng đó và các giá trị trong mảng là đúng loại.

Nó hoạt động bằng cách sử dụng các chữ theo lớp làm mã thông báo loại thời gian chạy, như được thảo luận trong Hướng dẫn Java . Lớp chữ được trình biên dịch coi là phiên bản của java.lang.Class. Để sử dụng một, chỉ cần làm theo tên của một lớp với .class. Vì vậy, String.classhoạt động như một Classđối tượng đại diện cho lớp String. Điều này cũng hoạt động cho các giao diện, enums, mảng bất kỳ chiều (ví dụ String[].class), nguyên thủy (ví dụ int.class) và từ khóa void(ví dụ void.class).

Classchính nó là chung (khai báo là Class<T>, nơi Tđứng cho các loại hình mà Classđối tượng là đại diện cho), có nghĩa là các loại String.classClass<String>.

Vì vậy, bất cứ khi nào bạn gọi các nhà xây dựng cho GenSet, bạn vượt qua trong một chữ lớp cho đối số đầu tiên đại diện cho một mảng của GenSetkiểu được khai báo của ví dụ (ví dụ như String[].classcho GenSet<String>). Lưu ý rằng bạn sẽ không thể có được một loạt các nguyên hàm, vì các nguyên thủy không thể được sử dụng cho các biến loại.

Bên trong hàm tạo, việc gọi phương thức casttrả về Objectđối số đã truyền cho lớp được đại diện bởi Classđối tượng mà phương thức được gọi. Gọi phương thức tĩnh newInstancetrong java.lang.reflect.Arraylợi nhuận như một Objectmột mảng của các loại đại diện bởi các Classđối tượng thông qua như là đối số đầu tiên và chiều dài theo quy định của intthông qua như là đối số thứ hai. Gọi phương thức getComponentTypetrả về một Classđối tượng đại diện cho các loại thành phần của mảng đại diện bởi các Classđối tượng trên mà phương pháp này được gọi là (ví dụ như String.classcho String[].class, nullnếu Classđối tượng không đại diện cho một mảng).

Câu cuối cùng không hoàn toàn chính xác. Gọi String[].class.getComponentType()trả về một Classđối tượng đại diện cho lớp String, nhưng kiểu của nó Class<?>thì không Class<String>, đó là lý do tại sao bạn không thể làm một cái gì đó như sau.

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

Tương tự với mọi phương thức trong Classđó trả về một Classđối tượng.

Về nhận xét của Joachimerer về câu trả lời này (tôi không đủ uy tín để tự nhận xét về nó), ví dụ sử dụng diễn viên T[]sẽ dẫn đến cảnh báo vì trình biên dịch không thể đảm bảo an toàn loại trong trường hợp đó.


Chỉnh sửa liên quan đến ý kiến ​​của Ingo:

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

5
Điều này là vô ích, nó chỉ là một cách phức tạp để viết Chuỗi mới [...]. Nhưng điều thực sự cần thiết là một cái gì đó giống như tĩnh công khai <T> T [] newArray (int size) {...}, và điều này chỉ đơn giản là không tồn tại trong java noir, nó có thể được mô phỏng với sự phản chiếu - lý do là thông tin về cách thức một loại chung được khởi tạo không có sẵn trong thời gian chạy.
Ingo

4
@Ingo Bạn đang nói về cái gì vậy? Mã của tôi có thể được sử dụng để tạo ra một mảng của bất kỳ loại nào.
gdejohn

3
@Charlatan: Chắc chắn, nhưng vì vậy mới có thể []. Câu hỏi là: ai biết loại và khi nào. Do đó, nếu tất cả những gì bạn có là một loại chung chung, bạn không thể.
Ingo

2
Tôi không nghi ngờ điều đó. Vấn đề là, bạn không nhận được một đối tượng Class khi chạy cho loại chung X.
Ingo

2
Hầu hết. Tôi thừa nhận rằng điều này nhiều hơn những gì có thể đạt được với [] mới. Trong thực tế, điều này hầu như sẽ luôn luôn làm công việc. Tuy nhiên, chẳng hạn, vẫn không thể viết một lớp container được tham số hóa với E có phương thức E [] toArray () và điều đó thực sự trả về một mảng E [] thực sự. Mã của bạn chỉ có thể được áp dụng khi có ít nhất một đối tượng E trong bộ sưu tập. Vì vậy, một giải pháp chung là không thể.
Ingo

42

Đây là câu trả lời duy nhất là loại an toàn

E[] a;

a = newArray(size);

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

Tôi đã phải tìm kiếm nó, nhưng vâng, đối số "độ dài" thứ hai Arrays#copyOf()không phụ thuộc vào độ dài của mảng được cung cấp làm đối số thứ nhất. Điều đó thật thông minh, mặc dù nó phải trả chi phí cho các cuộc gọi Math#min()System#arrayCopy(), không ai trong số đó thực sự cần thiết để hoàn thành công việc này. docs.oracle.com/javase/7/docs/api/java/util/ Khăn
seh

8
Điều này không hoạt động nếu Elà một loại biến. Các varargs tạo ra một mảng xóa Ekhi nào Elà một biến kiểu, làm cho nó không khác nhiều so với (E[])new Object[n]. Vui lòng xem http://ideone.com/T8xF91 . Đó không phải là loại an toàn hơn bất kỳ câu trả lời nào khác.
Radiodef

1
@Radiodef - giải pháp có thể chứng minh được loại an toàn tại thời điểm biên dịch. lưu ý rằng tẩy xóa không chính xác là một phần của thông số ngôn ngữ; thông số kỹ thuật được viết cẩn thận để chúng ta có thể thống nhất hoàn toàn trong tương lai - và sau đó giải pháp này cũng sẽ hoạt động hoàn hảo trong thời gian chạy, không giống như các giải pháp khác.
ZhongYu

@Radiodef - Thật đáng tranh luận liệu việc cấm tạo mảng chung có phải là một ý tưởng hay không. bất kể, ngôn ngữ không để lại một cửa hậu - vararg yêu cầu tạo mảng chung. Nó là tốt như thể ngôn ngữ đã cho phép new E[]. Vấn đề bạn đưa ra trong ví dụ của bạn là một vấn đề xóa chung, không phải là duy nhất cho câu hỏi này và câu trả lời này.
ZhongYu

2
@Radiodef - Có một số khác biệt. Tính chính xác của giải pháp này được kiểm tra bởi trình biên dịch; nó không dựa vào lý luận của con người về diễn viên cưỡng bức. Sự khác biệt không đáng kể cho vấn đề cụ thể này. Một số người chỉ thích được một chút ưa thích, đó là tất cả. Nếu bất cứ ai bị hiểu lầm bởi từ ngữ của OP, nó sẽ được làm rõ bởi ý kiến ​​của bạn và của tôi.
ZhongYu

33

Để mở rộng ra nhiều kích thước hơn, chỉ cần thêm []tham số và kích thước vào newInstance()( Tlà tham số loại, clslà một tham sốClass<T> , d1thông qua d5là số nguyên):

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

Xem Array.newInstance()để biết chi tiết.


4
+1 Đã có những câu hỏi về việc tạo mảng đa chiều bị đóng như là bản sao của bài đăng này - nhưng không có câu trả lời nào đề cập cụ thể đến điều đó.
Paul Bellora

1
@JordanC Có thể; mặc dù về tinh thần giống như stackoverflow.com/a/5671304/616460 ; Tôi sẽ nghĩ về cách tốt nhất để xử lý vào ngày mai. Tôi buồn ngủ.
Jason C

14

Trong Java 8, chúng ta có thể thực hiện một kiểu tạo mảng chung bằng cách sử dụng tham chiếu phương thức hoặc lambda. Điều này tương tự như phương pháp phản xạ (vượt qua a Class), nhưng ở đây chúng tôi không sử dụng phản xạ.

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

Ví dụ, điều này được sử dụng bởi <A> A[] Stream.toArray(IntFunction<A[]>).

Điều này cũng có thể được thực hiện trước Java 8 bằng cách sử dụng các lớp ẩn danh nhưng nó cồng kềnh hơn.


Bạn không thực sự cần một giao diện đặc biệt như thế ArraySuppliernày, bạn có thể khai báo hàm tạo GenSet(Supplier<E[]> supplier) { ...và gọi nó với cùng một dòng như bạn có.
Lii

4
@Lii Giống như ví dụ của tôi, nó sẽ như vậy IntFunction<E[]>, nhưng đúng vậy.
Radiodef

11

Điều này được trình bày trong Chương 5 (Generics) của Java hiệu quả, Phiên bản 2 , mục 25 ... Ưu tiên danh sách cho mảng

Mã của bạn sẽ hoạt động, mặc dù nó sẽ tạo ra một cảnh báo không được kiểm tra (mà bạn có thể loại bỏ với chú thích sau:

@SuppressWarnings({"unchecked"})

Tuy nhiên, có lẽ tốt hơn là sử dụng Danh sách thay vì Mảng.

Có một cuộc thảo luận thú vị về lỗi / tính năng này trên trang web của dự án OpenJDK .


8

Bạn không cần truyền đối số Class cho hàm tạo. Thử cái này.

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

kết quả:

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

7

Tổng quát Java hoạt động bằng cách kiểm tra các loại tại thời gian biên dịch và chèn các phôi phù hợp, nhưng xóa các loại trong các tệp được biên dịch. Điều này làm cho các thư viện chung có thể sử dụng được bằng mã không hiểu khái quát (đó là một quyết định thiết kế có chủ ý) nhưng điều đó có nghĩa là bạn thường không thể tìm ra loại nào trong thời gian chạy.

Công chúng Stack(Class<T> clazz,int capacity)xây dựng đòi hỏi bạn phải vượt qua một đối tượng Class tại thời gian chạy, mà thông tin có nghĩa là lớp có sẵn trong thời gian chạy để mã mà cần nó. VàClass<T> biểu mẫu có nghĩa là trình biên dịch sẽ kiểm tra xem đối tượng Class mà bạn vượt qua có chính xác là đối tượng Class cho loại T. Không phải là lớp con của T, không phải là siêu lớp của T, mà chính xác là T.

Điều này có nghĩa là bạn có thể tạo một đối tượng mảng thuộc loại thích hợp trong hàm tạo của mình, điều đó có nghĩa là loại đối tượng bạn lưu trữ trong bộ sưu tập của bạn sẽ kiểm tra các loại của chúng tại điểm chúng được thêm vào bộ sưu tập.


6

Xin chào mặc dù chủ đề đã chết, tôi muốn thu hút sự chú ý của bạn về điều này:

Generics được sử dụng để kiểm tra kiểu trong thời gian biên dịch:

  • Do đó, mục đích là để kiểm tra xem những gì đến là những gì bạn cần.
  • Những gì bạn trở lại là những gì người tiêu dùng cần.
  • Kiểm tra điều này:

nhập mô tả hình ảnh ở đây

Đừng lo lắng về các cảnh báo typecasting khi bạn đang viết lớp chung. Lo lắng khi bạn đang sử dụng nó.


6

Giải pháp này thì sao?

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

Nó hoạt động và trông quá đơn giản để là sự thật. Có bất kỳ nhược điểm?


3
Gọn gàng, nhưng chỉ hoạt động nếu bạn gọi nó là 'thủ công', tức là chuyển các phần tử riêng lẻ. Nếu bạn không thể tạo một phiên bản mới T[], thì bạn không thể lập trình một cách lập trình T[] elemsđể truyền vào hàm. Và nếu bạn có thể, bạn sẽ không cần chức năng.
orlade

5

Cũng xem mã này:

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

Nó chuyển đổi một danh sách của bất kỳ loại đối tượng nào thành một mảng cùng loại.


Có, bạn trả về null, đó không phải là mảng trống dự kiến. Đó là điều tốt nhất bạn có thể làm, nhưng không lý tưởng.
Kevin Cox

Điều này cũng có thể thất bại nếu Listcó nhiều loại đối tượng trong đó, ví dụ như toArray(Arrays.asList("abc", new Object()))sẽ ném ArrayStoreException.
Radiodef

Tôi đã sử dụng một phiên bản rút gọn của điều này; Điều đầu tiên tôi có thể sử dụng nó đã hoạt động, mặc dù phải thừa nhận rằng tôi đã không thử một số giải pháp liên quan hơn. Để tránh forvòng lặp và các vòng lặp khác tôi đã sử dụng Arrays.fill(res, obj);vì tôi muốn cùng một giá trị cho mỗi chỉ mục.
bbarker

5

Tôi đã tìm thấy một cách nhanh chóng và dễ dàng mà làm việc cho tôi. Lưu ý rằng tôi chỉ sử dụng cái này trên Java JDK 8. Tôi không biết liệu nó có hoạt động với các phiên bản trước không.

Mặc dù chúng ta không thể khởi tạo một mảng chung của một tham số loại cụ thể, chúng ta có thể chuyển một mảng đã được tạo cho một hàm tạo lớp chung.

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

    // ...

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

    // Do whatever with the array...
}

Bây giờ trong main chúng ta có thể tạo mảng như vậy:

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

        // ...

    }
}

Để linh hoạt hơn với các mảng của bạn, bạn có thể sử dụng một danh sách được liên kết, ví dụ. ArrayList và các phương thức khác được tìm thấy trong lớp Java.util.ArrayList.


4

Ví dụ này sử dụng phản chiếu Java để tạo một mảng. Làm điều này thường không được khuyến khích, vì nó không an toàn. Thay vào đó, những gì bạn nên làm chỉ là sử dụng Danh sách nội bộ và tránh mảng đó.


13
Ví dụ thứ hai (sử dụng Array.newInstance ()) trên thực tế typesafe. Điều này là có thể bởi vì loại T của đối tượng Class cần khớp với T của mảng. Về cơ bản, nó buộc bạn phải cung cấp thông tin mà thời gian chạy Java loại bỏ đối với các tổng quát.
Joachim Sauer

4

Vượt qua danh sách các giá trị ...

public <T> T[] array(T... values) {
    return values;
}

3

Tôi đã tạo đoạn mã này để khởi tạo một cách phản xạ một lớp được truyền cho một tiện ích kiểm tra tự động đơn giản.

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

Lưu ý phân khúc này:

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

để bắt đầu mảng trong đó Array.newInstance (lớp của mảng, kích thước của mảng) . Lớp có thể là cả nguyên thủy (int. Class) và object (Integer. Class).

BeanUtils là một phần của mùa xuân.


3

Trên thực tế, một cách dễ dàng hơn để làm như vậy, là tạo một mảng các đối tượng và chuyển nó sang loại mong muốn của bạn như ví dụ sau:

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

trong đó SIZElà một hằng số và Tlà một định danh loại


1

Các diễn viên bị ép buộc được đề xuất bởi những người khác đã không làm việc cho tôi, ném ngoại lệ của việc đúc bất hợp pháp.

Tuy nhiên, dàn diễn viên ngầm này hoạt động tốt:

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

Trong đó Item là một lớp tôi xác định có chứa thành viên:

private K value;

Bằng cách này, bạn nhận được một mảng loại K (nếu mục chỉ có giá trị) hoặc bất kỳ loại chung nào bạn muốn xác định trong Mục lớp.


1

Không ai khác đã trả lời câu hỏi về những gì đang xảy ra trong ví dụ bạn đã đăng.

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

Như những người khác đã nói thuốc generic bị "xóa" trong quá trình biên dịch. Vì vậy, trong thời gian chạy, một thể hiện của một cái chung không biết loại thành phần của nó là gì. Lý do cho điều này là lịch sử, Sun muốn thêm tướng mà không phá vỡ giao diện hiện có (cả nguồn và nhị phân).

Mặt khác, không biết loại thành phần của chúng khi chạy.

Ví dụ này giải quyết vấn đề bằng cách có mã gọi hàm tạo (biết kiểu này) truyền tham số cho lớp biết loại cần thiết.

Vì vậy, ứng dụng sẽ xây dựng lớp với một cái gì đó như

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

và hàm tạo bây giờ biết (trong thời gian chạy) loại thành phần là gì và có thể sử dụng thông tin đó để xây dựng mảng thông qua API phản chiếu.

Array.newInstance(clazz, capacity);

Cuối cùng, chúng ta có một kiểu truyền vì trình biên dịch không có cách nào để biết rằng mảng được trả về Array#newInstance()là loại đúng (mặc dù chúng ta biết).

Kiểu này hơi xấu nhưng đôi khi nó có thể là giải pháp kém nhất để tạo các kiểu chung cần biết loại thành phần của chúng khi chạy vì bất kỳ lý do gì (tạo mảng hoặc tạo phiên bản của loại thành phần, v.v.).


1

Tôi tìm thấy một loại công việc xung quanh vấn đề này.

Dòng bên dưới ném lỗi tạo mảng chung

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

Tuy nhiên nếu tôi gói gọn List<Person>trong một lớp riêng biệt, nó sẽ hoạt động.

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


public class PersonList {

    List<Person> people;

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

Bạn có thể hiển thị những người trong lớp PersonList thông qua một getter. Dòng dưới đây sẽ cung cấp cho bạn một mảng, có một List<Person>trong mọi phần tử. Nói cách khác mảng của List<Person>.

PersonList[] personLists=new PersonList[10];

Tôi cần một cái gì đó như thế này trong một số mã tôi đang làm việc và đây là những gì tôi đã làm để làm cho nó hoạt động. Cho đến nay không có vấn đề.


0

Bạn có thể tạo một mảng Object và truyền nó tới E ở mọi nơi. Vâng, đó không phải là cách rất sạch sẽ để làm điều đó nhưng ít nhất nó nên hoạt động.


"Chúng tôi đang tìm kiếm câu trả lời dài cung cấp một số giải thích và bối cảnh. Đừng chỉ đưa ra câu trả lời một dòng; giải thích lý do tại sao câu trả lời của bạn là chính xác, lý tưởng với trích dẫn. Câu trả lời mà không cần giải thích có thể bị xóa."
gparyani

BUt mà sẽ không hoạt động trong một số trường hợp như nếu lớp chung của bạn muốn thực hiện giao diện so sánh.
RamPrasadBismil

Chào mừng đến bảy năm trước, tôi cho rằng.
Esko

1
Điều này sẽ không hoạt động nếu bạn cố gắng trả lại mảng từ mã chung cho một người gọi không chung chung. Sẽ có một classcastexception.
cắm vào

0

thử cái này.

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

Tôi không thể chạy mã của bạn, Elementlớp của bạn đến từ đâu?

0

Một cách giải quyết dễ dàng, mặc dù lộn xộn cho việc này sẽ là lồng một lớp "người giữ" thứ hai bên trong lớp chính của bạn và sử dụng nó để giữ dữ liệu của bạn.

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

3
Điều này không thực sự hoạt động. new Holder<Thing>[10]là một tạo mảng chung.
Radiodef

0

Có thể không liên quan đến câu hỏi này nhưng trong khi tôi generic array creationgặp lỗi "" khi sử dụng

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

Tôi tìm ra các tác phẩm sau (và làm việc cho tôi) với @SuppressWarnings({"unchecked"}):

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

Vâng, điều này không hoàn toàn liên quan, nhưng bắt nguồn từ cùng một vấn đề (xóa, hiệp phương sai mảng). Dưới đây là ví dụ về bài đăng về cách tạo mảng các loại được tham số hóa: stackoverflow.com/questions/9542076/
mẹo

0

Tôi đang tự hỏi nếu mã này sẽ tạo ra một mảng chung hiệu quả?

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

Chỉnh sửa: Có lẽ một cách khác để tạo ra một mảng như vậy, nếu kích thước bạn yêu cầu được biết và nhỏ, sẽ chỉ đơn giản là cung cấp số lượng "null" cần thiết vào lệnh zeroArray?

Mặc dù rõ ràng điều này không linh hoạt bằng việc sử dụng mã createArray.


Không, điều này không hoạt động. Các varargs tạo ra sự xóa Tkhi nào Tlà một biến kiểu, tức là zeroArraytrả về một Object[]. Xem http://ideone.com/T8xF91 .
Radiodef

0

Bạn có thể sử dụng một diễn viên:

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

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

Nếu bạn định đề xuất điều này, bạn thực sự cần phải giải thích những hạn chế của nó. Không bao giờ tiếp xúc avới bên ngoài lớp học!
Radiodef

0

Tôi thực sự tìm thấy một giải pháp khá độc đáo để bỏ qua việc không thể bắt đầu một mảng chung. Những gì bạn phải làm là tạo một lớp có trong biến chung T như vậy:

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

và sau đó trong lớp mảng của bạn chỉ cần bắt đầu như vậy:

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

bắt đầu new Generic Invoker[]sẽ gây ra sự cố không được kiểm soát nhưng thực tế không nên có bất kỳ vấn đề nào.

Để lấy từ mảng, bạn nên gọi mảng [i] .variable như vậy:

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

Phần còn lại, chẳng hạn như thay đổi kích thước mảng có thể được thực hiện với Arrays.copyOf () như vậy:

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

Và chức năng thêm có thể được thêm vào như vậy:

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
Câu hỏi là về việc tạo ra một mảng của loại tham số loại chung T, không phải là một mảng của một số loại tham số.
Sotirios Delimanolis

Nó hoàn thành nhiệm vụ tương tự và không yêu cầu bạn đẩy vào một lớp làm cho bộ sưu tập tùy chỉnh của bạn dễ sử dụng hơn.
Tinh vân cua

Có gì nhiệm vụ ? Đó thực sự là một nhiệm vụ khác nhau: một mảng của một kiểu tham số hóa so với một mảng của một tham số loại chung.
Sotirios Delimanolis

Nó cho phép bạn tạo một mảng từ một loại chung? Vấn đề ban đầu là khởi tạo một mảng bằng cách sử dụng một kiểu chung mà sử dụng phương thức của tôi cho phép bạn thực hiện mà không cần người dùng đẩy vào một lớp hoặc đưa ra một lỗi không được kiểm soát, chẳng hạn như cố gắng truyền Object thành Chuỗi. Giống như ớn lạnh, tôi không phải là người giỏi nhất trong những gì tôi làm và tôi chưa đi học lập trình nhưng tôi nghĩ tôi vẫn xứng đáng được nhận một chút đầu vào thay vì bị một đứa trẻ khác nói xấu trên mạng.
Tinh vân con cua

Tôi đồng ý với Sotiros. Có hai cách để nghĩ về câu trả lời. Hoặc đó là một câu trả lời cho một câu hỏi khác, hoặc đó là một nỗ lực để khái quát hóa câu hỏi. Cả hai đều sai / không hữu ích. Những người đang tìm kiếm hướng dẫn về cách triển khai lớp "mảng chung" sẽ / ngừng đọc khi họ đọc tiêu đề câu hỏi. Và khi họ tìm thấy một câu hỏi với 30 câu trả lời, họ rất khó có thể cuộn đến cuối và đọc câu trả lời bỏ phiếu bằng không từ một người mới tham gia SO.
Stephen C

0

Theo vnportnoy cú pháp

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

tạo ra một loạt các tham chiếu null, được điền vào như

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

Đó là loại an toàn.


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

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

Bạn nên luôn luôn thêm một lời giải thích cho mã của bạn và giải thích lý do tại sao nó giải quyết câu hỏi được đăng ban đầu.
mjuarez

-1

Tạo mảng chung không được phép trong java nhưng bạn có thể làm như thế

class Stack<T> {
private final T[] array;
public Stack(int capacity) {
    array = (T[]) new Object[capacity];
 }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.