Cara paling efisien untuk melemparkan Daftar <SubClass> ke Daftar <BaseClass>

134

Saya punya List<SubClass>yang ingin saya perlakukan sebagai List<BaseClass>. Sepertinya itu seharusnya tidak menjadi masalah karena casting SubClasske a BaseClassadalah snap, tetapi kompiler saya mengeluh bahwa para pemain tidak mungkin.

Jadi, apa cara terbaik untuk mendapatkan referensi ke objek yang sama dengan List<BaseClass>?

Saat ini saya hanya membuat daftar baru dan menyalin daftar lama:

List<BaseClass> convertedList = new ArrayList<BaseClass>(listOfSubClass)

Tapi seperti yang saya pahami itu harus membuat daftar yang sama sekali baru. Saya ingin referensi ke daftar asli, jika memungkinkan!

Riley Lark
sumber
3
jawaban yang Anda miliki di sini: stackoverflow.com/questions/662508/…
lukastymo

Jawaban:

175

Sintaks untuk jenis tugas ini menggunakan wildcard:

List<SubClass> subs = ...;
List<? extends BaseClass> bases = subs;

Sangat penting untuk menyadari bahwa List<SubClass>ini tidak dipertukarkan dengan List<BaseClass>. Kode yang mempertahankan referensi ke List<SubClass>akan mengharapkan setiap item dalam daftar menjadi a SubClass. Jika bagian lain dari kode disebut daftar sebagai List<BaseClass>, kompiler tidak akan mengeluh ketika a BaseClassatau AnotherSubClassdimasukkan. Tetapi ini akan menyebabkan a ClassCastExceptionuntuk potongan kode pertama, yang mengasumsikan bahwa semua yang ada dalam daftar adalah a SubClass.

Koleksi generik tidak berperilaku sama dengan array di Jawa. Array adalah kovarian; yaitu, diizinkan untuk melakukan ini:

SubClass[] subs = ...;
BaseClass[] bases = subs;

Ini diperbolehkan, karena array "tahu" jenis elemen-elemennya. Jika seseorang mencoba untuk menyimpan sesuatu yang bukan turunan dari SubClassarray (melalui basesreferensi), pengecualian runtime akan dibuang.

Koleksi generik tidak "tahu" jenis komponennya; informasi ini "dihapus" pada waktu kompilasi. Oleh karena itu, mereka tidak dapat menaikkan pengecualian runtime ketika toko yang tidak valid terjadi. Alih-alih, a ClassCastExceptionakan dimunculkan pada titik kode yang jauh dan sulit diasosiasikan ketika nilai dibaca dari koleksi. Jika Anda memperhatikan peringatan kompiler tentang keamanan tipe, Anda akan menghindari kesalahan tipe ini saat runtime.

erickson
sumber
Harus dicatat bahwa dengan Array, alih-alih ClassCastException saat mengambil objek yang bukan tipe SubClass (atau diturunkan), Anda akan mendapatkan ArrayStoreException saat menyisipkan.
Axel
Terima kasih terutama untuk penjelasan mengapa kedua daftar ini tidak dapat dianggap sama.
Riley Lark
Sistem tipe Java berpura-pura bahwa array adalah kovarian, tetapi mereka tidak benar-benar dapat disubstitusikan, dibuktikan oleh ArrayStoreException.
Paŭlo Ebermann
38

erickson sudah menjelaskan mengapa Anda tidak bisa melakukan ini, tetapi di sini ada beberapa solusi:

Jika Anda hanya ingin mengambil unsur-unsur dari daftar dasar Anda, pada prinsipnya metode penerima Anda harus dinyatakan sebagai mengambil List<? extends BaseClass>.

Tetapi jika tidak dan Anda tidak dapat mengubahnya, Anda dapat membungkus daftar dengan Collections.unmodifiableList(...), yang memungkinkan mengembalikan Daftar supertype dari parameter argumen. (Ini menghindari masalah keamanan jenis dengan melempar UnsupportedOperationException pada percobaan penyisipan.)

Paŭlo Ebermann
sumber
Bagus! tidak tahu itu.
keuleJ
Terima kasih banyak!
Woland
14

Seperti yang dijelaskan oleh @erickson, jika Anda benar-benar ingin referensi ke daftar asli, pastikan tidak ada kode yang menyisipkan apa pun ke daftar itu jika Anda ingin menggunakannya lagi di bawah deklarasi aslinya. Cara termudah untuk mendapatkannya adalah dengan hanya membuangnya ke daftar ungenerik lama:

List<BaseClass> baseList = (List)new ArrayList<SubClass>();

Saya tidak akan merekomendasikan ini jika Anda tidak tahu apa yang terjadi pada Daftar dan akan menyarankan Anda mengubah kode apa pun yang perlu Daftar untuk menerima Daftar yang Anda miliki.

hd42
sumber
3

Di bawah ini adalah cuplikan berguna yang berfungsi. Itu membangun daftar array baru tetapi pembuatan objek JVM di atas kepala tidak signifikan.

Saya melihat jawaban lain tidak perlu rumit.

