Üst türlerin bir listesini alt türlerin listesine nasıl yayınlarsınız?


244

Örneğin, iki sınıfınız olduğunu varsayalım:

public class TestA {}
public class TestB extends TestA{}

Bir döndüren bir yöntem var List<TestA>ve ben TestBböylece sonunda ile bu listedeki tüm nesneleri döküm istiyorum List<TestB>.

Yanıtlar:


578

List<TestB>Neredeyse işe döküm ; ancak çalışmaz, çünkü bir parametrenin genel türünü diğerine atayamazsınız. Bununla birlikte, bir ara joker karakter türünden yayın yapabilirsiniz ve buna izin verilir (joker karakter türlerine ve bu tür karakterlerden yalnızca işaretlenmemiş bir uyarı ile yayınlayabileceğiniz için):

List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;

88
Tüm bu saygıyla, bence bu çılgın bir çözüm - Java'nın size sağlamaya çalıştığı güvenlik türünden kaçıyorsunuz. En azından bu Java eğitimine bakın ( docs.oracle.com/javase/tutorial/java/generics/subtyping.html ) ve bu sorunu düzeltmeden önce neden bu sorunu yaşadığınızı düşünün.
jfritz42

63
@ jfritz42 Ben "kaçmak" tipi güvenlik demezdim. Bu durumda Java'nın sahip olmadığı bilgisine sahip olursunuz. Döküm bunun için.
Planky

3
Bu şunu yazmama izin veriyor: List <String> myList = (List <String>) (List <?>) (New ArrayList <Integer> ()); Bu, çalışma zamanında kilitlenir. Liste <? sayı genişletir> myList = new ArrayList <Integer> ();
Bentaye

1
Ben dürüst katılıyorum @ jfritz42 Ben aynı sorunu yaşıyordu ve o sağlanan URL baktı ve döküm olmadan benim sorunum çözmek mümkün.
Sam Orozco

@ jfritz42 bu derleyici tarafından izin verilen bir Nesneyi Tamsayıya dökmekten işlevsel olarak farklı değildir
kcazllerraf

116

jeneriklerin dökümü mümkün değildir, ancak listeyi başka bir şekilde tanımlarsanız, TestB'yi depolamak mümkündür:

List<? extends TestA> myList = new ArrayList<TestA>();

Listedeki nesneleri kullanırken hala yapılacak tür denetiminiz vardır.


14
Bu geçerli bir Java sözdizimi bile değildir.
newacct

2
Bu doğru, ama aslında doğru olandan daha açıklayıcıydı
Salandur

Aşağıdaki yöntemi var: createSingleString (Liste <? Nesne> nesnelerini genişletir) Bu yöntemin içinde ben bir dizeye listeyi daraltmak için String.valueOf (Object) çağırır. Girdi listesi <Uzun>, Liste <Boolean>, vb. Olduğunda harika çalışır. Teşekkürler!
Chris Sprague

2
TestA arayüz ise ne olur?
Suvitruf - Andrei Apanasik

8
Bunun gerçekten işe yaradığı bir MCVE verebilir misiniz ? Anladım Cannot instantiate the type ArrayList<? extends TestA>.
Nateowami

42

Gerçekten yapamazsınız *:

Bu Java eğitiminden örnek alınmıştır

İki türü vardır varsayalım Ave Böyle ki B extends A. Sonra aşağıdaki kod doğrudur:

    B b = new B();
    A a = b;

Önceki kod geçerlidir, çünkü Bbir alt sınıfı A. Şimdi, List<A>ve ile ne olur List<B>?

O çıkıyor List<B>bir alt sınıfı değildir List<A>bu nedenle olamaz yazma

    List<B> b = new ArrayList<>();
    List<A> a = b; // error, List<B> is not of type List<A>

Ayrıca, olamaz bile yazmak

    List<B> b = new ArrayList<>();
    List<A> a = (List<A>)b; // error, List<B> is not of type List<A>

*: Biz her ikisi için ortak bir ebeveyn gerek döküm mümkün kılmak için List<A>ve List<B>: List<?>örneğin. Aşağıdaki olduğunu geçerlidir:

    List<B> b = new ArrayList<>();
    List<?> t = (List<B>)b;
    List<A> a = (List<A>)t;

Ancak bir uyarı alacaksınız. @SuppressWarnings("unchecked")Yönteminize ekleyerek bunu bastırabilirsiniz .


2
Ayrıca, derleyici tüm dönüşümü deşifre edebildiğinden, bu bağlantıdaki malzemenin kullanılması kontrol edilmemiş dönüşümü önleyebilir.
Ryan H

