Le moyen le plus efficace de convertir List <SubClass> en List <BaseClass>

134

J'ai un List<SubClass>que je veux traiter comme un List<BaseClass>. Il semble que cela ne devrait pas être un problème car la conversion de a SubClassen a BaseClassest un jeu d'enfant, mais mon compilateur se plaint que la distribution est impossible.

Alors, quel est le meilleur moyen d'obtenir une référence aux mêmes objets qu'un List<BaseClass>?

En ce moment, je fais juste une nouvelle liste et je copie l'ancienne liste:

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

Mais si je comprends bien, cela doit créer une liste entièrement nouvelle. J'aimerais une référence à la liste originale, si possible!

Riley Lark
la source
3
la réponse que vous avez ici: stackoverflow.com/questions/662508/…
lukastymo

Réponses:

175

La syntaxe de ce type d'affectation utilise un caractère générique:

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

Il est important de comprendre que a List<SubClass>n'est pas interchangeable avec a List<BaseClass>. Le code qui conserve une référence à l ' List<SubClass>attendra que chaque élément de la liste soit un SubClass. Si une autre partie du code se référait à la liste comme a List<BaseClass>, le compilateur ne se plaindra pas lorsqu'un BaseClassou AnotherSubClassest inséré. Mais cela entraînera un ClassCastExceptionpour le premier morceau de code, ce qui suppose que tout dans la liste est un SubClass.

Les collections génériques ne se comportent pas de la même manière que les tableaux en Java. Les tableaux sont covariants; c'est-à-dire qu'il est autorisé à faire ceci:

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

Ceci est autorisé, car le tableau "connaît" le type de ses éléments. Si quelqu'un tente de stocker quelque chose qui n'est pas une instance de SubClassdans le tableau (via la basesréférence), une exception d'exécution sera levée.

Les collections génériques ne «connaissent» pas leur type de composant; ces informations sont «effacées» au moment de la compilation. Par conséquent, ils ne peuvent pas déclencher d'exception d'exécution lorsqu'un magasin non valide se produit. Au lieu de cela, a ClassCastExceptionsera déclenché à un point très éloigné et difficile à associer dans le code lorsqu'une valeur est lue à partir de la collection. Si vous tenez compte des avertissements du compilateur concernant la sécurité des types, vous éviterez ces erreurs de type au moment de l'exécution.

Erickson
la source
Il convient de noter qu'avec Arrays, au lieu de ClassCastException lors de la récupération d'un objet qui n'est pas de type SubClass (ou dérivé), vous obtiendrez une ArrayStoreException lors de l'insertion.
Axel
Merci en particulier pour l'explication des raisons pour lesquelles les deux listes ne peuvent pas être considérées comme identiques.
Riley Lark
Le système de types Java prétend que les tableaux sont covariants, mais ils ne sont pas vraiment substituables, comme le prouve ArrayStoreException.
Paŭlo Ebermann
38

erickson a déjà expliqué pourquoi vous ne pouvez pas faire cela, mais voici quelques solutions:

Si vous souhaitez uniquement retirer des éléments de votre liste de base, votre méthode de réception doit en principe être déclarée comme prenant un List<? extends BaseClass>.

Mais si ce n'est pas le cas et que vous ne pouvez pas le changer, vous pouvez envelopper la liste avec Collections.unmodifiableList(...), ce qui permet de renvoyer une liste d'un supertype du paramètre de l'argument. (Cela évite le problème de sécurité de type en lançant une exception UnsupportedOperationException lors des tentatives d'insertion.)

Paŭlo Ebermann
la source
Génial! ne savait pas celui-là.
keuleJ
Merci beaucoup!
Woland
14

Comme @erickson l'a expliqué, si vous voulez vraiment une référence à la liste d'origine, assurez-vous qu'aucun code n'insère quoi que ce soit dans cette liste si jamais vous souhaitez l'utiliser à nouveau sous sa déclaration d'origine. Le moyen le plus simple de l'obtenir est de simplement le convertir dans une vieille liste non générique:

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

Je ne recommanderais pas cela si vous ne savez pas ce qui arrive à la liste et vous suggérerais de modifier le code dont la liste a besoin pour accepter la liste que vous avez.

hd42
la source
3

Vous trouverez ci-dessous un extrait utile qui fonctionne. Il construit une nouvelle liste de tableaux, mais la création d'objets JVM au-dessus de la tête n'est pas significative.

J'ai vu que d'autres réponses n'étaient pas forcément compliquées.

