3

I have read the thread Difference in LinkedList, queue vs list, but still don't understand the following code behavior:

List<Integer> l = new LinkedList<>();
l.add(10);
l.add(12);
l.remove(1); // removes index 1
System.out.println(l); // outputs [10]
    
Queue<Integer> q2 = new LinkedList<>();
q2.add(10);
q2.add(12);
q2.remove(1); // tries to remove object 1
System.out.println(q2); // outputs [10, 12]

I know, why the outputs differ:

  • in case of l the call remove(1) removes the element with the index 1
  • in case of q the call remove(1) tries to remove an object 1, but there are only Integers 10 and 12.

If I take a closer look to the interfaces:

  • Queue does not have a remove(int) method, but inherits a remove(Object) method from Collection.
  • List has a remove(int index) and remove(Object o) method.

So far, so good.

But: I do not understand, how Java is able to invoke two different methods while using the same syntax (remove(1))?

mrbela
  • 4,477
  • 9
  • 44
  • 79
  • Yes. Avoid auto-boxing/unboxing whenever possible. Enable compiler warnings for that, in your IDE – JayC667 Apr 28 '21 at 15:55

4 Answers4

6

For List, the compiler chooses remove(int) according to the method signature:

The process of determining applicability begins by determining the potentially applicable methods (§15.12.2.1). Then, to ensure compatibility with the Java programming language prior to Java SE 5.0, the process continues in three phases:

  1. The first phase 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.

Note the emphasized part, which means the compiler matches 1 with an int. Since there is an applicable method remove(int), that is the one chosen for invocation. In other words, the primitive type is considered first, before considering reference types such as Integer (the boxed type for int).

M A
  • 71,713
  • 13
  • 134
  • 174
1

Your main problem is the calling code. With

l.remove(1);

you are using an int as a parameter on a generic data structure that was declared with Integer as the type parameter. This works for queues also because of the automatic boxing behavior in Java.

If you use an Integer then both calls behave the same:

l.remove(Integer.valueOf(1));
q2.remove(Integer.valueOf(1));

Now both calls remove the desired object.

Seelenvirtuose
  • 20,273
  • 6
  • 37
  • 66
1

In your example l is of type List, which has two remove methods: remove(Object) inherited from Collection and remove(int) defined for List. A call to remove(1)is linked to remove(int) because that's an exact match. q2on the other hand is a Queue and this type does not specify remove(int). Only remove(Object) from Collection. So the compiler generates autoboxing from int to Integer and links the call to remove(Object).

What may be confusing: q2 in fact holds a List. But that doesn't matter. The compiler only "see's" the type of the variable referencing the Object. The real type of the Object is ignored (in fact unknown in most cases). Knowing q2 holds a Listyou could do a type cast:

((List<Integer>)q2).remove(1);

This would again lead to a call to List.remove(int) because the compiler then knows the object you're dealing with is a List

mrbela
  • 4,477
  • 9
  • 44
  • 79
Dietmar Höhmann
  • 397
  • 3
  • 10
-2

The implementations of both methods are different -- since a Queue only allows removal from the bottom and the top of the stack, you'll have to use the specific methods for both removal methods, versus a List which is meant for index operators.

In this case, Queue overrides remove(Object obj) to remove the object itself, versus list, which overrides remove(Object obj) and adds an extra remove(int index) to allow index and object removal.

LeoDog896
  • 3,472
  • 1
  • 15
  • 40