Por que o javac permite alguns elencos impossíveis e outros não?

52

Se eu tentar converter a Stringpara a java.util.Date, o compilador Java detectará o erro. Então, por que o compilador não sinaliza o seguinte como erro?

List<String> strList = new ArrayList<>();                                                                      
Date d = (Date) strList;

Obviamente, a JVM lança um ClassCastExceptionem tempo de execução, mas o compilador não o sinaliza.

O comportamento é o mesmo com o javac 1.8.0_212 e 11.0.2.

Mike Woinoski
fonte
2
Nada de especial por Listaqui. Date d = (Date) new Object();
Elliott Frisch
11
Eu tenho jogado com um arduino ultimamente. Adoraria um compilador que não aceitasse com alegria nenhum elenco e depois o fizesse com resultados totalmente imprevisíveis. String para inteiro? Coisa certa! Duplo para inteiro? Sim senhor! String para booleano? Pelo menos esse se torna principalmente falso ...
Stian Yttervik 22/03
@ ElliottFrisch: Há uma relação óbvia de herança entre Data e Objeto, mas não há relação entre Data e Lista. Então, eu esperava que o compilador sinalizasse esse elenco, da mesma forma que sinalizaria um elenco de String para Date. Mas, como Zabuza explica em sua excelente resposta, List é uma interface, portanto o elenco seria legal se strListfosse uma instância de uma classe que implemente List.
Mike Woinoski 22/03
Essa é uma pergunta frequentemente recorrente e tenho certeza de que já vi várias duplicatas dela. É basicamente a versão reversa do fortemente relacionado: stackoverflow.com/questions/21812289/…
Hulk
11
@StianYttervik -permitive é o que está fazendo isso. Ative os avisos do compilador.
bobsburner 23/03

Respostas:

86

O elenco é tecnicamente possível. Não é possível provar facilmente pelo javac que não é assim no seu caso e o JLS realmente define isso como um programa Java válido, portanto, sinalizar um erro seria incorreto.

Isso ocorre porque Listé uma interface. Então, você poderia ter uma subclasse de um Dateque realmente implementa Listdisfarçada como Listaqui - e, em seguida, convertê-la Dateseria perfeitamente aceitável. Por exemplo:

public class SneakyListDate extends Date implements List<Foo> {
    ...
}

E depois:

List<Foo> list = new SneakyListDate();
Date date = (Date) list; // This one is valid, compiles and runs just fine

Detectar tal cenário nem sempre é possível, pois exigiria informações de tempo de execução se a instância vier de, por exemplo, um método. E mesmo que isso exigiria muito mais esforço para o compilador. O compilador apenas evita lançamentos que são absolutamente impossíveis, porque não há como a árvore de classes corresponder. O que não é o caso aqui, como visto.

Observe que o JLS requer que seu código seja um programa Java válido. No 5.1.6.1. Conversão de referência restrita permitida diz:

Existe uma conversão de referência de restrição de tipo Sde referência para tipo de referência, Tse todas as seguintes forem verdadeiras :

  • [...]
  • Um dos seguintes casos se aplica :
    • [...]
    • Sé um tipo de interface, Té um tipo de classe e Tnão nomeia uma finalclasse.

Portanto, mesmo que o compilador possa descobrir que seu caso é realmente comprovadamente impossível, não é permitido sinalizar um erro porque o JLS o define como um programa Java válido.

Só seria permitido mostrar um aviso.

Zabuzard
fonte
16
E vale a pena notar, que o motivo pelo qual isso ocorre com String é que String é final, portanto o compilador sabe que nenhuma classe pode estendê-lo.
MTilsted 22/03
5
Na verdade, eu não acho que é a "finalidade" de String que faz myDate = (Date) myStringfalhar. Usando a terminologia JLS, a instrução tenta converter de S(the String) para T(the Date). Aqui, Snão é um tipo de interface, portanto, a condição JLS citada acima não se aplica. Como exemplo, tente converter um calendário para uma data e você receberá um erro do compilador, mesmo que nenhuma classe seja final.
Mike Woinoski 22/03
11
Não sei se estou decepcionado ou não, o compilador não pode fazer análises estáticas suficientes para provar que strList só pode ser do tipo ArrayList.
Joshua
3
O compilador não está proibido de verificar. Mas é proibido chamá-lo de erro. Isso tornaria o compilador não compatível. (Veja minha resposta ...)
Stephen C
3
Para adicionar um pouco de linguagem, o compilador precisaria provar que o tipo Date & Listé inabitável , não basta provar que está desabitado atualmente (pode ser no futuro).
Polygnome 23/03
15

