When Do Java Generics Require <? Extends T> Instead of <T> and Is There Any Downside of Switching

When do Java generics require ? extends T instead of T and is there any downside of switching?

First - I have to direct you to http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html -- she does an amazing job.

The basic idea is that you use

<T extends SomeClass>

when the actual parameter can be SomeClass or any subtype of it.

In your example,

Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));

You're saying that expected can contain Class objects that represent any class that implements Serializable. Your result map says it can only hold Date class objects.

When you pass in result, you're setting T to exactly Map of String to Date class objects, which doesn't match Map of String to anything that's Serializable.

One thing to check -- are you sure you want Class<Date> and not Date? A map of String to Class<Date> doesn't sound terribly useful in general (all it can hold is Date.class as values rather than instances of Date)

As for genericizing assertThat, the idea is that the method can ensure that a Matcher that fits the result type is passed in.

Stream Object instead of Stream ? extends T being produced when invoked on list

The "expected type: ... actual type: ..." message is not about list1.stream(), it's about the value you return in the lambda in flatMap. Observe where the red squiggles are:

Sample Image

It's saying that the code in the x -> ... lambda is returning a Stream<Object>, rather than the expected Stream<R>.

Note that map(spendable -> x + "" + appendable) can totally return a Stream<Object> with the right type arguments ( if it were .<Object>map(appendable ->x + "" +appendable) for example).

And in fact, the "expected type: ... actual type: ..." message is only produced by IntelliJ, so it's probably just IntelliJ being a bit silly. If you run javac, you get a much better error message:

incompatible types: inference variable R#1 has incompatible bounds
.flatMap(x -> {
^
equality constraints: R#2
lower bounds: R#3,String
where R#1,T#1,R#2,T#2,U,R#3 are type-variables:
R#1 extends Object declared in method <R#1>flatMap(Function<? super T#1,? extends Stream<? extends R#1>>)
T#1 extends Object declared in interface Stream
R#2 extends Object declared in method <T#2,U,R#2>cartesian(List<? extends T#2>,List<? extends U>,BiFunction<? super T#2,? super U,R#2>)
T#2 extends Object declared in method <T#2,U,R#2>cartesian(List<? extends T#2>,List<? extends U>,BiFunction<? super T#2,? super U,R#2>)
U extends Object declared in method <T#2,U,R#2>cartesian(List<? extends T#2>,List<? extends U>,BiFunction<? super T#2,? super U,R#2>)
R#3 extends Object declared in method <R#3>map(Function<? super T#1,? extends R#3>)

That says that the type parameter R in flatMap cannot be inferred, because its bounds cannot be satisfied simultaneously. R in flatMap has to be the same as the R in cartesian, but its lower bounds are R in map, and String.

Anyway, it seems like what you wanted to do was:

return list1.stream()
.flatMap(x -> list2.stream().map(y -> func.apply(x, y)));

java.lang.Class generics and wildcards

The subtyping relationship here is:

          Class<? extends Iface>
╱ ╲
Class<? extends Iface<?>> Class<Impl>

(Which I explained in my answer to 'Cannot convert from List<List> to List<List<?>>'.)

So essentially it doesn't compile because it's a sideways conversion.

If it's possible, you can do the casting I described over there:

(Class<? extends Iface<?>>)(Class<? extends Impl>)Impl.class

If you can't do the cast, then you probably just have to deal with a raw bounded Class<? extends Iface>. It's annoying primarily because of the warnings but it opens up the possibility for an error:

interface Iface<T> {
void accept(T a);
}

class Impl2 implements Iface<String> {
public void accept(String a) { }
}

class TestCase {
static Class<? extends Iface> clazz = Impl2.class;

public static void main(String[] args) throws Exception {
// throws ClassCastException
clazz.newInstance().accept(new Object());
}
}

Unlikely to happen, but it depends on what you're doing I suppose.


I tend to think this is a problem with the Java type system.

  • Possibly there should be a special rule that a type argument ? extends T<?> contains a type argument ? extends T such that e.g. a Class<? extends T> converts to a Class<? extends T<?>>. This doesn't make sense from the perspective of the existing way that subtyping is defined (T is a supertype of T<?>) but it makes sense from the perspective of type safety.

  • Or e.g. List.class should be a Class<List<?>> instead of a Class<List>.

  • Or some other clever thing people smarter than me can think up.

The interesting thing about the ClassCastException I described above is that it's completely artificial. And in fact, preventing it with the unchecked cast causes a warning.

Just a sign that generics in Java are not done yet, I guess.

Whats the use of saying ? extends SomeObject instead of SomeObject

List<SomeObject> l;

In this you cannot say List<SomeObject> l = new ArrayList<SubClassOfSomeObjectClass>;(not allowed)
wheres for

List<? extends SomeObject> l;

you can say

List<? extends SomeObject> l = new ArrayList<SubClassOfSomeObject>;(allowed)

But note that in List<? extends SomeObject> l = new ArrayList<SubClassOfSomeObject>; you cannot add anything to your list l because ? represents unknown class (Except null of-course).

Update: For your question in the comment What could I possibly do with a list if I cannot add anything to it?

Now consider a case in which you have to write a function to print your list but mind you it must only accept a List having objects which are subclasses of your SomeObject. In this case as I stated above you cannot use

public void printList(List<SubClassOfSomeObjectClass> someList)

So what would you do? You would do something like

    public void printList(List<? extends SomeObject> someList) {
for(SomeObject myObj : someList) {
//process read operations on myObj
}

Class using generic in the exception throws and have a List String param doesn't compile

Let's cover ClassB first.

Type arguments to a method is resolved by the caller of the method. It cannot be resolved by overrides in a subclass.

There are two ways to make ClassB work. You can add the type argument there too.1

class ClassB implements Interface {
public <T extends Exception> void method(List<String> list) throws T{}
}

Or you can move the type argument up to the interface, so the subclass can decide.

interface Interface<T extends Exception> {
void method(List<String> list) throws T;
}
class ClassB implements Interface<Exception> {
public void method(List<String> list) throws Exception {}
}

As for why ClassA work, it's because of the raw type in the argument. When you use a raw type, everything becomes raw, which means that when compiling ClassA, Interface looks like this raw type.

interface Interface {
void method(List list) throws Exception;
}

It is done that way for backwards compatibility. See JLS 4.8 Raw Types :

The superclasses (respectively, superinterfaces) of a raw type are the erasures of the superclasses (superinterfaces) of any of its parameterized invocations.

It goes on to say:

The use of raw types is allowed only as a concession to compatibility of legacy code. The use of raw types in code written after the introduction of generics into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types.


1 I was actually wondering how the caller could resolve T, i.e. how the compiler would infer what T is, given that nothing in a method call would indicate the valid exceptions.

According to the Eclipse compiler, when you write the following, T gets resolved as RuntimeException.

new ClassB().method(null);

You can override that by explicitly giving the type, e.g. this will make the method throw a ParseException.

new ClassB().<ParseException>method(null);

Follow-up

The inferred T is RuntimeException in Java 8. Without a catch statement, I get the following errors on earlier versions of Java:

1.5.0_22: unreported exception T; must be caught or declared to be thrown
1.6.0_45: unreported exception T; must be caught or declared to be thrown
1.7.0_79: error: unreported exception Exception; must be caught or declared to be thrown

Java 8 dedicated an entire chapter to type inference, and it says:

if the bound set contains throws αi, and the proper upper bounds of αi are, at most, Exception, Throwable, and Object, then Ti = RuntimeException.



Related Topics



Leave a reply



Submit