When Using == for a Primitive and a Boxed Value, Is Autoboxing Done, or Is Unboxing Done

When using == for a primitive and a boxed value, is autoboxing done, or is unboxing done

It is defined in the JLS #15.21.1:

If the operands of an equality operator are both of numeric type, or one is of numeric type and the other is convertible (§5.1.8) to numeric type, binary numeric promotion is performed on the operands (§5.6.2).

And JLS #5.6.2:

When an operator applies binary numeric promotion to a pair of operands, each of which must denote a value that is convertible to a numeric type, the following rules apply, in order:

  • If any operand is of a reference type, it is subjected to unboxing conversion
    [...]

So to answer your question, the Integer is unboxed into an int.

In Java, does == Box or Unbox when comparing an object and a constant value?

What you call "a constant value" is an int literal, so its type is int.

JLS 15.21.1 says:

If the operands of an equality operator are both of numeric type, or one is of numeric type and the other is convertible (§5.1.8) to numeric type, binary numeric promotion is performed on the operands.

In your v1 == 1000 test, 1000 is of numeric type and v1 is convertible to numeric type, so binary numeric promotion is performed.

JLS 5.6.2 (Binary numeric promotion) says:

If any operand is of a reference type, it is subjected to unboxing conversion

Hence, the Integer operand - v1 - is unboxed to an int and a comparison of two ints is performed. Therefore the result of the comparison is true.

When you compare two reference types - v1 == v2 - no unboxing takes places, only the references are compared, as written in JLS 15.21.3:

If the operands of an equality operator are both of either reference type or the null type, then the operation is object equality.

Since 1000 is too large to be cached by the Integer cache, b1 and b2 are not referencing the same instance, and therefore the result of the comparison is false.

Java autoboxing and comparison of Objects using operators

Because <, >, >=, and <= are numerical comparison, and thus, the compiler knows it has to do unboxing.

However, == and != always work as reference comparators for non-primitive types.

How does auto boxing/unboxing work in Java?

When in doubt, check the bytecode:

Integer n = 42;

becomes:

0: bipush        42
2: invokestatic #16 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1

So in actuality, valueOf() is used as opposed to the constructor (and the same goes for the other wrapper classes). This is beneficial since it allows for caching, and doesn't force the creation of a new object on each boxing operation.

The reverse is the following:

int n = Integer.valueOf(42);

which becomes:

0: bipush        42
2: invokestatic #16 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: invokevirtual #22 // Method java/lang/Integer.intValue:()I
8: istore_1

i.e. intValue() is used (again, it's analogous for the other wrapper types as well). This is really all auto(un)boxing boils down to.

You can read about boxing and unboxing conversions in JLS §5.1.7 and JLS §5.1.8, respectively.

Why do we use autoboxing and unboxing in Java?

Some context is required to fully understand the main reason behind this.

Primitives versus classes

Primitive variables in Java contain values (an integer, a double-precision floating point binary number, etc). Because these values may have different lengths, the variables containing them may also have different lengths (consider float versus double).

On the other hand, class variables contain references to instances. References are typically implemented as pointers (or something very similar to pointers) in many languages. These things typically have the same size, regardless of the sizes of the instances they refer to (Object, String, Integer, etc).

This property of class variables makes the references they contain interchangeable (to an extent). This allows us to do what we call substitution: broadly speaking, to use an instance of a particular type as an instance of another, related type (use a String as an Object, for example).

Primitive variables aren't interchangeable in the same way, neither with each other, nor with Object. The most obvious reason for this (but not the only reason) is their size difference. This makes primitive types inconvenient in this respect, but we still need them in the language (for reasons that mainly boil down to performance).

Generics and type erasure

Generic types are types with one or more type parameters (the exact number is called generic arity). For example, the generic type definition List<T> has a type parameter T, which can be Object (producing a concrete type List<Object>), String (List<String>), Integer (List<Integer>) and so on.

Generic types are a lot more complicated than non-generic ones. When they were introduced to Java (after its initial release), in order to avoid making radical changes to the JVM and possibly breaking compatibility with older binaries, the creators of Java decided to implement generic types in the least invasive way: all concrete types of List<T> are, in fact, compiled to (the binary equivalent of) List<Object> (for other types, the bound may be something other than Object, but you get the point). Generic arity and type parameter information are lost in this process, which is why we call it type erasure.

Putting the two together

Now the problem is the combination of the above realities: if List<T> becomes List<Object> in all cases, then T must always be a type that can be directly assigned to Object. Anything else can't be allowed. Since, as we said before, int, float and double aren't interchangeable with Object, there can't be a List<int>, List<float> or List<double> (unless a significantly more complicated implementation of generics existed in the JVM).

But Java offers types like Integer, Float and Double which wrap these primitives in class instances, making them effectively substitutable as Object, thus allowing generic types to indirectly work with the primitives as well (because you can have List<Integer>, List<Float>, List<Double> and so on).

The process of creating an Integer from an int, a Float from a float and so on, is called boxing. The reverse is called unboxing. Because having to box primitives every time you want to use them as Object is inconvenient, there are cases where the language does this automatically - that's called autoboxing.

Why wrapped type unboxed instead of boxing the primitive?

Consider this scenario:

 Boolean b1 = new Boolean(false); //I know you shouldn't be doing this but it's valid
boolean foo = b1 == false;
Boolean bar = b1 == false;

Now if things worked like you probably expect them to be, foo would be true and bar would be false. Or, alternatively both could be false but that would mean autoboxing everything all the time if just a single boxed primitive occurs in the expression. (And then potentially unboxing it for the assignment.)

I wouldn't consider that a good trade-off. Granted, dealing with NPEs from unboxing conversions is ugly but it's something you have to do anyway in most unboxing scenarios.

Java autoboxing rules

Unboxing will be happing when arithmetic operators, comparison operators appear.

eg:

Integer a = 10;
a = a+10; //1.unboxing a to int 2.calculate a+10 3.boxing 20 to Integer.
System.out.print(a > 10); //1.unboxing a to int 2. compare

But when == appear, it depends.

If boxing type appear on both side, it will compare the reference.But if base type appear on one side, and the other side is a boxing type, the boxing type will unboxing to base type.

eg:

Integer a = new Integer(129);
Integer b = new Integer(129);
System.out.println(a == b); // compare reference return false
System.out.println(a == 129); // a will unboxing and compare 129 == 129 return true

PS: In Java.lang.Integer Cache to support the object identity semantics of autoboxing for values between -128 and 127 (inclusive) as required by JLS.
See source code

So:

Integer a = 127;
Integer b = 127; //cached, the same as b a==b return ture

Integer c = 129;
Integer d = 129; // not cached, c==d return false

Autoboxing Unboxing Operator (!=) and (==) difference

This is because 10 is in between the range [-128, 127]. For this range == works fine since the JVM caches the values and the comparison will be made on the same object.

Every time an Integer (object) is created with value in that range, the same object will be returned instead of creating the new object.

See the JLS for further information.

Why is autoboxing not allowed for primitive arrays when using Arrays.sort()?

According to the JLS, Section 5.1.7, there are only the following specified boxing conversions, and arrays aren't involved in any of them:

Boxing conversion converts expressions of primitive type to
corresponding expressions of reference type. Specifically, the
following nine conversions are called the boxing conversions:

  • From type boolean to type Boolean

  • From type byte to type Byte

  • From type short to type Short

  • From type char to type Character

  • From type int to type Integer

  • From type long to type Long

  • From type float to type Float

  • From type double to type Double

  • From the null type to the null type



Related Topics



Leave a reply



Submit