A maneira mais eficiente de converter a lista <SubClass> na lista <BaseClass>

134

Eu tenho um List<SubClass>que eu quero tratar como um List<BaseClass>. Parece que não deve ser um problema, já que converter um SubClasspara um BaseClassé muito fácil, mas meu compilador reclama que o elenco é impossível.

Então, qual é a melhor maneira de obter uma referência aos mesmos objetos que um List<BaseClass>?

No momento, estou apenas fazendo uma nova lista e copiando a lista antiga:

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

Mas, pelo que entendi, isso precisa criar uma lista totalmente nova. Gostaria de uma referência à lista original, se possível!

Riley Lark
fonte
3
a resposta que você tem aqui: stackoverflow.com/questions/662508/…
lukastymo

Respostas:

175

A sintaxe para esse tipo de atribuição usa um curinga:

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

É importante perceber que a nãoList<SubClass> é intercambiável com a . O código que mantém uma referência à espera que cada item da lista seja a . Se outra parte do código se referir à lista como a , o compilador não reclamará quando um ou for inserido. Mas isso causará um para o primeiro trecho de código, que assume que tudo na lista é a .List<BaseClass>List<SubClass>SubClassList<BaseClass>BaseClassAnotherSubClassClassCastExceptionSubClass

Coleções genéricas não se comportam da mesma forma que matrizes em Java. Matrizes são covariantes; isto é, é permitido fazer o seguinte:

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

Isso é permitido, porque a matriz "conhece" o tipo de seus elementos. Se alguém tentar armazenar algo que não é uma instância da SubClassmatriz (via basesreferência), uma exceção de tempo de execução será lançada.

Coleções genéricas não "conhecem" seu tipo de componente; essas informações são "apagadas" no momento da compilação. Portanto, eles não podem gerar uma exceção de tempo de execução quando ocorre um armazenamento inválido. Em vez disso, a ClassCastExceptionserá gerado em algum ponto distante e difícil de associar no código quando um valor for lido da coleção. Se você prestar atenção aos avisos do compilador sobre segurança de tipo, evitará esses erros de tipo em tempo de execução.

erickson
fonte
Observe que com Arrays, em vez de ClassCastException ao recuperar um objeto que não é do tipo SubClass (ou derivado), você receberá um ArrayStoreException ao inserir.
Axel
Obrigado especialmente pela explicação de por que as duas listas não podem ser consideradas iguais.
Riley Lark
O sistema do tipo Java finge que as matrizes são covariantes, mas não são realmente substituíveis, comprovada pela ArrayStoreException.
Paŭlo Ebermann 8/03/16
38

O erickson já explicou por que você não pode fazer isso, mas aqui estão algumas soluções:

Se você deseja apenas remover elementos de sua lista base, em princípio, seu método de recebimento deve ser declarado como a List<? extends BaseClass>.

Mas se não estiver e você não puder alterá-lo, poderá agrupar a lista com Collections.unmodifiableList(...), o que permite retornar uma Lista de um supertipo do parâmetro do argumento. (Evita o problema de segurança tipográfica lançando UnsupportedOperationException nas tentativas de inserção.)

Paŭlo Ebermann
fonte
Ótimo! não conhecia esse.
precisa
Muito obrigado!
Woland
14

Como o @erickson explicou, se você realmente deseja uma referência à lista original, certifique-se de que nenhum código insira nada nessa lista, se você quiser usá-lo novamente sob sua declaração original. A maneira mais simples de obtê-lo é lançá-lo em uma lista simples e antiga de genéricos:

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

Eu não recomendaria isso se você não souber o que acontece com a Lista e sugeriria que você altere qualquer código que precise da Lista para aceitar a Lista que você possui.

hd42
fonte
3

Abaixo está um trecho útil que funciona. Ele constrói uma nova lista de matrizes, mas a sobrecarga de criação de objeto da JVM é significativa.

Vi outras respostas não necessariamente necessárias.

