Se quiser ficar ainda mais confuso, faça o mesmo exercício mas troque o valor 12 por 1000 e se surpreenda
A explicação aqui é que == envolvendo objetos, sejam eles quais for (Integer, String, ContaCorrente etc) sempre comparam referências. Então quando você dá new você me explicitamente dois objetos diferentes, cada um em sua referência, e aí é óbvio que == vai dar false.
Mas aí você se pergunta: porque o 1o caso dá true? Ele compara valor? Não, nunca. O == sempre compara referências. Só que no primeiro caso as referências são a mesma por uma combinação de fatores, e portanto o resultado é true.
Para entender é importante saber o que faz o autoboxing do Java 5. Ele não dá new explicitamente (ou seja, o 1o caso é diferente do 2o caso). O autoboxing sempre usa Integer.valueOf para embrulhar o valor. Na pratica, essas duas expressoes são equivalentes:
Integer i = 12;
Integer i = Integer.valueOf(12);
Por fim, sabendo disso, se voce ler o javadoc do valueOf vai quer que nem sempre esse método cria novos objetos. Ele pode ter um cache interno de Integers mais usados e apenas devolver a mesma referência se for pedido mais de uma vez. Aqui voce ja deve ter desconfiado que é exatamente o que acontece ao atribuir 12 a duas varias diferentes: ele devolve o mesmo objeto e temos duas referencias para o mesmo objeto.
Quando você usa 1000 o valueOf não utiliza o cache (que vai de -128 a um valor configurado pela propriedade “java.lang.Integer.IntegerCache.high” - caso não exista é usado 127).
Não tenho 100% de certeza agora.
Mas, se não me engano, para Short e Integer, quando os valores estão entre -128 e 127, o resultado da comparação é true;
caso contrário, o resultado é false.
Parece que a JVM faz isso para economizar memória, ou algo parecido.
Como disse no post acima, é mais ou menos verdade. Você pode configurar o valor máximo do cache para o tipo Integer. Para os demais (Short e Long) é usado 127 como valor máximo.