Integers are reference types. They are different from ints, which are primitive types.
The == operator, when applied to reference types, checks for reference equality. Essentially, this means that two references are == if and only if they refer to the same object in memory. The .equals() operator (i1.equals(i2)) is used to check if the two objects hold the same value.
However, there are certain pre-existing objects of certain reference types with specific values. If the compiler identifies certain constants in the code, it will, rather than create a new object to hold that constant, point that reference to the pre-created object. In the following code, the compiler does not create a new object for i1 and i2, rather, it sets them both to the pre-existing Integer that holds that value. So i1 and i2 refer to the same object, and therefore are "==" to each other.
Integer i1 = 50;
Integer i2 = 50;
Integer i3 = new Integer(50);
Integer i4 = new Integer(50);
System.out.println("does i1==i2? " + (i1==i2));
System.out.println("does i1==i3? " + (i1==i3));
System.out.println("does i3==i4? " + (i1==i4));
Will output
does i1==i2? true
does i1==i3? false
does i3==i4? false