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
object-oriented
type-inference
language-design
Sam Washburn
fonte
fonte
Respostas:
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 retornarT
e gerar um erro.No seu exemplo específico, o compilador poderia ter sido projetado para deduzir que
parent
é realmente umaApple
aná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.fonte
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
Apple
e, em seguida, esse conhecimento foi descartado, armazenando a referência em uma variável do tipoFruit
. Então você deseja usar a mesma referência queApple
outra vez.Como as informações foram descartadas apenas "localmente", com certeza, o compilador pode manter o conhecimento que
parent
é realmente umApple
, mesmo que seu tipo declarado sejaFruit
.Mas geralmente ninguém faz isso. Se você deseja criar
Apple
e usá-lo comoApple
, armazene-o em umaApple
variável, não em umaFruit
.Quando você tem um
Fruit
e deseja usá-lo como umApple
, geralmente significa que você obteveFruit
alguns meios que geralmente poderiam retornar qualquer tipo deFruit
, mas, nesse caso, você sabe que era umApple
. Quase sempre você não apenas o construiu, foi aprovado por algum outro código.Um exemplo óbvio é se eu tenho uma
parseFruit
funçã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 deFruit
, mas se eu chamarparseFruit("apple")
, em seguida, eu sei que vai chamar umApple
e pode querer usarApple
mé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.
fonte
É uma questão de onde você deseja desenhar a linha. Você pode criar um idioma que detecte a validade do downcast implícito:
Agora, vamos extrair um método:
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:
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çãoeat(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.fonte