Considera questo esempio:

codice:
Animale animal = new Cane();
animal.stampa();

Cane cane = (Cane) animal; // Questo cast è consentito
cane.stampa();
		
Animale anim = new Animale();
Cane can = (Cane) anim; // Questo cast causa un'eccezione a runtime
can.stampa();
Per quantop riguarda questo:

Animale animal = new Animale();
Cane dog = new Cane();
animal = dog;

non hai trasformato animal in un oggetto di tipo Cane come affermi. In realtà (java lavora per riferimenti e non per valore) hai assegnato ad animal lo stesso riferimento di dog per cui entrambe le variabili puntano allo stesso riferimento.