Why Can This Generic Method with a Bound Return Any Type

Why can this generic method with a bound return any type?

This is actually a legitimate type inference*.

We can reduce this to the following example (Ideone):

interface Foo {
<F extends Foo> F bar();

public static void main(String[] args) {
Foo foo = null;
String baz = foo.bar();
}
}

The compiler is allowed to infer a (nonsensical, really) intersection type String & Foo because Foo is an interface. For the example in the question, Integer & IElement is inferred.

It's nonsensical because the conversion is impossible. We can't do such a cast ourselves:

// won't compile because Integer is final
Integer x = (Integer & IElement) element;

Type inference basically works with:

  • a set of inference variables for each of a method's type parameters.
  • a set of bounds that must be conformed to.
  • sometimes constraints, which are reduced to bounds.

At the end of the algorithm, each variable is resolved to an intersection type based on the bound set, and if they're valid, the invocation compiles.

The process begins in 8.1.3:

When inference begins, a bound set is typically generated from a list of type parameter declarations P1, ..., Pp and associated inference variables α1, ..., αp. Such a bound set is constructed as follows. For each l (1 ≤ l ≤ p):

  • […]

  • Otherwise, for each type T delimited by & in a TypeBound, the bound αl <: T[P1:=α1, ..., Pp:=αp] appears in the set […].

So, this means first the compiler starts with a bound of F <: Foo (which means F is a subtype of Foo).

Moving to 18.5.2, the return target type gets considered:

If the invocation is a poly expression, […] let R be the return type of m, let T be the invocation's target type, and then:

  • […]

  • Otherwise, the constraint formula ‹R θ → T› is reduced and incorporated with [the bound set].

The constraint formula ‹R θ → T› gets reduced to another bound of R θ <: T, so we have F <: String.

Later on these get resolved according to 18.4:

[…] a candidate instantiation Ti is defined for each αi:

  • Otherwise, where αi has proper upper bounds U1, ..., Uk, Ti = glb(U1, ..., Uk).

The bounds α1 = T1, ..., αn = Tn are incorporated with the current bound set.

Recall that our set of bounds is F <: Foo, F <: String. glb(String, Foo) is defined as String & Foo. This is apparently a legitimate type for glb, which only requires that:

It is a compile-time error if, for any two classes (not interfaces) Vi and Vj, Vi is not a subclass of Vj or vice versa.

Finally:

If resolution succeeds with instantiations T1, ..., Tp for inference variables α1, ..., αp, let θ' be the substitution [P1:=T1, ..., Pp:=Tp]. Then:

  • If unchecked conversion was not necessary for the method to be applicable, then the invocation type of m is obtained by applying θ' to the type of m.

The method is therefore invoked with String & Foo as the type of F. We can of course assign this to a String, thus impossibly converting a Foo to a String.

The fact that String/Integer are final classes is apparently not considered.


* Note: type erasure is/was completely unrelated to the issue.

Also, while this compiles on Java 7 as well, I think it's reasonable to say we needn't worry about the specification there. Java 7's type inference was essentially a less sophisticated version of Java 8's. It compiles for similar reasons.


As an addendum, while strange, this will likely never cause a problem that was not already present. It's rarely useful to write a generic method whose return type is solely inferred from the return target, because only null can be returned from such a method without casting.

Suppose for example we have some map analog which stores subtypes of a particular interface:

interface FooImplMap {
void put(String key, Foo value);
<F extends Foo> F get(String key);
}

class Bar implements Foo {}
class Biz implements Foo {}

It's already perfectly valid to make an error such as the following:

FooImplMap m = ...;
m.put("b", new Bar());
Biz b = m.get("b"); // casting Bar to Biz

So the fact that we can also do Integer i = m.get("b"); is not a new possibility for error. If we were programming code like this, it was already potentially unsound to begin with.

Generally, a type parameter should only be solely inferred from the target type if there is no reason to bound it, e.g. Collections.emptyList() and Optional.empty():

private static final Optional<?> EMPTY = new Optional<>();

public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}

