Downcasting automático inferindo o tipo

11

Em java, você deve converter explicitamente para fazer o downcast de uma variável

public class Fruit{}  // parent class
public class Apple extends Fruit{}  // child class

public static void main(String args[]) {
    // An implicit upcast
    Fruit parent = new Apple();
    // An explicit downcast to Apple
    Apple child = (Apple)parent;
}

Existe alguma razão para esse requisito, além do fato de que o java não faz nenhuma inferência de tipo?

Existem "truques" na implementação de downcast automático em um novo idioma?

Por exemplo:

Apple child = parent; // no cast required 
Sam Washburn
fonte
Você está dizendo que, se o compilador puder inferir que o objeto que está sendo baixado é sempre do tipo correto, você poderá não expressar explicitamente o downcast? Mas, dependendo da versão do compilador e quanta inferência de tipo ele pode fazer, alguns programas podem não ser válidos ... bugs no inferenciador de tipo podem impedir a gravação de programas válidos, etc ... não faz sentido introduzir algumas informações especiais. caso isso dependa de muitos fatores, não seria intuitivo para os usuários se eles precisassem continuar adicionando ou removendo transmissões sem motivo a cada versão.
Bakuriu 18/10/16

Respostas:

24

Os upcasts sempre são bem-sucedidos.

Os downcasts podem resultar em um erro de tempo de execução, quando o tipo de tempo de execução do objeto não é um subtipo do tipo usado na conversão.

Como a segunda é uma operação perigosa, a maioria das linguagens de programação digitadas exige que o programador a solicite explicitamente. Essencialmente, o programador está dizendo ao compilador "confie em mim, eu sei melhor - isso estará OK em tempo de execução".

Quando se trata de sistemas de tipos, os upcasts colocam o ônus da prova no compilador (que deve ser verificado estaticamente), os downcasts colocam o ônus da prova no programador (que precisa pensar muito sobre isso).

Alguém poderia argumentar que uma linguagem de programação projetada corretamente proibiria completamente os downcasts ou forneceria alternativas de conversão segura, por exemplo, retornar um tipo opcional Option<T>. Muitas linguagens difundidas, no entanto, escolheram a abordagem mais simples e pragmática de simplesmente retornar Te gerar um erro.

No seu exemplo específico, o compilador poderia ter sido projetado para deduzir que parenté realmente uma Appleanálise estática simples e permitir a conversão implícita. No entanto, em geral, o problema é indecidível, portanto, não podemos esperar que o compilador execute muita mágica.

chi
fonte
1
Apenas para referência, um exemplo de uma linguagem que faz downcasts para opcionais é Rust, mas é porque ela não tem herança verdadeira, apenas um "qualquer tipo" .
Kroltan
3

Normalmente, o downcasting é o que você faz quando o conhecimento estaticamente conhecido que o compilador tem sobre o tipo de algo é menos específico do que o que você sabe (ou pelo menos espera).

Em situações como o seu exemplo, o objeto foi criado como um Applee, em seguida, esse conhecimento foi descartado, armazenando a referência em uma variável do tipo Fruit. Então você deseja usar a mesma referência que Appleoutra vez.

Como as informações foram descartadas apenas "localmente", com certeza, o compilador pode manter o conhecimento que parenté realmente um Apple, mesmo que seu tipo declarado seja Fruit.

Mas geralmente ninguém faz isso. Se você deseja criar Applee usá-lo como Apple, armazene-o em uma Applevariável, não em uma Fruit.

Quando você tem um Fruite deseja usá-lo como um Apple, geralmente significa que você obteve Fruitalguns meios que geralmente poderiam retornar qualquer tipo de Fruit, mas, nesse caso, você sabe que era um Apple. Quase sempre você não apenas o construiu, foi aprovado por algum outro código.

Um exemplo óbvio é se eu tenho uma parseFruitfunção que pode transformar seqüências de caracteres como "maçã", "laranja", "limão" etc. na subclasse apropriada; geralmente todos nós (e o compilador) pode saber sobre esta função é que ele retorna algum tipo de Fruit, mas se eu chamar parseFruit("apple"), em seguida, eu sei que vai chamar um Applee pode querer usar Applemétodos, para que eu pudesse abatido.

Novamente, um compilador suficientemente inteligente pode descobrir isso aqui, incorporando o código-fonte parseFruit, pois estou chamando-o com uma constante (a menos que esteja em outro módulo e tenhamos compilação separada, como em Java). Mas você deve poder ver facilmente como exemplos mais complicados envolvendo informações dinâmicas podem se tornar mais difíceis (ou até impossíveis!) Para o compilador verificar.

Em códigos realistas, os downcasts geralmente ocorrem onde o compilador não pode verificar se o downcast é seguro usando métodos genéricos, e não em casos simples, como imediatamente após um upcast jogar fora as mesmas informações de tipo que estamos tentando recuperar por downcasting.

Ben
fonte
3

É uma questão de onde você deseja desenhar a linha. Você pode criar um idioma que detecte a validade do downcast implícito:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    // An implicit downcast to Apple 
    Apple child = parent; 
}

Agora, vamos extrair um método:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    eat(parent);
}
public static void eat(Fruit parent) { 
    // An implicit downcast to Apple 
    Apple child = parent; 
}

Ainda estamos bem. A análise estática é muito mais difícil, mas ainda é possível.

Mas o problema aparece no momento em que alguém acrescenta:

public static void causeTrouble() { 
    // An implicit upcast 
    Fruit trouble = new Orange();
    eat(trouble);
}

Agora, onde você deseja gerar o erro? Isso cria um dilema, pode-se dizer que está o problema Apple child = parent;, mas isso pode ser refutado por "Mas funcionou antes". Por outro lado, a adição eat(trouble);causou o problema ", mas o objetivo principal do polimorfismo é permitir exatamente isso.

Nesse caso, você pode fazer parte do trabalho do programador, mas não pode fazê-lo completamente. Quanto mais você o leva antes de desistir, mais difícil será explicar o que deu errado. Portanto, é melhor interromper o mais rápido possível, de acordo com o princípio de relatar erros com antecedência.

BTW, em Java, o downcast que você descreveu não é realmente um downcast. Este é um elenco geral, que também pode lançar maçãs para laranjas. Então, tecnicamente falando, a idéia do @ chi já está aqui, não há downcasts em Java, apenas "everycasts". Seria bom projetar um operador de downcast especializado que gerasse um erro de compilação quando o tipo de resultado não puder ser encontrado a jusante do tipo de argumento. Seria uma boa idéia tornar o "everycast" muito mais difícil de usar, a fim de desencorajar os programadores de usá-lo sem uma boa razão. A XXXXX_cast<type>(argument)sintaxe do C ++ vem à mente.

Agent_L
fonte