Why no autoboxing when removing primitive type from a List in Java?
Asked Answered
A

2

4

I have the code below throwing IndexOutOfBoundsException:

 List<Character> list = new ArrayList<>();
 char c  = 'a';
 list.add(c);
 list.remove(c); // gets fixed by passing list.remove((Character)c);

I know that this happens because autoboxing is not happening while removal and it happens while adding an element. My question is why? What is so special in adding that autoboxing from char to Character is possible while in the remove method it's not?

Analysand answered 2/4, 2018 at 19:14 Comment(0)
F
7

Java will consider boxing a primitive type to a wrapper type, but only if it hasn't already considered an overloaded method that doesn't need to box the argument.

The JLS, Section 15.12.2, covers which overload is chosen as follows:

  1. The first phase (§15.12.2.2) performs overload resolution without permitting boxing or unboxing conversion, or the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the second phase.

This guarantees that any calls that were valid in the Java programming language before Java SE 5.0 are not considered ambiguous as the result of the introduction of variable arity methods, implicit boxing and/or unboxing. However, the declaration of a variable arity method (§8.4.1) can change the method chosen for a given method method invocation expression, because a variable arity method is treated as a fixed arity method in the first phase. For example, declaring m(Object...) in a class which already declares m(Object) causes m(Object) to no longer be chosen for some invocation expressions (such as m(null)), as m(Object[]) is more specific.

(bold emphasis is mine)

  1. The second phase (§15.12.2.3) performs overload resolution while allowing boxing and unboxing, but still precludes the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the third phase.

This ensures that a method is never chosen through variable arity method invocation if it is applicable through fixed arity method invocation.

  1. The third phase (§15.12.2.4) allows overloading to be combined with variable arity methods, boxing, and unboxing.

The compiler sees no other overload of add that could possibly match add(Character) without boxing, so it considers boxing in the second phase and finds its match.

However, the compiler does see an overload of remove that matches without boxing, remove(int), so the char is widened to an int. The compiler found its method in the first phase, so the second phase was never considered. This forces you, as you have figured out, to cast the char explicitly to a Character to get the proper method matched.

This happens to be a case where in Java 5, introducing boxing and unboxing causes an ambiguity that didn't exist before that version. If these method names were designed with this in mind, the designers most likely would have used different method names, avoiding overloading and this ambiguity here.

Flocculus answered 2/4, 2018 at 19:26 Comment(0)
D
9

That's not really a problem of auto unboxing, but a problem of overloading: there is a List::remove(int) (remove by index in the list) method that exist which is more specific than List::remove(E) (remove by searching an object using Object::equals).

In your case, your char is casted to int.

In the case of add, the equivalent version to removing with an index, is List::add(int, E) (see javadoc for details). List::add(E) is equivalent to list.add(add(list.size(), E).

Dariusdarjeeling answered 2/4, 2018 at 19:17 Comment(3)
then why not happening while adding?Au
@Roushan45 because there is no add method on List that accepts a single int.Lacedaemonian
@Roushan45 The remove(int) is not to remove an int, but to remove at an index, so there is not add(int), only add(T) and add(int, T) for specific indexSilassilastic
F
7

Java will consider boxing a primitive type to a wrapper type, but only if it hasn't already considered an overloaded method that doesn't need to box the argument.

The JLS, Section 15.12.2, covers which overload is chosen as follows:

  1. The first phase (§15.12.2.2) performs overload resolution without permitting boxing or unboxing conversion, or the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the second phase.

This guarantees that any calls that were valid in the Java programming language before Java SE 5.0 are not considered ambiguous as the result of the introduction of variable arity methods, implicit boxing and/or unboxing. However, the declaration of a variable arity method (§8.4.1) can change the method chosen for a given method method invocation expression, because a variable arity method is treated as a fixed arity method in the first phase. For example, declaring m(Object...) in a class which already declares m(Object) causes m(Object) to no longer be chosen for some invocation expressions (such as m(null)), as m(Object[]) is more specific.

(bold emphasis is mine)

  1. The second phase (§15.12.2.3) performs overload resolution while allowing boxing and unboxing, but still precludes the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the third phase.

This ensures that a method is never chosen through variable arity method invocation if it is applicable through fixed arity method invocation.

  1. The third phase (§15.12.2.4) allows overloading to be combined with variable arity methods, boxing, and unboxing.

The compiler sees no other overload of add that could possibly match add(Character) without boxing, so it considers boxing in the second phase and finds its match.

However, the compiler does see an overload of remove that matches without boxing, remove(int), so the char is widened to an int. The compiler found its method in the first phase, so the second phase was never considered. This forces you, as you have figured out, to cast the char explicitly to a Character to get the proper method matched.

This happens to be a case where in Java 5, introducing boxing and unboxing causes an ambiguity that didn't exist before that version. If these method names were designed with this in mind, the designers most likely would have used different method names, avoiding overloading and this ambiguity here.

Flocculus answered 2/4, 2018 at 19:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.