This is A-OK because Optional.empty() can neither produce nor consume a T.

Java Generics: Returning Bounded Generic Type

What you are doing is wrong. That's why you get the error. You can call your method with ExceptionC exceptionC=managedException(ExceptionD d) and you will end up with a cast (ExceptionC) exceptionD; And casting it masks the error but you get it at runtime.

Change your method to:

public ParentException managedException(Exception cause) {        
if(ExceptionA.class.isInstance(cause)) {
return ExceptionA.class.cast(cause);
} else if(ExceptionB.class.isInstance(cause)) {
return ExceptionB.class.cast(cause);
} else if(ExceptionC.class.isInstance(cause)){
return ExceptionC.class.cast(cause);
} else {
return new ExceptionD(cause.getMessage(), cause);
}
}

You don't need generics here. All these exceptions are also ParentExceptions so you can juse return them. When you think about it you are trying to make the method return different types. Which cannot be done like that because if you have a variable that is initialized from this method you need to know what would be the result. And you know that the result will be ParentException but you can't know which kind of parent exception is that.

The reason behind it is that your method if written like is not returning ParentException - it is returning T (a subclass). And you can return a different type of subclass and not the one you are trying to get.

In a simpler example if we have:

class A {}

class B extends A{ };

class C extends A{ };

public <T extends A> T test() {
return (T) new B();
}

we can call it with C c=test(); we actually try to cast (C) new B(); which is incompatible but we have masked it and we get the exception at runtime

Return bounded type is incompatible with a type that matches the bound

You've created a generic method by declaring T as a type parameter with an upper bound. With generic methods, you must be aware that the caller can decide what T is by passing an explicit type argument to your method.

class ClassC extends ClassA {}

ClassC c = new Factory().<ClassC>create();

There is no guarantee that the type parameter chosen by the caller, explicitly or implicitly, will match the type of what is returned, and you're returning a ClassB. The compiler cannot guarantee type safety here so this is disallowed.

If you don't need the generics, remove the type parameter off the method and declare create to return a ClassB, ClassA, or Object.

If you need the generics, then you must take a parameter of type Class<T> and create an instance with it to satisfy the compiler.

public <T extends ClassA> T create(Class<T> clazz) throws ReflectionRelatedExceptions 
{
return clazz.getConstructor().newInstance();
}

Generic return type upper bound - interface vs. class - surprisingly valid code

CharSequence is an interface. Therefore even if SomeClass does not implement CharSequence it would be perfectly possible to create a class

class SubClass extends SomeClass implements CharSequence

Therefore you can write

SomeClass c = getCharSequence();

because the inferred type X is the intersection type SomeClass & CharSequence.

This is a bit odd in the case of Integer because Integer is final, but final doesn't play any role in these rules. For example you can write

<T extends Integer & CharSequence>

On the other hand, String is not an interface, so it would be impossible to extend SomeClass to get a subtype of String, because java does not support multiple-inheritance for classes.

With the List example, you need to remember that generics are neither covariant nor contravariant. This means that if X is a subtype of Y, List<X> is neither a subtype nor a supertype of List<Y>. Since Integer does not implement CharSequence, you cannot use List<Integer> in your doCharSequence method.

You can, however get this to compile

<T extends Integer & CharSequence> void foo(List<T> list) {
doCharSequence(list);
}

If you have a method that returns a List<T> like this:

static <T extends CharSequence> List<T> foo() 

you can do

List<? extends Integer> list = foo();

Again, this is because the inferred type is Integer & CharSequence and this is a subtype of Integer.

Intersection types occur implicitly when you specify multiple bounds (e.g. <T extends SomeClass & CharSequence>).

For further information, here is the part of the JLS where it explains how type bounds work. You can include multiple interfaces, e.g.

<T extends String & CharSequence & List & Comparator>

but only the first bound may be a non-interface.

Java generic method: inconsistency when inferring upper bound on return type from argument type

Col<Baz> col = Test.unsafe (new Foo ());

Test.unsafe infers T as Object in this case; since everything extends Object, the bounds are satisfied.