29

Java 8 ile,

List<TestB> variable = collectionOfListA
    .stream()
    .map(e -> (TestB) e)
    .collect(Collectors.toList());

32
Bu aslında listeyi mi döküyor? Tüm listeyi yinelemek, her bir öğeyi yayınlamak ve daha sonra bunları istenen türden yeni başlatılmış bir listeye eklemek için daha çok işlem dizisi okuduğundan. Başka bir deyişle, Java7 ile tam olarak bunu yapamaz mısın- a new List<TestB>, loop ve cast ile?
Patrick M

5
OP'den "ve bu listedeki tüm nesneleri yayınlamak istiyorum" - yani aslında, buradaki soruların cevabı olmasa bile, soruyu belirtilen şekilde cevaplayan birkaç cevaptan biri.
Luke Usherwood

17

Sanırım yanlış yönde döküm yapıyorsunuz ... yöntem TestAnesnelerin bir listesini döndürürse , o zaman onları dökmek gerçekten güvenli değildirTestB .

Temel olarak derleyiciden, bunları desteklemeyen TestBbir türde işlemler gerçekleştirmenize izin vermesini istersiniz TestA.


11

Bu yaygın olarak başvurulan bir soru olduğundan ve mevcut cevaplar esas olarak neden işe yaramadığını (veya üretim kodunda hiç görmek istemediğim hileli, tehlikeli çözümler önerdiğini) açıkladığından, başka bir cevap eklemenin uygun olduğunu düşünüyorum. tuzaklar ve olası bir çözüm.


Bunun genel olarak çalışmadığının nedeni diğer cevaplarda zaten belirtilmiştir: Dönüştürmenin gerçekten geçerli olup olmadığı, orijinal listede bulunan nesnelerin türüne bağlıdır. Listede türü türü olmayan TestB, ancak farklı bir alt sınıfı olan nesneler olduğunda TestA, döküm geçerli değildir .


Tabii ki, atmalar geçerli olabilir . Bazen derleyici tarafından kullanılamayan türler hakkında bilgi sahibi olabilirsiniz. Bu durumlarda, listeleri yayınlamak mümkündür, ancak genel olarak önerilmez :

Ya biri ...

  • ... tüm listeyi yayınlayabilir veya
  • ... listenin tüm öğelerini yayınlar

İlk yaklaşımın (şu anda kabul edilen cevaba karşılık gelen) yansımaları belirsizdir. İlk bakışta düzgün çalışıyor gibi görünebilir. Ancak giriş listesinde yanlış türler varsa ClassCastException, belki kodda tamamen farklı bir yere a atılır ve bu hata ayıklamak ve yanlış öğenin listeye nerede kaydığını bulmak zor olabilir. En kötü sorun, birisinin geçersiz öğeleri sonradan ekleyebilmesidir. liste yayınlandıktan hata ayıklamayı daha da zor hale getirmesidir.

Bu sahte hata ayıklama sorunu yöntemler ailesi ClassCastExceptionsile hafifletilebilir Collections#checkedCollection.


Listeye türüne göre filtre uygulama

Bir dönüştürme ilişkin daha tip-güvenli yolu List<Supertype> bir etmek List<Subtype>aslında etmektir filtre listesi ve belirli bir tür tek elemanlarını içeren yeni bir liste oluşturmak. Böyle bir yöntemin uygulanması için bazı serbestlik dereceleri vardır (örneğin, nullgirişlerin tedavisi ile ilgili ), ancak olası bir uygulama şöyle görünebilir:

/**
 * Filter the given list, and create a new list that only contains
 * the elements that are (subtypes) of the class c
 * 
 * @param listA The input list
 * @param c The class to filter for
 * @return The filtered list
 */
private static <T> List<T> filter(List<?> listA, Class<T> c)
{
    List<T> listB = new ArrayList<T>();
    for (Object a : listA)
    {
        if (c.isInstance(a))
        {
            listB.add(c.cast(a));
        }
    }
    return listB;
}

Bu yöntem, bu örnekte olduğu gibi, rasgele listeleri (yalnızca tür parametrelerine ilişkin belirli bir Alt Tür-Süpertip ilişkisiyle değil) filtrelemek için kullanılabilir:

// A list of type "List<Number>" that actually 
// contains Integer, Double and Float values
List<Number> mixedNumbers = 
    new ArrayList<Number>(Arrays.asList(12, 3.4, 5.6f, 78));

// Filter the list, and create a list that contains
// only the Integer values:
List<Integer> integers = filter(mixedNumbers, Integer.class);