List<BaseClass> baselist = new ArrayList<>(sublist);
Sigirisetti
la source
1
Merci pour cet extrait de code, qui peut fournir une aide immédiate. Une explication appropriée améliorerait considérablement sa valeur éducative en montrant pourquoi c'est une bonne solution au problème, et la rendrait plus utile aux futurs lecteurs avec des questions similaires, mais pas identiques. Veuillez modifier votre réponse pour ajouter une explication et donner une indication des limites et des hypothèses applicables. En particulier, en quoi cela diffère-t-il du code de la question qui fait une nouvelle liste?
Toby Speight
La surcharge n'est pas insignifiante, car elle doit également copier (superficiellement) chaque référence. En tant que tel, il s'adapte à la taille de la liste, il s'agit donc d'une opération O (n).
john16384 le
2

J'ai manqué la réponse où vous venez de lancer la liste originale, en utilisant une double distribution. Alors voici pour l'exhaustivité:

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

Rien n'est copié et l'opération est rapide. Cependant, vous trompez le compilateur ici, vous devez donc absolument vous assurer de ne pas modifier la liste de telle sorte que le subListdébut contienne des éléments d'un sous-type différent. Lorsqu'il s'agit de listes immuables, ce n'est généralement pas un problème.

john16384
la source
1

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

jtahlborn
la source
5
Cela ne devrait pas fonctionner, car pour cette méthode, tous les arguments et le résultat prennent le même paramètre de type.
Paŭlo Ebermann le
bizarre, tu as raison. pour une raison quelconque, j'avais l'impression que cette méthode était utile pour remonter une collection générique. pourrait encore être utilisé de cette façon et il fournira une collection "sûre" si vous voulez utiliser suppresswarnings.
jtahlborn
1

Ce que vous essayez de faire est très utile et je trouve que je dois le faire très souvent dans le code que j'écris. Un exemple de cas d'utilisation:

Disons que nous avons une interface Fooet que nous avons un zorkingpackage qui a un ZorkingFooManagerqui crée et gère des instances de package-private ZorkingFoo implements Foo. (Un scénario très courant.)

Donc, ZorkingFooManagerdoit contenir un private Collection<ZorkingFoo> zorkingFoosmais il doit exposer un fichier public Collection<Foo> getAllFoos().

La plupart des programmeurs java ne réfléchiraient pas à deux fois avant de l'implémenter getAllFoos()en allouant un nouveau ArrayList<Foo>, en le remplissant avec tous les éléments zorkingFooset en le renvoyant. J'apprécie l'idée qu'environ 30% de tous les cycles d'horloge consommés par le code java s'exécutant sur des millions de machines partout dans le monde ne font rien d'autre que créer des copies inutiles de ArrayLists qui sont récupérées en microsecondes après leur création.

La solution à ce problème est, bien entendu, de réduire la collection. Voici la meilleure façon de le faire:

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

Ce qui nous amène à la castList()fonction:

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

La resultvariable intermédiaire est nécessaire en raison d'une perversion du langage java:

  • return (List<T>)list;produit une exception "cast non vérifié"; jusqu'ici tout va bien; mais alors:

  • @SuppressWarnings( "unchecked" ) return (List<T>)list; est une utilisation illégale de l'annotation suppress-warnings.

Ainsi, même s'il n'est pas casher à utiliser @SuppressWarningssur une returninstruction, il est apparemment bien de l'utiliser sur une affectation, donc la variable supplémentaire "result" résout ce problème. (Il devrait être optimisé par le compilateur ou par le JIT de toute façon.)

Mike Nakis
la source
0

Quelque chose comme ça devrait fonctionner aussi:

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

Je ne sais pas vraiment pourquoi Clazz est nécessaire dans Eclipse.

olivervbk
la source
0

Que diriez-vous de lancer tous les éléments. Il créera une nouvelle liste, mais référencera les objets d'origine de l'ancienne liste.

List<BaseClass> convertedList = listOfSubClass.map(x -> (BaseClass)x).collect(Collectors.toList());
Drordk
la source
C'est le morceau de code complet qui fonctionne pour convertir la liste de sous-classes en super classe.
kanaparthikiran
0

C'est le morceau de code de travail complet utilisant les génériques, pour convertir la liste de sous-classes en super classe.

Méthode de l'appelant qui passe le type de sous-classe

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

Méthode de l'appelé qui accepte n'importe quel sous-type de la classe de base

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