Col<Baz> col = Test.safe (new Tag<Foo> ());

T can't be inferred to be Object in this case: it's Foo, because you've said that it's Foo, without any bounds. Similarly, V is exactly Baz. Since Baz doesn't extend Foo, it's a compiler error.

How do I make the method return type generic?

You could define callFriend this way:

public <T extends Animal> T callFriend(String name, Class<T> type) {
return type.cast(friends.get(name));
}

Then call it as such:

jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();

This code has the benefit of not generating any compiler warnings. Of course this is really just an updated version of casting from the pre-generic days and doesn't add any additional safety.

Java Generics: Generic type defined as return type only

The method returns a type of whatever you expect it to be (<X> is defined in the method and is absolutely unbounded).

This is very, very dangerous as no provision is made that the return type actually matches the returned value.

The only advantage this has is that you don't have to cast the return value of such generic lookup methods that can return any type.

I'd say: use such constructs with care, because you lose pretty much all type-safety and gain only that you don't have to write an explicit cast at each call to get().

And yes: this pretty much is black magic that blows up at runtime and breaks the entire idea of what generics should achieve.

How to reference a generic return type with multiple bounds

While the type parameters of a generic method can be restricted by bounds, such as extends Foo & Bar, they are ultimately decided by the caller. When you call getFooBar(), the call site already knows what T is being resolved to. Often, these type parameters will be inferred by the compiler, which is why you don't usually need to specify them, like this:

FooBar.<FooAndBar>getFooBar();

But even when T is inferred to be FooAndBar, that's really whats happening behind the scenes.

So, to answer your question, such a syntax like this:

Foo&Bar bothFooAndBar = FooBar.getFooBar();

Would never be useful in practice. The reason is that the caller must already know what T is. Either T is some concrete type:

FooAndBar bothFooAndBar = FooBar.<FooAndBar>getFooBar(); // T is FooAndBar

Or, T is an unresolved type parameter, and we're in its scope:

<U extends Foo & Bar> void someGenericMethod() {
U bothFooAndBar = FooBar.<U>getFooBar(); // T is U
}

Another example of that:

class SomeGenericClass<V extends Foo & Bar> {
void someMethod() {
V bothFooAndBar = FooBar.<V>getFooBar(); // T is V
}
}

Technically, that wraps up the answer. But I'd also like to point out that your example method getFooBar is inherently unsafe. Remember that the caller decides what T gets to be, not the method. Since getFooBar doesn't take any parameters related to T, and because of type erasure, its only options would be to return null or to "lie" by making an unchecked cast, risking heap pollution. A typical workaround would be for getFooBar to take a Class<T> argument, or else a FooFactory<T> for example.

Update

It turns out I was wrong when I asserted that the caller of getFooBar must always know what T is. As @MiserableVariable points out, there are some situations where the type argument of a generic method is inferred to be a wildcard capture, rather than a concrete type or type variable. See his answer for a great example of a getFooBar implementation that uses a proxy to drive home his point that T is unknown.

As we discussed in the comments, an example using getFooBar created confusion because it takes no arguments to infer T from. Certain compilers throw an error on a contextless call to getFooBar() while others are fine with it. I thought that the inconsistent compile errors - along with the fact that calling FooBar.<?>getFooBar() is illegal - validated my point, but these turned out to be red herrings.

Based on @MiserableVariable's answer, I put together an new example that uses a generic method with an argument, to remove the confusion. Assume we have interfaces Foo and Bar and an implementation FooBarImpl:

interface Foo { }
interface Bar { }
static class FooBarImpl implements Foo, Bar { }

We also have a simple container class that wraps an instance of some type implementing Foo and Bar. It declares a silly static method unwrap that takes a FooBarContainer and returns its referent:

static class FooBarContainer<T extends Foo & Bar> {

private final T fooBar;

public FooBarContainer(T fooBar) {
this.fooBar = fooBar;
}

public T get() {
return fooBar;
}

static <T extends Foo & Bar> T unwrap(FooBarContainer<T> fooBarContainer) {
return fooBarContainer.get();
}
}