System.out.println(integers); // Prints [12, 78]

7

Sen döküm olamaz List<TestB>için List<TestA>Steve Kuo bahseder AMA sen içeriğini dökümü gibi List<TestA>içine List<TestB>. Takip etmeyi dene:

List<TestA> result = new List<TestA>();
List<TestB> data = new List<TestB>();
result.addAll(data);

Muhtemelen hatalar bu yüzden bu kodu denemedim ama fikir listeye öğeleri (TestB nesneleri) ekleyerek veri nesnesi üzerinden yinelemek gerekir olmasıdır. Umarım bu işe yarar.


bu niye oylandı? Benim için yararlı oldu ve aşağı oy için bir açıklama görmüyorum.
Zod

7
Elbette bu yazı yanlış değil. Ancak soruyu soran kişinin nihayet bir TestB listesine ihtiyacı vardır. Bu durumda bu cevap yanıltıcıdır. List <TestA> .addAll (List <TestB>) öğesini çağırarak List <TestB> içindeki tüm öğeleri List <TestA> öğesine ekleyebilirsiniz. Ancak List <TestB> .addAll (List <TestA>);
prageeth

6

En güvenli yol, uygulamada bir AbstractListve döküm öğeleri uygulamaktır. ListUtilYardımcı sınıf oluşturdum :

public class ListUtil
{
    public static <TCastTo, TCastFrom extends TCastTo> List<TCastTo> convert(final List<TCastFrom> list)
    {
        return new AbstractList<TCastTo>() {
            @Override
            public TCastTo get(int i)
            {
                return list.get(i);
            }

            @Override
            public int size()
            {
                return list.size();
            }
        };
    }

    public static <TCastTo, TCastFrom> List<TCastTo> cast(final List<TCastFrom> list)
    {
        return new AbstractList<TCastTo>() {
            @Override
            public TCastTo get(int i)
            {
                return (TCastTo)list.get(i);
            }

            @Override
            public int size()
            {
                return list.size();
            }
        };
    }
}

Sen kullanabilirsiniz castlistesi ve de körü körüne dökme nesnelere yöntemi convertgüvenli döküm için yöntemiyle. Misal:

void test(List<TestA> listA, List<TestB> listB)
{
    List<TestB> castedB = ListUtil.cast(listA); // all items are blindly casted
    List<TestB> convertedB = ListUtil.<TestB, TestA>convert(listA); // wrong cause TestA does not extend TestB
    List<TestA> convertedA = ListUtil.<TestA, TestB>convert(listB); // OK all items are safely casted
}

4

Bilmemin tek yolu kopyalamaktır:

List<TestB> list = new ArrayList<TestB> (
    Arrays.asList (
        testAList.toArray(new TestB[0])
    )
);

2
Oldukça garip bir derleyici uyarısı vermediğini düşünüyorum.
Simon Forsberg

bu dizilerle tuhaflıktır. Ah, java dizileri çok işe yaramaz. Ancak, kesinlikle diğer cevaplardan daha kötü ve okunaklı olmadığını düşünüyorum. Bunu artık alçıdan daha güvensiz görmüyorum.
Thufir

3

Bir nesne başvurusunu yayınladığınızda, yalnızca nesnenin türünü değil referans türünü kullanırsınız. döküm nesnenin gerçek türünü değiştirmez.

Java, Nesne türlerini dönüştürmek için örtük kurallara sahip değildir. (İlkellerden farklı olarak)

Bunun yerine, bir türün diğerine nasıl dönüştürüleceğini ve manuel olarak nasıl çağrılacağını sağlamanız gerekir.

public class TestA {}
public class TestB extends TestA{ 
    TestB(TestA testA) {
        // build a TestB from a TestA
    }
}

List<TestA> result = .... 
List<TestB> data = new List<TestB>();
for(TestA testA : result) {
   data.add(new TestB(testA));
}

Bu, doğrudan destekli bir dilde olduğundan daha ayrıntılıdır, ancak işe yarar ve bunu çok sık yapmanız gerekmez.


2

sınıfın bir nesnesi TestAvarsa, onu yayınlayamazsınız TestB. her TestBşey bir TestA, ama başka bir yol değil.

aşağıdaki kodda:

TestA a = new TestA();
TestB b = (TestB) a;

ikinci satır a ClassCastException.

yalnızca TestAnesnenin kendisi olduğunda bir referans oluşturabilirsiniz TestB. Örneğin:

TestA a = new TestB();
TestB b = (TestB) a;

