¿Por qué un <tipo> de Lista absorbe el elemento de no tipo en el tiempo de compilación y ejecución?

Tengo esta demostración, que no necesito una solución particular con una arquitectura rediseñada, pero solo entiendo por qué se comporta de esa manera y cualquier cosa que me falta para evitarla. Me pregunto por qué

El compilador permite la inserción de un elemento que no es el tipo de la lista en la lista

La excepción ClassCast se produce cuando intentamos obtener el elemento en lugar de cuando lo presionamos.

import Test.*; //Inner classes
import java.util.List;
import java.util.ArrayList;

public class Test<E extends Object> {

    private List<E> list = new ArrayList<E>();
    public Test() {}

    public static void main(String[] args) {
        Test<String> a = new Test<String>();
        a.addElement(new String());
        a.addElement(new Integer(0)); // No complain in comp/run-time, I dont understand why CastException is not thrown here
        String class1 = a.getElement(0); 
        String class2 = a.getElement(1); //No complain in comp but ClassCastException: Integer cannot be cast to String
        //Integer class3 = a.getElement(1); //Compilation error
    }

    public void addElement (Object node) { list.add((E) node); } //No complain in comp/run-time
    public E getElement(int index)       { return list.get(index); }
}

¿Qué podría ser una solución? Observe la línea addElement donde necesito pasar una SuperClass, en lugar del tipo E. Esto es necesario para mi arquitectura, esto es solo una simple demostración simulada. Pero de todos modos, la conversión al tipo E debería actuar como se desea y lanzar una CastExeption en tiempo de ejecución, ¿no debería?

Respuestas a la pregunta(2)

Su respuesta a la pregunta