Die effizienteste Möglichkeit, List <SubClass> in List <BaseClass> umzuwandeln

134

Ich habe eine List<SubClass>, die ich als behandeln möchte List<BaseClass>. Es scheint, dass es kein Problem sein sollte, da das Umwandeln von a SubClassin a BaseClassein Kinderspiel ist, aber mein Compiler beschwert sich, dass das Umwandeln unmöglich ist.

Was ist der beste Weg, um einen Verweis auf dieselben Objekte wie ein zu erhalten List<BaseClass>?

Im Moment erstelle ich nur eine neue Liste und kopiere die alte Liste:

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

Aber so wie ich es verstehe, muss eine völlig neue Liste erstellt werden. Ich möchte, wenn möglich, einen Verweis auf die ursprüngliche Liste!

Riley Lark
quelle
3
Die Antwort, die Sie hier haben: stackoverflow.com/questions/662508/…
lukastymo

Antworten:

175

Die Syntax für diese Art der Zuweisung verwendet einen Platzhalter:

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

Es ist wichtig zu erkennen , dass eine List<SubClass>ist nicht mit einem austauschbar List<BaseClass>. Code, der einen Verweis auf enthält List<SubClass>, erwartet, dass jedes Element in der Liste a ist SubClass. Wenn ein anderer Teil des Codes auf die Liste als a verweist, List<BaseClass>beschwert sich der Compiler nicht, wenn ein BaseClassoder AnotherSubClasseingefügt wird. Dies führt jedoch zu a ClassCastExceptionfür den ersten Code, der davon ausgeht, dass alles in der Liste a ist SubClass.

Generische Sammlungen verhalten sich nicht wie Arrays in Java. Arrays sind kovariant; das heißt, es ist erlaubt, dies zu tun:

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

Dies ist zulässig, da das Array den Typ seiner Elemente "kennt". Wenn jemand versucht, etwas zu speichern, das keine Instanz SubClassim Array ist (über die basesReferenz), wird eine Laufzeitausnahme ausgelöst.

Generika Sammlungen Sie nicht „wissen“ , deren Komponententyp; Diese Informationen werden beim Kompilieren "gelöscht". Daher können sie keine Laufzeitausnahme auslösen, wenn ein ungültiger Speicher auftritt. Stattdessen wird a ClassCastExceptionan einem weit entfernten, schwer zuzuordnenden Punkt im Code ausgelöst, wenn ein Wert aus der Sammlung gelesen wird. Wenn Sie die Compiler-Warnungen zur Typensicherheit beachten, vermeiden Sie diese Typfehler zur Laufzeit.

erickson
quelle
Beachten Sie, dass bei Arrays anstelle von ClassCastException beim Abrufen eines Objekts, das nicht vom Typ SubClass (oder abgeleitet) ist, beim Einfügen eine ArrayStoreException angezeigt wird.
Axel
Vielen Dank insbesondere für die Erklärung, warum die beiden Listen nicht als gleich angesehen werden können.
Riley Lark
Das Java-Typsystem gibt vor, dass Arrays kovariant sind, aber nicht wirklich ersetzbar, wie die ArrayStoreException beweist.
Paŭlo Ebermann
38

erickson hat bereits erklärt, warum man das nicht kann, aber hier einige lösungen:

Wenn Sie nur Elemente aus Ihrer Basisliste entfernen möchten, sollte Ihre Empfangsmethode grundsätzlich als a deklariert werden List<? extends BaseClass>.

Wenn dies nicht Collections.unmodifiableList(...)der Fall ist und Sie es nicht ändern können, können Sie die Liste umbrechen, wodurch eine Liste eines Supertyps des Argumentparameters zurückgegeben werden kann. (Es vermeidet das Typensicherheitsproblem, indem UnsupportedOperationException bei Einfügeversuchen ausgelöst wird.)

Paŭlo Ebermann
quelle
Toll! wusste das nicht.
KeuleJ
Vielen Dank!
Woland
14

Wie @erickson erklärte, stellen Sie sicher, dass kein Code etwas in diese Liste einfügt, wenn Sie sie unter der ursprünglichen Deklaration jemals wieder verwenden möchten, wenn Sie wirklich einen Verweis auf die ursprüngliche Liste wünschen. Der einfachste Weg, es zu bekommen, besteht darin, es einfach in eine einfache alte ungenerische Liste umzuwandeln:

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

Ich würde dies nicht empfehlen, wenn Sie nicht wissen, was mit der Liste passiert, und vorschlagen würden, den Code zu ändern, der die Liste benötigt, um die Liste zu akzeptieren, die Sie haben.

hd42
quelle
3

Unten finden Sie einen nützlichen Ausschnitt, der funktioniert. Es wird eine neue Array-Liste erstellt, aber die Erstellung von JVM-Objekten über Kopf ist unwichtig.

Ich habe gesehen, dass andere Antworten nicht unbedingt kompliziert sind.

