Collections.emptyList () retorna uma lista <Object>?

269

Estou com problemas para navegar na regra do Java para inferir parâmetros de tipo genérico. Considere a seguinte classe, que possui um parâmetro de lista opcional:

import java.util.Collections;
import java.util.List;

public class Person {
  private String name;
  private List<String> nicknames;

  public Person(String name) {
    this(name,Collections.emptyList());
  }

  public Person(String name,List<String> nicknames) {
    this.name = name;
    this.nicknames = nicknames;
  }
}

Meu compilador Java fornece o seguinte erro:

Person.java:9: The constructor Person(String, List<Object>) is undefined

Mas Collections.emptyList()retorna tipo <T> List<T>, não List<Object>. Adicionar um elenco não ajuda

public Person(String name) {
  this(name,(List<String>)Collections.emptyList());
}

rendimentos

Person.java:9: inconvertible types

Usando em EMPTY_LISTvez deemptyList()

public Person(String name) {
  this(name,Collections.EMPTY_LIST);
}

rendimentos

Person.java:9: warning: [unchecked] unchecked conversion

Considerando que a seguinte alteração faz com que o erro desapareça:

public Person(String name) {
  this.name = name;
  this.nicknames = Collections.emptyList();
}

Alguém pode explicar qual regra de verificação de tipo estou enfrentando aqui e a melhor maneira de contornar isso? Neste exemplo, o exemplo de código final é satisfatório, mas com classes maiores, eu gostaria de poder escrever métodos seguindo esse padrão de "parâmetro opcional" sem duplicar o código.

Para crédito extra: quando é apropriado usar EMPTY_LISTao invés de emptyList()?

Chris Conway
fonte
1
Para todas as perguntas relacionadas ao Java Generics, recomendo " Java Generics and Collections " de Maurice Naftalin, Philip Wadler.
Julien Chastang

Respostas:

447

O problema que você está enfrentando é que, embora o método emptyList()retorne List<T>, você não forneceu o tipo, então o padrão é retornar List<Object>. Você pode fornecer o parâmetro type e fazer com que seu código se comporte conforme o esperado, assim:

public Person(String name) {
  this(name,Collections.<String>emptyList());
}

Agora, quando você está realizando uma tarefa direta, o compilador pode descobrir os parâmetros de tipo genérico para você. É chamado de inferência de tipo. Por exemplo, se você fez isso:

public Person(String name) {
  List<String> emptyList = Collections.emptyList();
  this(name, emptyList);
}

a emptyList()chamada retornaria corretamente a List<String>.

InverseFalcon
fonte
12
Entendi. Vindo do mundo da ML, é estranho para mim que Java não possa inferir o tipo correto: o tipo de parâmetro formal e o tipo de retorno de emptyList são claramente unificáveis. Mas acho que o tipo inferenciador só pode dar "pequenos passos".
22430 Chris Conway
5
Em alguns casos simples, pode parecer possível para o compilador inferir o parâmetro de tipo ausente nesse caso - mas isso pode ser perigoso. Se existirem várias versões do método com parâmetros diferentes, você pode acabar chamando o errado. E o segundo não pode sequer existe ainda ...
Bill Michell
13
Essa notação "Coleções. <> String emptyList ()" é realmente estranha, mas faz sentido. Mais fácil que Enum <E estende Enum <E>>. :)
Thiago Chaves
12
O fornecimento de um parâmetro de tipo não é mais necessário no Java 8 (a menos que haja uma ambiguidade em possíveis tipos genéricos).
Vitalii Fedorenko
9
O segundo trecho mostra bem a inferência de tipo, mas não será compilado, é claro. A chamada para thisdeve ser a primeira instrução no construtor.
Arjan 10/07
99

Você quer usar:

Collections.<String>emptyList();

Se você procurar na fonte o que emptyList você vê, ele realmente faz um

return (List<T>)EMPTY_LIST;
carson
fonte
26

o método emptyList possui esta assinatura:

public static final <T> List<T> emptyList()

Que <T>antes de os meios lista de palavras que infere o valor do parâmetro genérico T do tipo de variável, o resultado é atribuído. Então, neste caso:

List<String> stringList = Collections.emptyList();

O valor de retorno é então referenciado explicitamente por uma variável do tipo List<String>, para que o compilador possa descobrir isso. Nesse caso:

setList(Collections.emptyList());

Não há uma variável de retorno explícita para o compilador usar para descobrir o tipo genérico; portanto, o padrão é Object.

Dan Vinton
fonte