Now let's say we have a wildcard parameterized type of FooBarContainer:

FooBarContainer<?> unknownFooBarContainer = ...;

We're allowed to pass unknownFooBarContainer into unwrap. This shows my earlier assertion was wrong, because the call site doesn't know what T is - only that it is some type within the bounds extends Foo & Bar.

FooBarContainer.unwrap(unknownFooBarContainer); // T is a wildcard capture, ?

As I noted, calling unwrap with a wildcard is illegal:

FooBarContainer.<?>unwrap(unknownFooBarContainer); // compiler error

I can only guess that this is because wildcard captures can never match each other - the ? argument provided at the call site is ambiguous, with no way of saying that it should specifically match the wildcard in the type of unknownFooBarContainer.

So, here's the use case for the syntax the OP is asking about. Calling unwrap on unknownFooBarContainer returns a reference of type ? extends Foo & Bar. We can assign that reference to Foo or Bar, but not both:

Foo foo = FooBarContainer.unwrap(unknownFooBarContainer);
Bar bar = FooBarContainer.unwrap(unknownFooBarContainer);

If for some reason unwrap were expensive and we only wanted to call it once, we would be forced to cast:

Foo foo = FooBarContainer.unwrap(unknownFooBarContainer);
Bar bar = (Bar)foo;

So this is where the hypothetical syntax would come in handy:

Foo&Bar fooBar = FooBarContainer.unwrap(unknownFooBarContainer);

This is just one fairly obscure use case. There would be pretty far-ranging implications for allowing such a syntax, both good and bad. It would open up room for abuse where it wasn't needed, and it's completely understandable why the language designers didn't implement such a thing. But I still think it's interesting to think about.

Note - Since JDK 10 there is the var reserved type name, which makes this possible:

var fooBar = FooBarContainer.unwrap(unknownFooBarContainer);

The variable fooBar is inferred to have a type that implements both Foo and Bar and that cannot be denoted explicitly in source code.



A note about heap pollution

(Mostly for @MiserableVariable) Here's a walkthrough of how an unsafe method like getFooBar causes heap pollution, and its implications. Given the following interface and implementations:

interface Foo { }

static class Foo1 implements Foo {
public void foo1Method() { }
}

static class Foo2 implements Foo { }

Let's implement an unsafe method getFoo, similar to getFooBar but simplified for this example:

@SuppressWarnings("unchecked")
static <T extends Foo> T getFoo() {
//unchecked cast - ClassCastException is not thrown here if T is wrong
return (T)new Foo2();
}

public static void main(String[] args) {
Foo1 foo1 = getFoo(); //ClassCastException is thrown here
}

Here, when the new Foo2 is cast to T, it is "unchecked", meaning because of type erasure the runtime doesn't know it should fail, even though it should in this case since T was Foo1. Instead, the heap is "polluted", meaning references are pointing to objects they shouldn't have been allowed to.

The failure happens after the method returns, when the Foo2 instance tries to get assigned to the foo1 reference, which has the reifiable type Foo1.

You're probably thinking, "Okay so it blew up at the call site instead of the method, big deal." But it can easily get more complicated when more generics are involved. For example:

static <T extends Foo> List<T> getFooList(int size) {
List<T> fooList = new ArrayList<T>(size);
for (int i = 0; i < size; i++) {
T foo = getFoo();
fooList.add(foo);
}
return fooList;
}

public static void main(String[] args) {

List<Foo1> foo1List = getFooList(5);

// a bunch of things happen

//sometime later maybe, depending on state
foo1List.get(0).foo1Method(); //ClassCastException is thrown here
}

Now it doesn't blow up at the call site. It blows up sometime later when the contents of foo1List get used. This is how heap pollution gets harder to debug, because the exception stacktrace doesn't point you to the actual problem.

It gets even more complicated when the caller is in generic scope itself. Imagine instead of getting a List<Foo1> we're getting a List<T>, putting it in a Map<K, List<T>> and returning it to yet another method. You get the idea I hope.



Related Topics



Leave a reply



Submit