Vamos considerar uma generalização do seu exemplo:

List<String> strList = someMethod();       
Date d = (Date) strList;

Essas são as principais razões pelas quais Date d = (Date) strList;não há erro de compilação.

  • A razão intuitiva é que o compilador não (em geral) conhece o tipo preciso do objeto retornado por essa chamada de método. É possível que, além de ser uma classe que implemente List, ela também seja uma subclasse de Date.

  • O motivo técnico é que a Java Language Specification "permite" a conversão de referência restritiva que corresponde a esse tipo de conversão . De acordo com o JLS 5.1.6.1 :

    "Existe uma conversão de referência de restrição de tipo Sde referência para tipo de referência, Tse todas as seguintes forem verdadeiras:"

    ...

    5) " Sé um tipo de interface, Té um tipo de classe e Tnão nomeia uma finalclasse."

    ...

    Em um local diferente, o JLS também diz que uma exceção pode ser lançada em tempo de execução ...

    Observe que a determinação do JLS 5.1.6.1 é baseada apenas nos tipos declarados das variáveis ​​envolvidas, e não nos tipos de tempo de execução reais. No caso geral, o compilador não conhece e não pode conhecer os tipos de tempo de execução reais.


Então, por que o compilador Java não pode descobrir que o elenco não funcionará?

  • No meu exemplo, a someMethodchamada pode retornar objetos com vários tipos. Mesmo que o compilador tenha sido capaz de analisar o corpo do método e determinar o conjunto preciso de tipos que podem ser retornados, não há nada para impedir que alguém o altere para retornar tipos diferentes ... depois de compilar o código que o chama. Essa é a razão básica pela qual o JLS 5.1.6.1 diz o que diz.

  • No seu exemplo, um compilador inteligente pode descobrir que o elenco nunca pode ter sucesso. E é permitido emitir um aviso em tempo de compilação para apontar o problema.

Então, por que um compilador inteligente não pode dizer que isso é um erro?

  • Porque o JLS diz que este é um programa válido. Período. Qualquer compilador que chamasse isso de erro não seria compatível com Java.

  • Além disso, qualquer compilador que rejeite programas Java que o JLS e outros compiladores dizem que é válido é um impedimento à portabilidade do código-fonte Java.

Stephen C
fonte
4
Promovido o fato de que, após compilar a classe de chamada, a implementação da função chamada pode mudar , portanto, mesmo que seja possível em tempo de compilação, com a implementação atual do chamado , que a conversão seja impossível, isso pode não ser o caso em tempos de execução posteriores quando o chamado foi alterado ou substituído.
Peter - Restabelece Monica
2
Voto positivo por destacar o problema de portabilidade que seria introduzido se um compilador tentar ser muito inteligente.
Mike Woinoski 22/03
2

5.5.1 Carcaça do tipo de referência:

Dado um tipo de referência em tempo de compilação S(origem) e um tipo de referência em tempo de compilação T(destino), existe uma conversão de conversão de Spara Tse nenhum erro em tempo de compilação ocorrer devido às regras a seguir.

[...]

If Sé um tipo de interface:

  • [...]

  • Se Té uma classe ou tipo de interface que não é definitiva, em seguida, se existe um supertipo Xde T, e um supertipo Yde S, de tal modo que ambos Xe Ysão tipos parametrizados comprovadamente distintas, e que os rasuras de Xe Ysão o mesmo, um erro de tempo de compilação ocorre.

    Caso contrário, o elenco é sempre legal no momento da compilação (porque, mesmo Tque não implemente S, uma subclasse de Tpoder).

List<String>é Se Dateé Tno seu caso.

Oleksandr Pyrohov
fonte