4

I understand that one reason Lower-bounded wildcards exist is so that a collection is not immutable when adding new elements.

E.g.

List<? extends Number> obj = new ArrayList<>();//Now this list is immutable
obj.add(new Integer(5));//Does not compile
List<? super Number> objTwo = new ArrayList<>();//This list is mutable
objTwo.add(new Integer(5));//Compiles

The following does not compile because I tried to get the long value of numbers.

Q1: What methods would I be able to use? Only Objects methods?:

public void testLowerBounds(List<? super Number> numbers){
        if (!numbers.isEmpty()){
           System.out.println(numbers.get(0).longValue());//Does not compile
        }    

}

How my question came about: I am currently learning about streams and the book specifies the following stream method:

Optional<T> min(Comparator<? super T> comparator)

And implements it as follows:

Stream<String> s = Stream.of("monkey", "ape", "bonobo");    
Optional<String> min = s.min((s1, s2) -> s1.length()—s2.length());

Q2: How is the comparator allowed to use string methods when is used?

If I had to answer Q2: I would say that optional is specifying "You have to pass me an implementation of Comparator that has a generic type "String" or something that implements "String". Would I be correct in saying this?

Looking forward to your response.

Holger
  • 243,335
  • 30
  • 362
  • 661
  • These seem to be two different questions. And concerning the first assumption, when the compiler prevents adding to the list, it does _not_ mean the list is immutable. The compiler is simply obeying the rules of static type checking of the language. – M A Feb 05 '17 at 10:50
  • Also a `List` does not have a `longValue()` method. – M A Feb 05 '17 at 10:52
  • Hi AR.3 thanks for the feedback. I have changed the question as you were right, I was checking a list method instead of the contents. They indeed are two different questions, I just wanted to explain the origin of the question. I get what you are saying about immutable, but now it is logically immutable as the compiler won't let you add to the list. Do you have answers to either question now? – Christopher Barrett Feb 05 '17 at 11:16
  • Concerning the first question, it is already answered in detail in http://stackoverflow.com/questions/4343202/difference-between-super-t-and-extends-t-in-java. – M A Feb 05 '17 at 11:26
  • I suspect you have a typo in the `min()` method declaration. Please check. – Ole V.V. Feb 05 '17 at 11:28
  • Thanks @AR.3 . From what I can gather from that link you posted, because we can make List super Number> number = new ArrayList(); we cannot use any methods except Objects methods. So Q1 is answered (Although I do find it strange you could assign Strings to a super Number> by Through Object). Any ideas for Q2, do think my statement is correct? – Christopher Barrett Feb 05 '17 at 12:55
  • I cannot get `Optional min( super T> comparator)` to compile. Depending on the context I put it in, I get “Syntax error on token "(", Type expected after this token” or another error message. You may want to provide complete code, granted it’s not too long. Hard to answer Q2 without compilable code. :-) – Ole V.V. Feb 05 '17 at 15:49
  • Hi @Ole V.V. The code you are trying to compile is the method contained within the Stream java class https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html Please do not try to compile that code. The code section I would like you to answer is the implementation (below the section on the stream method). Thanks – Christopher Barrett Feb 06 '17 at 08:16
  • In that case my suspicion was correct, you did quote it wrongly. It says `Optional min(Comparator super T> comparator);` in the `Stream` interface. – Ole V.V. Feb 06 '17 at 08:56
  • For Q2, the compiler just has to find a type `? super String` (since we know from the declaration of `s` that `T` is `String`) that makes `(s1, s2) -> s1.length()—s2.length()` a `Comparator>`. I don’t know the details of generics well enough to tell whether it might choose `CharSequence` over `String`, but both work the same here, so we need not really care. – Ole V.V. Feb 06 '17 at 09:04
  • 1
    First, the compiler must infer the type of the lambda expression `(s1,s2)->...`, with the constraint that it's a subtype of `Comparator super String>`. The inference rule chooses `Comparator`, which is apparently more sensible than other choices like `Comparator`. Once the type of the lambda expression is chosen, we now know the types of lambda parameters `s1,s2`, which are both `String`. Therefore, in the lambda body, we can call `String` methods on them. Note that the compiler cannot look into a lambda body before the lambda parameter types are known. – ZhongYu Feb 11 '17 at 02:53

1 Answers1

2

First of all, you should not confuse wildcard type parameters with mutability. Having a wildcard in a List’s element type does not prevent modifications, it only imposes a few practical restrictions to what you can do with the list.

Having a list declared like List<? extends Number> implies that the referenced list has an actual element type of Number or a subclass of Number, e.g. it could be a List<Integer> or List<Double>. So you can’t add an arbitrary Number instance as you can’t know whether it is compatible to the actual element type.

But you can still add null, as the null reference is known to be compatible with all reference types. Further, you can always remove elements from a list, e.g. call remove or clear without problems. You can also call methods like Collections.swap(list, index1, index2), which is interesting as it wouldn’t be legal to call list.set(index1, list.get(index2)) due to formal rules regarding wildcard types, but passing the list to another method that might use a non-wildcard type variable to represent the list’s element type works. It’s obviously correct, as it only sets elements stemming from the same list, which must be compatible.

Likewise, if you have a Comparator<Number>, you can call Collections.sort(list, comparator), as a comparator which can handle arbitrary numbers, will be able to handle whatever numbers are actually stored in the list.

To sum it up, having ? extends in a collection’s element type does not prevent modifications.


As said, you can’t insert arbitrary new elements into a list whose actual element type might be an unknown subclass of the bound, like with List<? extends Number>. But you are guaranteed to get a Number instance when retrieving an element, as every instance of subtype of Number is also an instance of Number. When you declare a List<? super Number>, its actual element type might be Number or a super type of Number, e.g. Object or Serializable. You can insert arbitrary Number instances, as you know it will be compatible to whatever actual element type the list has, as it is a super type of number. When you retrieve an instance, you only know that it is an instance of Object, as that’s the super type of all instances. To compare with the ? extends case, having a ? super declaration does not prevent reading, it only imposes some practical limitations. And likewise, you can still pass it to Collections.swap, because, regardless of how little we known about the actual type, inserting what we just retrieved from the same list, works.

In your second question, you are confusing the sides. You are now not looking at the implementation of min, but at the caller. The declaration of min(Comparator<? super T> c) allows the caller to pass any comparator being parameterized with T or a super type of T. So when you have a Stream<String>, it is valid to pass a Comparator<String> to the min method, which is exactly, what you are implementing via the (s1, s2) -> s1.length()—s2.length() lambda expression (though, I’d prefer Comparator.comparingInt(String::length)).

Within the implementation of min, there is indeed no knowledge about what either, T or the actual type argument of the Comparator, is. But it’s sufficient to know that any stream element that is of type T can be passed to the comparator’s compare method, which might expect T or a super type of T.

Holger
  • 243,335
  • 30
  • 362
  • 661
  • Thanks @Holger. You had some nice explanations. I especially liked the answer to my second question (Nice lambda by the way, using method references). I get your point about mutability. With the answer to my first question I gathered you are saying I have access only to Object methods, which is what I expected as the reference type could be object as that is the information you have available at that point in time. – Christopher Barrett Feb 06 '17 at 19:26