List <SubClass> 'ı List <BaseClass>' a dönüştürmenin en verimli yolu


134

Bende List<SubClass>muamele etmek istediğim bir List<BaseClass>. Bir döküm beri bir sorun olmamalı gibi görünüyor SubClassbir için BaseClassbir çırpıda, ama benim derleyici dökme imkansız olduğunu şikayet ediyor.

Öyleyse, aynı nesnelere bir referans almanın en iyi yolu List<BaseClass>nedir?

Şu anda sadece yeni bir liste yapıyorum ve eski listeyi kopyalıyorum:

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

Ama anladığım kadarıyla bu tamamen yeni bir liste oluşturmalı. Mümkünse orijinal listeye bir referans almak istiyorum!


3
burada sahip olduğunuz cevap: stackoverflow.com/questions/662508/…
lukastymo

Yanıtlar:


175

Bu tür atamaların sözdizimi bir joker karakter kullanır:

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

Bu bir fark etmek önemlidir List<SubClass>olduğu değil bir ile değiştirilebilir List<BaseClass>. Bir referansı tutan kod List<SubClass>, listedeki her öğenin bir SubClass. Kodun başka bir parçası listeye a olarak atıfta bulunuyorsa List<BaseClass>, derleyici bir BaseClassveya AnotherSubClasseklendiğinde şikayet etmeyecektir . Ancak bu ClassCastException, listedeki her şeyin a olduğunu varsayan ilk kod parçası için a neden olacaktır SubClass.

Genel koleksiyonlar, Java'daki dizilerle aynı davranmaz. Diziler kovaryanttır; yani, bunu yapmasına izin verilir:

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

Buna izin verilir, çünkü dizi, elemanlarının türünü "bilir". Birisi SubClassdizide örneği olmayan bir şeyi ( basesreferans yoluyla ) depolamaya çalışırsa , bir çalışma zamanı istisnası atılır.

Do Jenerik koleksiyonları değil onların bileşen türünü "bilmek"; bu bilgi derleme sırasında "silinir". Bu nedenle, geçersiz bir depo oluştuğunda bir çalışma zamanı istisnası oluşturamazlar. Bunun yerine, ClassCastExceptionkoleksiyondan bir değer okunduğunda, kodda çok uzak, ilişkilendirilmesi zor bir noktada bir yükseltilecektir. Tür güvenliğiyle ilgili derleyici uyarılarını dikkate alırsanız, çalışma zamanında bu tür hatalarından kaçınacaksınız.


SubClass (veya türetilmiş) türünde olmayan bir nesneyi alırken Arrays ile ClassCastException yerine, eklerken bir ArrayStoreException alacağınıza dikkat edilmelidir.
Axel

Özellikle iki listenin neden aynı sayılamayacağını açıkladığınız için teşekkürler.
Riley Lark

Java türü sistem dizilerin ortak değişken olduğunu varsayar, ancak ArrayStoreException tarafından kanıtlandığı gibi gerçekten ikame edilemezler.
Paŭlo Ebermann

38

erickson bunu neden yapamayacağınızı zaten açıkladı, ancak burada bazı çözümler:

Sadece temel listenizden eleman çıkarmak istiyorsanız, prensipte alma yönteminizin bir List<? extends BaseClass>.

Ama değilse ve değiştiremiyorsanız, listeyi şununla sarmalayabilirsiniz Collections.unmodifiableList(...)ki bu, argümanın parametresinin bir süper tipinin bir Listesini döndürmeye izin verir. (Ekleme denemelerine UnsupportedOperationException atarak tür güvenliği sorununu önler.)


Harika! bunu bilmiyordum.
keuleJ

Çok teşekkür ederim!
Woland

14

@Erickson'un açıkladığı gibi, orijinal listeye gerçekten bir referans istiyorsanız, orijinal beyanı altında bir daha kullanmak istiyorsanız, hiçbir kodun bu listeye hiçbir şey eklemediğinden emin olun. Bunu elde etmenin en basit yolu, onu basit ve eski bir jenerik olmayan listeye atmaktır:

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

Listeye ne olacağını bilmiyorsanız ve Listeyi kabul etmek için Listeye ihtiyaç duyan kodu değiştirmenizi tavsiye ediyorsanız bunu tavsiye etmem.


3

Aşağıda işe yarayan kullanışlı bir pasaj bulunmaktadır. Yeni bir dizi listesi oluşturur ancak JVM nesnesinin baştan yaratılması önemsizdir.

Diğer cevapların gereksiz yere karmaşık olduğunu gördüm.

List<BaseClass> baselist = new ArrayList<>(sublist);