böylece, her zaman bir listesini kullanamazlar TestAlistesine TestB.


1
Sanırım şöyle bir şeyden bahsediyor, 'a' olarak oluşturulan bir yöntem argümanı olarak - TestA a = new TestB () alıyorsunuz, o zaman TestB nesnesini almak istiyorsunuz ve bunu yapmanız gerekiyor - TestB b = ( TestB) a;
samsamara

2

Bu selectInstancesyöntemi Eclipse Collections'ta kullanabilirsiniz . Bu, yeni bir koleksiyon oluşturmayı içerecektir, ancak döküm kullanan kabul edilen çözüm kadar etkili olmayacaktır.

List<CharSequence> parent =
        Arrays.asList("1","2","3", new StringBuffer("4"));
List<String> strings =
        Lists.adapt(parent).selectInstancesOf(String.class);
Assert.assertEquals(Arrays.asList("1","2","3"), strings);

Ben sadece tip downcasts StringBuffergöstermek selectInstancesdeğil, aynı zamanda koleksiyon karışık türleri içeriyorsa filtre edecek göstermek için örneğe dahil .

Not: Eclipse Collections için bir komisyoncuyum.


1

Bu, tip silme nedeniyle mümkündür. Onu bulacaksın

List<TestA> x = new ArrayList<TestA>();
List<TestB> y = new ArrayList<TestB>();
x.getClass().equals(y.getClass()); // true

Dahili olarak her iki liste de türdedir List<Object>. Bu nedenle birini diğerine dökemezsiniz - dökülecek hiçbir şey yoktur.


"Yayınlanacak hiçbir şey yok" ve "Birini diğerine atamazsınız" biraz çelişkidir. Integer j = 42; Integer i = (Integer) j;iyi çalışıyor, her ikisi de tamsayı, ikisi de aynı sınıfa sahip, yani "dökülecek bir şey yok".
Simon Forsberg

1

Sorun şu ki, yöntem bir TestB içeriyorsa TestA listesini döndürmez, bu yüzden doğru yazılırsa ne olur? Sonra bu oyuncu kadrosu:

class TestA{};
class TestB extends TestA{};
List<? extends TestA> listA;
List<TestB> listB = (List<TestB>) listA;

umut edebileceğiniz kadar iyi çalışır (Eclipse sizi tam olarak ne yaptığınızı kontrol etmeyen bir oyuncu olarak uyarır, bu yüzden meh). Peki bunu probleminizi çözmek için kullanabilir misiniz? Aslında bunun için şunları yapabilirsiniz:

List<TestA> badlist = null; // Actually contains TestBs, as specified
List<? extends TestA> talist = badlist;  // Umm, works
List<TextB> tblist = (List<TestB>)talist; // TADA!

Tam olarak ne istedin? veya tam olarak söylemek gerekirse:

List<TestB> tblist = (List<TestB>)(List<? extends TestA>) badlist;

benim için gayet iyi derlenmiş gibi görünüyor.


1
class MyClass {
  String field;

  MyClass(String field) {
    this.field = field;
  }
}

@Test
public void testTypeCast() {
  List<Object> objectList = Arrays.asList(new MyClass("1"), new MyClass("2"));

  Class<MyClass> clazz = MyClass.class;
  List<MyClass> myClassList = objectList.stream()
      .map(clazz::cast)
      .collect(Collectors.toList());

  assertEquals(objectList.size(), myClassList.size());
  assertEquals(objectList, myClassList);
}

Bu test gösterileri nasıl atmak List<Object>için List<MyClass>. Ancak objectList, aynı türden örnekleri içermesi gerektiğine dikkat etmeniz gerekir MyClass. Ve bu örnek List<T>kullanıldığında dikkate alınabilir . Bu amaçla kurucuda alan alın Class<T> clazzve onun yerine kullanın MyClass.class.


0

Bu işe yarar

List<TestA> testAList = new ArrayList<>();
List<TestB> testBList = new ArrayList<>()
testAList.addAll(new ArrayList<>(testBList));

1
Bu gereksiz bir kopya oluşturur.
defnull

0

Bir listeyi manuel olarak yayınlamanın, aşağıdaki gibi bir araç uygulayan bazı araç kutusu tarafından hala sağlanmadığı oldukça garip:

@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends E, E> List<T> cast(List<E> list) {
    return (List) list;
}

Tabii ki, bu öğeleri tek tek kontrol etmeyecektir, ancak uygulamamızın yalnızca alt türü sağladığını iyi bildiğimizde, burada kaçınmak istediğimiz şey tam olarak budur.

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.