web-dev-qa-db-fra.com

Moyen le plus efficace de transtyper List <SubClass> to List <BaseClass>

J'ai un List<SubClass> que je veux traiter comme un List<BaseClass>. Il semble que cela ne devrait pas poser de problème, car lancer SubClass en BaseClass est 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 suis en train de faire une nouvelle liste et de copier 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!

122
Riley Lark

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

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

Il est important de réaliser qu'un List<SubClass> est pas interchangeable avec un List<BaseClass>. Code qui conserve une référence au List<SubClass> s'attendra à ce que chaque élément de la liste soit un SubClass. Si une autre partie du code se réfère à la liste en tant que List<BaseClass>, le compilateur ne se plaindra pas lorsqu'un BaseClass ou AnotherSubClass sera inséré. Mais cela entraînera un ClassCastException pour 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 SubClass dans le tableau (via la référence bases,), une exception d'exécution sera levée.

Les collections génériques ne pas "connaissent" leur type de composant; cette information est "effacée" au moment de la compilation. Par conséquent, ils ne peuvent pas générer une exception d'exécution lorsqu'un magasin non valide se produit. Au lieu de cela, ClassCastException sera levé à un point de code lointain et difficile à associer lorsqu'une valeur est lue dans la collection. Si vous tenez compte des avertissements du compilateur concernant la sécurité des types, vous éviterez ces erreurs de type lors de l'exécution.

159
erickson

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

Si vous souhaitez uniquement supprimer 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(...), qui permet de renvoyer une liste d'un sur-type du paramètre de l'argument. (Cela évite le problème de sécurité des types en lançant UnsupportedOperationException lors des tentatives d'insertion.)

37
Paŭlo Ebermann

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 vous souhaitez un jour l'utiliser à nouveau sous sa déclaration d'origine. Le moyen le plus simple de l'obtenir est simplement de le convertir en une liste ancienne et peu générique:

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

Je ne recommanderais pas ceci si vous ne savez pas ce qu'il advient de la liste et vous suggérer de changer le code qui nécessite la liste pour accepter la liste que vous avez.

11
hd42

Vous trouverez ci-dessous un extrait utile qui fonctionne. Il construit une nouvelle liste de tableaux mais la création d’objets JVM n’est pas significative.

J'ai vu d'autres réponses sont forcément compliquées.

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

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

1
jtahlborn

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 d'utilisation:

Supposons que nous ayons une interface Foo et un package zorking qui possède un ZorkingFooManager qui crée et gère les instances de package-private ZorkingFoo implements Foo. (Un scénario très commun.)

Donc, ZorkingFooManager doit contenir un private Collection<ZorkingFoo> zorkingFoos Mais il doit exposer une public Collection<Foo> getAllFoos().

La plupart des programmeurs Java de Java) n’y penseraient pas avant de mettre en œuvre getAllFoos() comme allouant un nouveau ArrayList<Foo>, En le renseignant avec tous les éléments de zorkingFoos et Je me réjouis de penser qu'environ 30% de tous les cycles d'horloge consommés par Java s'exécutant sur des millions de machines de la planète ne font que créer de telles copies inutiles de ArrayLists qui sont microsecondes collectées après leur création.

La solution à ce problème consiste bien entendu à sous-estimer la collection. Voici le meilleur moyen de le faire:

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

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

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

La variable intermédiaire result est nécessaire en raison d'une perversion du langage Java:

  • return (List<T>)list; produit une exception "distribution non contrôlée"; 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 d'utiliser @SuppressWarnings Sur une instruction return, il semble qu'il soit correct de l'utiliser pour une affectation, de sorte que la variable "résultat" supplémentaire résout ce problème. (Cela devrait être optimisé par le compilateur ou par le JIT de toute façon.)

1
Mike Nakis

Il s’agit de l’élément de code complet utilisant Generics pour convertir la liste des sous-classes en super-classes.

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

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

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

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

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

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

Quelque chose comme ça devrait aussi marcher:

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 ..

0
olivervbk