List<BaseClass> baselist = new ArrayList<>(sublist);
Sigirisetti
sumber
1
Terima kasih atas cuplikan kode ini, yang dapat memberikan bantuan segera. Penjelasan yang tepat akan sangat meningkatkan nilai pendidikannya dengan menunjukkan mengapa ini adalah solusi yang baik untuk masalah ini, dan akan membuatnya lebih bermanfaat bagi pembaca masa depan dengan pertanyaan yang serupa, tetapi tidak sama. Harap edit jawaban Anda untuk menambahkan penjelasan, dan berikan indikasi batasan dan asumsi apa yang berlaku. Secara khusus, bagaimana ini berbeda dari kode dalam pertanyaan yang membuat daftar baru?
Toby Speight
Overhead tidak signifikan, karena juga harus (dangkal) menyalin setiap referensi. Karena itu skala dengan ukuran daftar, jadi ini adalah operasi O (n).
john16384
2

Saya melewatkan jawaban di mana Anda baru saja melemparkan daftar asli, menggunakan gips ganda. Jadi ini untuk kelengkapan:

List<BaseClass> baseList = (List<BaseClass>)(List<?>)subList;

Tidak ada yang disalin, dan operasinya cepat. Namun, Anda menipu kompilator di sini sehingga Anda harus benar-benar memastikan untuk tidak mengubah daftar sedemikian rupa sehingga subListmulai berisi item dari sub jenis yang berbeda. Ketika berhadapan dengan daftar abadi, ini biasanya bukan masalah.

john16384
sumber
1

List<BaseClass> convertedList = Collections.checkedList(listOfSubClass, BaseClass.class)

jtahlborn
sumber
5
Ini seharusnya tidak berfungsi, karena untuk metode ini semua argumen dan hasilnya mengambil parameter tipe yang sama.
Paŭlo Ebermann
aneh, kamu benar. untuk beberapa alasan, saya mendapat kesan metode ini berguna untuk meng-upcast koleksi generik. masih bisa digunakan dengan cara ini dan itu akan memberikan koleksi "aman" jika Anda ingin menggunakan peredam penekan.
jtahlborn
1

Apa yang Anda coba lakukan sangat berguna dan saya menemukan bahwa saya perlu melakukannya sangat sering dalam kode yang saya tulis. Contoh use case:

Katakanlah kami memiliki antarmuka Foodan kami memiliki zorkingpaket yang memiliki ZorkingFooManageryang membuat dan mengelola instance dari paket-pribadi ZorkingFoo implements Foo. (Skenario yang sangat umum.)

Jadi, ZorkingFooManagerperlu mengandung private Collection<ZorkingFoo> zorkingFoostetapi perlu mengekspos a public Collection<Foo> getAllFoos().

Sebagian besar programmer java tidak akan berpikir dua kali sebelum mengimplementasikan getAllFoos()sebagai mengalokasikan yang baru ArrayList<Foo>, mengisinya dengan semua elemen dari zorkingFoosdan mengembalikannya. Saya menikmati menghibur pikiran bahwa sekitar 30% dari semua siklus jam yang dikonsumsi oleh kode java yang berjalan pada jutaan mesin di seluruh planet ini tidak melakukan apa pun selain membuat salinan ArrayLists yang tidak berguna yang merupakan mikrodetik sampah yang dikumpulkan setelah pembuatannya.

Solusi untuk masalah ini, tentu saja, menurunkan koleksi. Inilah cara terbaik untuk melakukannya:

static <T,U extends T> List<T> downCastList( List<U> list )
{
    return castList( list );
}

Yang membawa kita ke castList()fungsi:

static <T,E> List<T> castList( List<E> list )
{
    @SuppressWarnings( "unchecked" )
    List<T> result = (List<T>)list;
    return result;
}

resultVariabel perantara diperlukan karena penyimpangan bahasa java:

  • return (List<T>)list;menghasilkan pengecualian "pemain yang tidak dicentang"; sejauh ini baik; tapi kemudian:

  • @SuppressWarnings( "unchecked" ) return (List<T>)list; adalah penggunaan anotasi penindasan-peringatan secara ilegal.

Jadi, meskipun tidak halal untuk digunakan @SuppressWarningspada returnpernyataan, tampaknya baik-baik saja untuk menggunakannya pada tugas, sehingga variabel "hasil" tambahan menyelesaikan masalah ini. (Itu harus dioptimalkan baik oleh kompiler atau oleh JIT.)

Mike Nakis
sumber
0

Sesuatu seperti ini seharusnya bekerja juga:

public static <T> List<T> convertListWithExtendableClasses(
    final List< ? extends T> originalList,
    final Class<T> clazz )
{
    final List<T> newList = new ArrayList<>();
    for ( final T item : originalList )
    {
        newList.add( item );
    }// for
    return newList;
}

Tidak benar-benar tahu mengapa clazz diperlukan di Eclipse ..

olivervbk
sumber
0

Bagaimana dengan casting semua elemen. Ini akan membuat daftar baru, tetapi akan merujuk objek asli dari daftar lama.

List<BaseClass> convertedList = listOfSubClass.map(x -> (BaseClass)x).collect(Collectors.toList());
drordk
sumber
Ini adalah bagian lengkap dari kode yang berfungsi untuk melemparkan daftar kelas sub ke kelas super.
kanaparthikiran
0

Ini adalah bagian kode yang lengkap menggunakan Generics, untuk memberikan daftar sub kelas ke kelas super.

Metode penelepon yang melewati tipe subclass

List<SubClass> subClassParam = new ArrayList<>();    
getElementDefinitionStatuses(subClassParam);

Metode Callee yang menerima subtipe dari kelas dasar

private static List<String> getElementDefinitionStatuses(List<? extends 
    BaseClass> baseClassVariableName) {
     return allElementStatuses;
    }
}
kanaparthikiran
sumber