Essa é uma dúvida bem comum ao trabalhar com polimorfismo com tipagem estática.
Basicamente, temos que lembrar que as variáveis em Java guardam referências a Objetos e que o Compilador análise o seu código de acordo com o tipo declarado e não o tipo definido. Nesse caso, vc faz ambos em uma linha, vamos separar:
Funcionario f; // Declaração: existe uma referência f e ela aponta para um objeto do tipo Funcionario
f = new Gerente();
/*
* Definição: f agora aponta para um objeto do tipo Gerente (que é um subtipo de Funcionario)
* mas como declarado acima, o compilador só pode enxergar métodos e propriedades
* declarados na classe Funcionario
*/
A ideia é criar classes o mais genérico possível a fim de reaproveitar ao máximo o nosso código. Exemplo é o getBonificacao()
, ele é declarado na classe Funcionario
, então todas as subclasses o tem (é assim que o compilador vê). Uma subclasse pode sobrescrever (Override) um método, ou seja, redefini-lo. Assim, quando o método sobrescrito é invocado pela subclasse, o comportamento será o definido pela subclasse, essa é a vantagem do Polimorfismo. Podemos ter um mesmo método implementado de diversas formas. Por isso, classes abstratas e interfaces são muito usadas, elas permitem declarar métodos sem defini-los garantindo o polimorfismo.
Agora, se o método não é declarado na classe Mãe, apenas na filha, o compilador não tem como saber que o tipo mais genérico vai possuir aquele comportamento mais a frente no código. Pra isso, ele teria que analisar nosso código mais a fundo e, isso ele não faz. Mais a frente, será apresentado a Exception
que são erros que podem ocorrer no momento da execução, esses erros o compilador não tem como prever.
Quanto ao fato de poder funcionar Miyashiroo, se o Java tivesse uma tipagem dinâmica como PHP, isso funcionaria sem problemas. Vc até consegue passar a referência para outra variável mais específica usando cast:
Funcionario f = new Gerente();
Gerente g = (Gerente) f;
Nesse caso, pegamos a referência f, criamos um objeto do tipo Gerente. Na segunda linha, passamos a referência para outra variável mas falamos para o compilador: eu sei que a referência f pode ser tratada como Gerente, faça isso. Entretanto, se fizermos isso:
Funcionario f = new Funcionario();
Gerente g = (Gerente) f;
O código também compila, mas na hora de rodar vai dar erro. Porque forçamos algo na etapa de compilação que não funciona no momento em que o código é executado.
Espero ter respondido, se tiver mais dúvidas. Estamos por aqui.
Abraços e bons estudos.