List<BaseClass> baselist = new ArrayList<>(sublist);
sigirisetti
fonte
1
Obrigado por este trecho de código, que pode fornecer ajuda imediata. Uma explicação adequada melhoraria bastante seu valor educacional, mostrando por que essa é uma boa solução para o problema e a tornaria mais útil para futuros leitores com perguntas semelhantes, mas não idênticas. Por favor edite sua resposta para adicionar explicação, e dar uma indicação do que limitações e premissas se aplicam. Em particular, como isso difere do código na pergunta que faz uma nova lista?
Toby Speight
A sobrecarga não é insignificante, pois também deve (superficial) copiar todas as referências. Como tal, é dimensionado com o tamanho da lista, portanto, é uma operação O (n).
john16384 22/02
2

Perdi a resposta em que você acabou de lançar a lista original, usando um elenco duplo. Então, aqui está a completude:

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

Nada é copiado e a operação é rápida. No entanto, você está enganando o compilador aqui, portanto, certifique-se de não modificar a lista de maneira que subListcomece a conter itens de um subtipo diferente. Ao lidar com listas imutáveis, isso geralmente não é um problema.

john16384
fonte
1

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

jtahlborn
fonte
5
Isso não deve funcionar, pois para esses métodos todos os argumentos e o resultado assumem o mesmo parâmetro de tipo.
Paŭlo Ebermann 22/02
estranho, você está certo. por alguma razão, fiquei com a impressão de que esse método era útil para converter uma coleção genérica. ainda pode ser usado dessa maneira e fornecerá uma coleção "segura" se você quiser usar avisos de supressão.
jtahlborn
1

O que você está tentando fazer é muito útil e acho que preciso fazê-lo com muita frequência no código que escrevo. Um exemplo de caso de uso:

Digamos que temos uma interface Fooe que temos um zorkingpacote ZorkingFooManagerque cria e gerencia instâncias do pacote-privado ZorkingFoo implements Foo. (Um cenário muito comum.)

Portanto, ZorkingFooManagerprecisa conter um, private Collection<ZorkingFoo> zorkingFoosmas precisa expor a public Collection<Foo> getAllFoos().

A maioria dos programadores de Java não pensaria duas vezes antes de implementar getAllFoos()como alocar um novo ArrayList<Foo>, preenchê-lo com todos os elementos zorkingFoose retorná-lo. Gosto de pensar que cerca de 30% de todos os ciclos de relógio consumidos pelo código java executado em milhões de máquinas em todo o planeta não fazem nada além de criar cópias inúteis de ArrayLists que são microssegundos após a criação.

A solução para esse problema é, obviamente, fazer o downcast da coleção. Aqui está a melhor maneira de fazer isso:

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

O que nos leva à castList()função:

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

A resultvariável intermediária é necessária devido a uma perversão da linguagem java:

  • return (List<T>)list;produz uma exceção "elenco não verificado"; Por enquanto, tudo bem; mas então:

  • @SuppressWarnings( "unchecked" ) return (List<T>)list; é um uso ilegal da anotação de suprimir avisos.

Portanto, mesmo que não seja kosher usar @SuppressWarningsem uma returndeclaração, aparentemente é bom usá-la em uma atribuição, portanto a variável extra "resultado" resolve esse problema. (Deve ser otimizado para fora pelo compilador ou pelo JIT de qualquer maneira.)

Mike Nakis
fonte
0

Algo assim também deve funcionar:

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

Realmente não sei por que o clazz é necessário no Eclipse.

olivervbk
fonte
0

Que tal lançar todos os elementos. Ele criará uma nova lista, mas fará referência aos objetos originais da lista antiga.

List<BaseClass> convertedList = listOfSubClass.map(x -> (BaseClass)x).collect(Collectors.toList());
drordk
fonte
Este é o código completo que funciona para converter a lista de subclasses em superclasses.
kanaparthikiran
0

Este é o trecho de código completo usando Genéricos, para converter a lista de subclasses em superclasses.

Método de chamada que passa o tipo de subclasse

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

Método Callee que aceita qualquer subtipo da classe base

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