Vamos dar um exemplo. Digamos que você tenha um código assim:
try {
x = 0 / 0;
} catch (Exception ex) {
System.out.println ("SH*T HAPPENS");
} catch (RuntimeException ex) {
System.out.println ("Tio, deu problema. Que que foi?");
} catch (ArithmeticException ex) {
System.out.println ("dividiu por zero, hein?");
}
Quando você divide 0 por 0, deve ocorrer um ArithmeticException (que é uma subclasse de RuntimeException, que é uma subclasse de Exception).
Se o programa acima fosse válido, você teria uma mensagem “SH*T HAPPENS” (que é o que você não está esperando, na verdade) porque os catches são processados na ordem em que foram escritos (não na ordem que você pensou - compilador não lê pensamentos), e nesse caso, como uma ArithmeticException é uma Exception, então você acabaria tratando antes do desejado.
Como o pessoal que definiu a linguagem pensou um pouquinho, a definição da linguagem diz que isso é ilegal, porque sua intenção é tratar uma ArithmeticException, não uma Exception genérica. Então você é forçado a especificar primeiro as exceções mais detalhadas (ArithmeticException) e por último as mais abrangentes (Exception).
try {
x = 0 / 0;
} catch (ArithmeticException ex) {
System.out.println ("dividiu por zero, hein?");
} catch (RuntimeException ex) {
System.out.println ("Tio, deu problema. Que que foi?");
} catch (Exception ex) {
System.out.println ("SH*T HAPPENS");
}