3

The following code does not compile in Eclipse. It says "The method putHelper(List,int,E) in the type Abc is not applicable for the arguments (List <.capture#8-of extends E>",int,E)"

private <E> void putHelper(List<E> list, int i, E value) {
    list.set(i, value);
}

public <E> void put(List<? extends E> list, int toPos, E value) {
    // list.set(toPos,value);
    putHelper(list, toPos, value);
}

I don't understand why it is so? Because the code below works fine.

  public <E> void put(List<? extends E> list,int fromPos, int toPos) {
  putHelper(list,fromPos,toPos);
  }

  private <E> void putHelper(List<E> list,int i, int j) {
  list.set(j,list.get(i));
  }

And I understand that here the helper method is able to capture the wildcard type, but why not in the earlier code?

EDIT: In the third case, if I change type parameter in the put method to List<.? super E> and when I try to call the put() method from another method which takes a list, Eclipse doesn't compile it. It says, "The method put(List<.? super E>,int,E) in the type Abc is not applicable for the arguments (List <.capture#6-of extends E>",int,E)"

public static <E> void insertAndProcess(List<? extends E> list) {

// Iterate through the list for some range of values i to j

    E value = list.get(i);

//Process the element and put it back at some index

    put(list, i+1, value);

//Repeat the same for few more elements
}

private static <E> void putHelper(List<E> list, int i, E value) {
    list.set(i, value);
}

public static <E> void put(List<? super E> list, int toPos, E value) {
    putHelper(list, toPos, value);
}

Here, how can insertAndProcess() call put() method and use it in its implementation, while the user can still be able to call both these methods with say ArrayList<.Integer>?

shujin
  • 181
  • 11

3 Answers3

2

This is because of the Get and Put Principle also known by the acronym PECS which stands of Producer Extends, Consumer Super.

This is explained in this SO question: What is PECS (Producer Extends Consumer Super)?

But basically:

public <E> void put(List<? extends E> list, int toPos, E value) {
   // list.set(toPos,value);
   putHelper(list, toPos, value);
}

<? extends E> can't be used here because the List is being used as a consumer (it is taking elements) so it should use super instead of extends.

public <E> void put(List<? super E> list, int toPos, E value) {
   // list.set(toPos,value);
   putHelper(list, toPos, value);
}

EDIT

In your 2nd case, List is acting as a producer because it is producing elements via the call to get() so you can use extends.

However, in your 3rd example, you are both getting and putting into the same list so I don't think you can use wildcards at all.

This compiles:

public static <E> void insertAndProcess(List<E> list) {

    // Iterate through the list for some range of values i to j
    E value = list.get(i);

    // Process the element and put it back at some index
    putHelper(list, i+1, value);

    // Repeat the same for few more elements
}

Note that since we don't need to use any wildcards anyway because we are getting and setting from the same list so the type E must be the same no matter what it is.

Community
  • 1
  • 1
dkatzel
  • 29,286
  • 2
  • 57
  • 64
  • Thank you for the explanation. In the second case, where the code works, is the List consumer or producer? I have edited the question and added another case, please help with the third case. – shujin Jun 24 '14 at 17:51
  • Thank you for the answer. Well, I wanted to restrict the calls to the method insertAndProcess() by passing lists of type, say Number, only because it uses some specific implementation for that type, so I needed to use extends E>. I guess, I'd have to restrict that in the class itself now. – shujin Jun 25 '14 at 20:04
1

Generics are sometimes a bit difficult. Try to analyze it step by step.

Imagine - in your first example (that doesn't compile) - you call the put method with the type variable E replaced by Number. As you are constraining the input parameter list with List<? extends E> it could be a List<Integer> for example. The parameter value is of type E (remember: E is Number), so it could be a Double for example. And this must not be allowed because you would try to add a Double into a List<Integer>.

In the second example you only have a constraint on the input parameter list. No other parameter is dependent on E. Setting and getting on a list will always work because the elements in question must be of the same type.

A solution:

Always remind the acronym PECS. See Wildcard (Java). That means you should declare the put method as following (in your first example):

public <E> void put(List<? super E> list, int toPos, E value) {
    putHelper(list, toPos, value);
}
Seelenvirtuose
  • 19,157
  • 6
  • 34
  • 57
  • Thank you. The clear explanation helped a lot. I had read about PECS, but I declared put(List extends E>) so that it could be used with insertAndProcess(), please see the EDITED question. Any help is appreciated. – shujin Jun 24 '14 at 17:49
  • 1
    @shujin When _producing_ and _consuming_ you cannot declare _extends_ nor _super_. So you must stick to `List`. – Seelenvirtuose Jun 25 '14 at 05:51
0

List<E> will accept only E class objects where as List<? extends E> can accept any sub-class of E.

When you say a method can accept only List<E> passing List<? extends E> will confuse the compiler because it does not know which type of Object your list will actually contain.

Note that in java, this is illegal:

List<E> list = new ArrayList<? extends E>();

E.g. List<Number> list = new ArrayList<Integer>();

In the second example, your list expects any sub-class of E and what your passing is list of E objects so its allowed. E.g. List<? extends Number> list = new ArrayList<Integer>();

Nikhil Talreja
  • 2,691
  • 1
  • 12
  • 20