List<BaseClass> baselist = new ArrayList<>(sublist);
Sigirisetti
quelle
1
Vielen Dank für dieses Code-Snippet, das möglicherweise sofortige Hilfe bietet. Eine richtige Erklärung würde den Bildungswert erheblich verbessern , indem sie zeigt, warum dies eine gute Lösung für das Problem ist, und sie für zukünftige Leser mit ähnlichen, aber nicht identischen Fragen nützlicher machen. Bitte bearbeiten Sie Ihre Antwort, um eine Erklärung hinzuzufügen, und geben Sie an, welche Einschränkungen und Annahmen gelten. Wie unterscheidet sich dies insbesondere von dem Code in der Frage, mit der eine neue Liste erstellt wird?
Toby Speight
Der Overhead ist nicht unerheblich, da er auch jede Referenz (flach) kopieren muss. Als solches skaliert es mit der Größe der Liste, so dass es sich um eine O (n) -Operation handelt.
john16384
2

Ich habe die Antwort verpasst, bei der Sie gerade die ursprüngliche Liste mit einer Doppelbesetzung besetzt haben. Der Vollständigkeit halber hier:

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

Es wird nichts kopiert und der Vorgang ist schnell. Sie betrügen hier jedoch den Compiler, sodass Sie unbedingt darauf achten müssen, die Liste nicht so zu ändern, dass die subListStarts Elemente eines anderen Untertyps enthalten. Beim Umgang mit unveränderlichen Listen ist dies normalerweise kein Problem.

john16384
quelle
1

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

jtahlborn
quelle
5
Dies sollte nicht funktionieren, da für diese Methoden alle Argumente und das Ergebnis denselben Typparameter verwenden.
Paŭlo Ebermann
seltsam, du hast recht. Aus irgendeinem Grund hatte ich den Eindruck, dass diese Methode für die Übertragung einer generischen Sammlung nützlich war. könnte weiterhin auf diese Weise verwendet werden und bietet eine "sichere" Sammlung, wenn Sie Unterdrückungswarnungen verwenden möchten.
Jtahlborn
1

Was Sie versuchen zu tun, ist sehr nützlich und ich finde, dass ich es sehr oft in Code tun muss, den ich schreibe. Ein Beispiel für einen Anwendungsfall:

Angenommen, wir haben eine Schnittstelle Foound ein zorkingPaket, das ZorkingFooManagerInstanzen von package-private erstellt und verwaltet ZorkingFoo implements Foo. (Ein sehr häufiges Szenario.)

So ZorkingFooManagermuss ein enthalten , private Collection<ZorkingFoo> zorkingFoosaber es braucht eine belichten public Collection<Foo> getAllFoos().

Die meisten Java-Programmierer würden nicht zweimal überlegen, bevor sie getAllFoos()ein neues ArrayList<Foo>zuweisen, es mit allen Elementen aus zorkingFoosfüllen und es zurückgeben. Ich genieße es, den Gedanken zu unterhalten, dass etwa 30% aller Taktzyklen, die von Java-Code auf Millionen von Computern auf der ganzen Welt verbraucht werden, nichts anderes tun, als solche nutzlosen Kopien von ArrayLists zu erstellen, die nach ihrer Erstellung Mikrosekunden mit Müll gesammelt werden.

Die Lösung für dieses Problem besteht natürlich darin, die Sammlung herunterzuwerfen. Hier ist der beste Weg, dies zu tun:

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

Was uns zur castList()Funktion bringt :

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

Die Zwischenvariable resultist aufgrund einer Perversion der Java-Sprache erforderlich:

  • return (List<T>)list;erzeugt eine "ungeprüfte Besetzung" -Ausnahme; So weit, ist es gut; aber dann:

  • @SuppressWarnings( "unchecked" ) return (List<T>)list; ist eine illegale Verwendung der Annotation "Unterdrückungswarnungen".

Obwohl die Verwendung @SuppressWarningsfür eine returnAnweisung nicht koscher ist , ist es anscheinend in Ordnung, sie für eine Zuweisung zu verwenden. Die zusätzliche Variable "result" löst dieses Problem. (Es sollte sowieso entweder vom Compiler oder von der JIT optimiert werden.)

Mike Nakis
quelle
0

So etwas sollte auch funktionieren:

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

Ich weiß nicht genau, warum in Eclipse Clazz benötigt wird.

olivervbk
quelle
0

Wie wäre es, alle Elemente zu gießen? Es wird eine neue Liste erstellt, es wird jedoch auf die ursprünglichen Objekte aus der alten Liste verwiesen.

List<BaseClass> convertedList = listOfSubClass.map(x -> (BaseClass)x).collect(Collectors.toList());
drordk
quelle
Dies ist der vollständige Code, mit dem die Unterklassenliste in eine Superklasse umgewandelt werden kann.
Kanaparthikiran
0

Dies ist der vollständige Code, der Generics verwendet, um die Unterklassenliste in eine Superklasse umzuwandeln.

Aufrufer-Methode, die den Unterklassentyp übergibt

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

Callee-Methode, die jeden Subtyp der Basisklasse akzeptiert

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