2 + 2 = 5?

Rode o programa abaixo e me expliquem por que é que 2 + 2 = 5.

import java.lang.reflect.*;

class DontMessWithImmutableClasses {
    public static void main(String[] args) 
        throws IllegalAccessException, NoSuchFieldException {
        // 2+2 = 5?
        Field f = Integer.class.getDeclaredField ("value");
        f.setAccessible (true);
	Integer two = 2;
	f.set (two, 3);
	System.out.printf ("The value of two is now '%d' %n", two);
	Integer x;
	x = 2;
	x = x + 2;
	System.out.printf ("The value of x is now '%d' %n", x); // prints '5'
    }
}

Atenção, pessoal mais desesperado: isso não cai na certificação. É só uma curiosidade, e só deve funcionar a partir do Java 5.0.

Mas escrevi isso só para desestimulá-los a tentar usar wrappers como classes mutáveis; isso pode dar confusão, como a que vocês viram acima.

Foi por causa dessa atribuição aqui?

f.set (two, 3);

A linha está mudando o valor para 3.

Você muda o valor de 2 no cache, é isso?

E o fonte compilado está correto… nem podemos culpar o compilador…

public static void main(String args[]) throws IllegalAccessException, NoSuchFieldException { Field f = java.lang.Integer.getDeclaredField("value"); f.setAccessible(true); Integer two = Integer.valueOf(2); f.set(two, Integer.valueOf(3)); System.out.printf("The value of two is now '%d' %n", new Object[] { two }); Integer x = Integer.valueOf(2); x = Integer.valueOf(x.intValue() + 2); System.out.printf("The value of x is now '%d' %n", new Object[] { x }); }

AahHAahAHa
Mto boa!!

Sim senhor.
Mas o curioso é ver que o resultado é 5 e não 6.
Como vocês viram, só descompilando é que dá para entender por quê.

[quote=thingol]Rode o programa abaixo e me expliquem por que é que 2 + 2 = 5.

[code]
import java.lang.reflect.*;

class DontMessWithImmutableClasses {
public static void main(String[] args)
throws IllegalAccessException, NoSuchFieldException {
// 2+2 = 5?
Field f = Integer.class.getDeclaredField (“value”);
f.setAccessible (true);
Integer two = 2;
f.set (two, 3);
System.out.printf (“The value of two is now ‘%d’ %n”, two);
Integer x;
x = 2;
x = x + 2;
System.out.printf (“The value of x is now ‘%d’ %n”, x); // prints ‘5’
}
}
[/code][/quote]

Bem, deixa eu ver se entendi:

Integer two = 2;

Já que wrappers são imuatáveis, esse trecho deve criar uma “instância controlada” de Integer com o valor 2 de de referência de controle 2.

f.set (two, 3);

Esse trecho altera o valor para 3 da intância controlada de referência 2.

x = 2;

Recupera a instância de referência 2;

x = x + 2;

Cria uma nova instância de Integer. O valor vem do valor do objeto de referência 2 (com valor 3) mais 2;

Sendo assim:
Valor da referência 2, que é 3, mas 2 = 5.

Estou certo?

[]'s
JL

Que massa, 128 pra cima não funciona… rs

Reflection é traiçoeiro.

Sim senhor.
Mas o curioso é ver que o resultado é 5 e não 6.
Como vocês viram, só descompilando é que dá para entender por quê.[/quote]

Só discordando do “só descompilando”.

Operações ariteméticas sempre são feitas com primitivos. Logo, o uso de primitivo em x + 2 é regra. Logo o valor tem que ser trasnformado de integer para int , somado a 2 e voltado a integer. é por isso que nunca se deve usar wrapper em calculos. Auto-(un)boxing não é a pedra filosofal

A outra coisa é saber que os wrapper primitivos fazem cache dos valores mais usado invocados via valueOf(). Ok, isso não é tão regra, mas isso vc descobre pelo primeiro pedaço do codigo.

O ponto é que não ha necessidade de descompilar para saber o resultado.

Wrappers costumam ser imutáveis, a menos que você force a natureza deles e use indevidamente reflection, como fiz acima.

O trecho acima mexe na instância do Integer que está no cache (não se esqueça - a JVM tem um cache de 256 Integers, que vão de -128 a +127), alterando diretamente o valor do campo “value”, que era 2 e passou a ser 3.

Na verdade, x = 2 é uma abreviação para x = Integer.valueOf (2), ou seja, ele pega no cache de 256 integers o Integer que seria correspondente ao valor 2. Como fizemos uma agressão ao meio ambiente, aham, como mudamos o valor do campo “value” para 3, então ele pegou um Integer cujo valor agora é 3. Que nojo!

A linha acima é uma abreviação para
x = Integer.valueOf (x.intValue() + 2). Como x.intValue() retorna 3 (devido à agressão ao meio ambiente), então 3 + 2 = 5, e então x recebe um Integer cujo value é 5.

Vejam como a explicação é complicada.

Moral da história: não abusem de reflection para tornar classes imutáveis em “mutáveis” na marra. Você pode acabar tendo problemas difíceis de entender.

esse cache na verdade é uma especie de mapa que possui um indice com o valor bruto primitivo e valorado a uma referencia wrapper correto?
e é ele que traz a performance para o autoboxing!

Basta olhar o fonte de java.lang.Integer, que está no arquivo /java/lang/Integer.java dentro de src.zip que está no diretório do JDK.

Lá você pode ver que existe um array estático de Integer, com 256 posições, contendo valores pré-alocados de Integer que vão de -128 a +127.

O método valueOf (que é chamado pelo autoboxing) faz mais ou menos isto aqui:

variável estática array: Integer [256];
...

se n < -128 ou n > 127 então
   retorne new Integer (n);
senão
   retorne array [n + 128];
fim se;

é isso mesmo, logo se o objeto( indice n+128 ) sofreu alteração(via reflection) o resultado é o inesperado 2 + 2 = 5

que divertido isso… da pra se perder legal se usado em uma aplicação.

E é por isso que você deve evitar mexer em campos privados usando reflection. Alguma coisa pode ocorrer…

O mesmo caso ocorre aqui.

		Field field = String.class.getDeclaredField("value");
		field.setAccessible(true);
		String s = "a";
		field.set(s, "b".toCharArray());
		String x = "a";
		System.out.println(x + new String("a"));

Animal…hehe :lol: