A maneira mais eficiente de transmitir List para List

Eu tenho uma List que eu quero tratar como uma List . Parece que não deveria ser um problema, já que SubClass uma SubClass em uma BaseClass é muito fácil, mas meu compilador reclama que o casting é impossível.

Então, qual é a melhor maneira de obter uma referência aos mesmos objects que uma List ?

Agora eu estou apenas fazendo uma nova lista e copiando a lista antiga:

 List convertedList = new ArrayList(listOfSubClass) 

Mas, pelo que entendi, é preciso criar uma lista inteiramente nova. Eu gostaria de uma referência à lista original, se possível!

A syntax para este tipo de atribuição usa um caractere curinga:

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

É importante perceber que uma List não é intercambiável com uma List . O código que retém uma referência à List espera que cada item da lista seja uma SubClass . Se outra parte do código se referir à lista como uma List , o compilador não irá reclamar quando um BaseClass ou AnotherSubClass for inserido. Mas isso causará um ClassCastException para a primeira parte do código, que assume que tudo na lista é uma SubClass .

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

 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 seja uma instância de SubClass na matriz (por meio da referência de bases ), uma exceção de tempo de execução será lançada.

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

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

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

Mas se não for e você não pode alterá-lo, você pode quebrar a lista com Collections.unmodifiableList(...) , que permite retornar uma lista de um supertipo do parâmetro do argumento. (Evita o problema de segurança de tipos lançando UnsupportedOperationException em tentativas de inserção.)

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

 List baseList = (List)new ArrayList(); 

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

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

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 Foo e temos um pacote zorking que possui um ZorkingFooManager que cria e gerencia instâncias de ZorkingFoo implements Foo privado para ZorkingFoo implements Foo . (Um cenário muito comum)

Portanto, o ZorkingFooManager precisa conter uma private Collection zorkingFoos mas precisa expor uma public Collection getAllFoos() .

A maioria dos programadores java não pensaria duas vezes antes de implementar getAllFoos() como alocando um novo ArrayList , preenchendo-o com todos os elementos de zorkingFoos e retornando-o. Eu gosto de pensar que cerca de 30% de todos os ciclos de clock consumidos pelo código java rodando em milhões de máquinas em todo o planeta não estão fazendo nada além de criar cópias inúteis de ArrayLists, que são microssegundos coletores de lixo após sua criação.

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

 static  List downCastList( List list ) { return castList( list ); } 

O que nos leva à function castList() :

 static  List castList( List list ) { @SuppressWarnings( "unchecked" ) List result = (List)list; return result; } 

A variável de result intermediário é necessária devido a uma perversão da linguagem java:

  • return (List)list; produz uma exceção “unchecked cast”; Por enquanto, tudo bem; mas então:

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

Portanto, mesmo que não seja kosher usar @SuppressWarnings em uma instrução de return , aparentemente é bom usá-lo em uma atribuição, então a variável “resultado” extra resolve esse problema. (Deve ser otimizado pelo compilador ou pelo JIT, de qualquer maneira.)

Algo como isso deve funcionar também:

 public static  List convertListWithExtendableClasses( final List< ? extends T> originalList, final Class clazz ) { final List newList = new ArrayList<>(); for ( final T item : originalList ) { newList.add( item ); }// for return newList; } 

Não sei realmente porque o clazz é necessário no Eclipse.

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

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

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

Eu vi outras respostas não necessariamente complicadas.

 List baselist = new ArrayList<>(sublist);