1
Hemen yardımcı olabilecek bu kod parçacığı için teşekkür ederiz. Uygun bir açıklama , bunun neden soruna iyi bir çözüm olduğunu göstererek eğitimsel değerini büyük ölçüde artıracak ve benzer, ancak aynı olmayan sorulara sahip gelecekteki okuyucular için daha yararlı hale getirecektir. Açıklama eklemek ve hangi sınırlamaların ve varsayımların geçerli olduğuna dair bir gösterge vermek için lütfen yanıtınızı düzenleyin . Özellikle, yeni bir liste oluşturan sorudaki koddan bunun farkı nedir?
Toby Speight

Her referansı (sığ) kopyalamak zorunda olduğu için, genel gider önemsiz değildir. Bu nedenle, listenin boyutuna göre ölçeklenir, dolayısıyla bir O (n) işlemidir.
john16384

2

Çift oyuncu kullanarak orijinal listeyi yayınladığınız cevabı kaçırdım. İşte burada tamlık için:

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

Hiçbir şey kopyalanmaz ve işlem hızlıdır. Bununla birlikte, burada derleyiciyi kandırıyorsunuz, bu nedenle listeyi, subListbaşlangıçlar farklı bir alt türden öğeler içerecek şekilde değiştirmediğinizden kesinlikle emin olmalısınız . Değişmez listelerle uğraşırken bu genellikle bir sorun değildir.


1

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


5
Bu yöntemlerde tüm bağımsız değişkenler ve sonuç aynı tür parametresini aldığından, bu çalışmamalıdır.
Paŭlo Ebermann

garip, haklısın. Bazı nedenlerden dolayı, bu yöntemin genel bir koleksiyonun yukarı dökümünü yapmak için yararlı olduğunu düşünüyordum. yine de bu şekilde kullanılabilir ve bastırma uyarılarını kullanmak istiyorsanız "güvenli" bir koleksiyon sağlayacaktır.
jtahlborn

1

Yapmaya çalıştığınız şey çok yararlı ve bunu yazdığım kodda çok sık yapmam gerektiğini görüyorum. Örnek bir kullanım durumu:

Bir arayüzümüz olduğunu Foove paket-özel örneklerini yaratan ve yöneten bir zorkingpaketimiz olduğunu ZorkingFooManagervarsayalım ZorkingFoo implements Foo. (Çok yaygın bir senaryo.)

Yani, ZorkingFooManagera içermesi gerekiyor private Collection<ZorkingFoo> zorkingFoosama a public Collection<Foo> getAllFoos().

Çoğu java programcısı, getAllFoos()yeni bir tane ayırmayı ArrayList<Foo>, onu tüm öğeleriyle zorkingFoosdoldurup geri döndürmeyi uygulamadan önce iki kez düşünmez . Gezegenin dört bir yanındaki milyonlarca makinede çalışan java kodunun tükettiği tüm saat döngülerinin yaklaşık% 30'unun, oluşturulduktan sonra mikrosaniyelerden sonra çöp olarak toplanan ArrayList'lerin böylesine gereksiz kopyalarını oluşturmaktan başka hiçbir şey yapmadığı düşüncesini eğlendirmekten keyif alıyorum.

Bu problemin çözümü elbette koleksiyonun aşağı dökümünü yapmaktır. İşte bunu yapmanın en iyi yolu:

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

Bu da bizi castList()işleve getiriyor :

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

resultJava dilinin bir sapkınlığı nedeniyle ara değişken gereklidir:

  • return (List<T>)list;bir "kontrol edilmeyen çevrim" istisnası üretir; çok uzak çok iyi; ama sonra:

  • @SuppressWarnings( "unchecked" ) return (List<T>)list; bastırma uyarıları ek açıklamasının yasa dışı kullanımıdır.

Dolayısıyla, @SuppressWarningsbir returnifadede kullanmak koşer olmasa da, görünüşe göre onu bir atamada kullanmak iyidir, bu nedenle ekstra "sonuç" değişkeni bu sorunu çözer. (Derleyici veya JIT tarafından yine de optimize edilmelidir.)


0

Bunun gibi bir şey de çalışmalıdır:

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

Eclipse'de neden clazz'a ihtiyaç olduğunu gerçekten bilmiyorum ..


0

Tüm elementleri dökmeye ne dersiniz? Yeni bir liste oluşturacak, ancak eski listeden orijinal nesnelere referans verecektir.

List<BaseClass> convertedList = listOfSubClass.map(x -> (BaseClass)x).collect(Collectors.toList());

Bu, alt sınıf listesini süper sınıfa dönüştürmek için çalışan eksiksiz kod parçasıdır.
kanaparthikiran

0

Bu, alt sınıf listesini süper sınıfa dönüştürmek için Generics kullanan eksiksiz çalışan kod parçasıdır.

Alt sınıf türünü geçen arayan yöntemi

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

Temel sınıfın herhangi bir alt türünü kabul eden Callee yöntemi

private static List<String> getElementDefinitionStatuses(List<? extends 
    BaseClass> baseClassVariableName) {
     return allElementStatuses;
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.