How is it possible that a method with generic return type with a bound can be assigned to a variable outside of that bound?
Asked Answered
D

1

6

Suppose I have the following structure:

public interface A {
}

public interface B {
}

public interface B1 extends B {
}

public interface B2 extends B {
}

public class C implements A, B1 {
    private final String s;

    public C(final String s) {
        this.s = s;
    }
}

public class D implements A, B2 {
    private final Exception e;

    public D(final Exception e) {
        this.e = e;
    }
}

public class SomeClass<T> {
    private final T t;
    private final Exception e;

    public SomeClass(final T t, final Exception e) {
        this.t = t;
        this.e = e;
    }

    public <U extends B> U transform(final java.util.function.Function<T, ? extends U> mapper1, final java.util.function.Function<Exception, ? extends U> mapper2) {
        return t == null ? mapper2.apply(e) : mapper1.apply(t);
    }
}

When now we do the following in another class:

public class AnotherClass {
    public static void main(final String[] args) {
        SomeClass<String> someClass = new SomeClass<>("Hello World!", null);
// this line is what is bothering me
        A mappedResult = someClass.transform(C::new, D::new);
    }
}

The code compiles without any problems. Why does the code compile? How is it possible that the type of 'mappedResult' can be A, even though the generic U in the method is declared to be a subtype of B?

Declaratory answered 8/6, 2020 at 13:27 Comment(4)
I think U is inferred to be B & A both, so you can assign the result to an AAttainture
@user Really? I didn't know composition types were an actual thing in Java's type system... For example, you can't specify them explicitly: .<A & B>transform(...).Olvan
@Olvan Yeah, I don't know for sure either, but inferred types can be a lot more specific than specified ones. For example you can do var obj = new Object() { int x() {return 0;} }; System.out.println(obj.x()); and it should work because the compiler infers that the type of obj is not just Object, it also contains that methodAttainture
generics employ type erasure, this affects the compilation processBateau
D
0

Ok, so based on the comments on the question and some discussion with other people, there was a major point that I missed that might need addressing and that actually explains the answer given in the comments.

It's clear that the following compiles:

Object mappedResult = someClass.transform(C::new, D::new);

And yet Object is not a subclass of B, of course. The bound will ensure that the the types of C and D (in this case) will be a subtype of B, but they can be other types as well due thanks to other interfaces both C and D implement. The compiler will check what types they are and look at the most specific type(s) that they have in common. In this case, that is both A and B, so the type is derived to be A & B. Therefore, assigning this result to A is possible, because the compiler will derive the result to be an A as well.

The bound does provide some restrictions regarding the input, but not regarding the output and not regarding to the types of variables to which you can assign the result. That is what I was confused about before.

Another way to see this is the following: if the method had been defined as follows:

public <U> U transform(final java.util.function.Function<T, ? extends U> mapper1, final java.util.function.Function<Exception, ? extends U> mapper2) {
    return t == null ? mapper2.apply(e) : mapper1.apply(t);
}

then the result can still be assigned to an A or a B when calling it as before. The bound had no influence on that. All it ensures here is that both mapper functions need to map to a result that is a subtype of U. With the bound, that becomes a subtype of U which is a subtype of B. But the fact that the result is a subtype of A doesn't change the fact that it is also a subtype of B. Therefore, the result can be assigned to either type.

Declaratory answered 12/6, 2020 at 10:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.