Apologies for the poor title. I would appreciate some help in crafting a better one, but I don't even know how to describe my problem.
Let's say that I have the following types.
interface M<K, V> {}
interface F<I, O> {}
And let's say that I make a class that implements both of these interfaces.
record A<A1, A2> (A1 a1, A2 a2)
implements
M<A1, A2>,
F<A1, A2>
{}
Ok, cool. I can now use A
anywhere that I otherwise would have used M
or F
.
Now here is my problem. I am trying to do the following, and failing with compiler errors.
public class DoublyNestedGenerics
{
interface M<K, V> {}
interface F<I, O> {}
record A <A1, A2> (A1 a1, A2 a2)
implements
M<A1, A2>,
F<A1, A2>
{}
private
static
<
D1,
D2,
D extends
M<D1, D2>
& F<D1, D2>
>
D
canItWork(final D1 d1, final D2 d2)
{
// return new A(d1, d2);
throw new UnsupportedOperationException();
}
}
Ok, so everything compiles. But if I uncomment the return and replace the exception, I get the following compilation error.
DoublyNestedGenerics.java:28: warning: [rawtypes] found raw type: A
return new A(d1, d2);
^
missing type arguments for generic class A<A1,A2>
where A1,A2 are type-variables:
A1 extends Object declared in record A
A2 extends Object declared in record A
Ok, makes sense. Let me change the return to be this instead.
return new A<>(d1, d2);
Pressing compile gives me the following compiler error.
DoublyNestedGenerics.java:28: error: incompatible types: cannot infer type arguments for A<>
return new A<>(d1, d2);
^
reason: no instance(s) of type variable(s) A1,A2 exist so that A<A1,A2> conforms to D
where A1,A2,D,D1,D2 are type-variables:
A1 extends Object declared in record A
A2 extends Object declared in record A
D extends M<D1,D2>,F<D1,D2> declared in method <D1,D2,D>canItWork(D1,D2)
D1 extends Object declared in method <D1,D2,D>canItWork(D1,D2)
D2 extends Object declared in method <D1,D2,D>canItWork(D1,D2)
1 error
I did not fully understand the error, so I decided to simplify the problem.
I changed the method to instead use some hardcoded types, instead of just D1
and D2
. Here is what the new method looks like.
private
static
<
D extends
M<Integer, Integer>
& F<Integer, Integer>
>
D
canItWorkAttempt2(final Integer d1, final Integer d2)
{
// return new A<>(d1, d2);
throw new UnsupportedOperationException();
}
Swapping out the comments and pressing compile gave me the following compiler error.
DoublyNestedGenerics.java:44: error: incompatible types: cannot infer type arguments for A<>
return new A<>(d1, d2);
^
reason: no instance(s) of type variable(s) A1,A2 exist so that A<A1,A2> conforms to D
where A1,A2,D are type-variables:
A1 extends Object declared in record A
A2 extends Object declared in record A
D extends M<Integer,Integer>,F<Integer,Integer> declared in method <D>canItWorkAttempt2(Integer,Integer)
1 error
Ok, much smaller, and therefore, easier to parse.
One thing that stood out to me was that it said A1 extends Object declared in record A
.
Well that's not right -- it should be Integer
, not Object
. Maybe the inference need some help. So, I changed the return to be this instead.
return new A<Integer, Integer>(d1, d2);
Which resulted in the following compiler error.
DoublyNestedGenerics.java:44: error: incompatible types: A<Integer,Integer> cannot be converted to D
return new A<Integer, Integer>(d1, d2);
^
where D is a type-variable:
D extends M<Integer,Integer>,F<Integer,Integer> declared in method <D>canItWorkAttempt2(Integer,Integer)
1 error
Progress! I now started to have suspicions, and to confirm them, I decided to simplify my problem significantly. I created the following class.
record N (Integer n1, Integer n2)
implements
M<Integer, Integer>,
F<Integer, Integer>
{}
Compiled with no issues. Beautiful, let's try to change the return type to use this instead. Here is what I changed the return to.
return new N(d1, d2);
With high hopes, I pressed compile.
DoublyNestedGenerics.java:50: error: incompatible types: N cannot be converted to D
return new N(d1, d2);
^
where D is a type-variable:
D extends M<Integer,Integer>,F<Integer,Integer> declared in method <D>canItWorkAttempt2(Integer,Integer)
1 error
Very disappointing. But something else popped out to me. In the error, it said where D is a type-variable: D extends M<Integer,Integer>,F<Integer,Integer>
.
They used the word extends. Out of desperation, I tried to add the following types.
interface C<C1, C2> extends M<C1, C2>, F<C1, C2> {}
record N2 (Integer n1, Integer n2) implements C<Integer, Integer> {}
And then, from there, I changed my return to say this.
final C<Integer, Integer> blah = new N2(d1, d2);
return blah;
Then I pressed compile.
DoublyNestedGenerics.java:55: error: incompatible types: C<Integer,Integer> cannot be converted to D
return blah;
^
where D is a type-variable:
D extends M<Integer,Integer>,F<Integer,Integer> declared in method <D>canItWorkAttempt2(Integer,Integer)
1 error
At this point, I am annoyed.
The error message is somehow telling me that C<Integer,Integer> cannot be converted to D
. And it also is telling me that D is a type-variable
, specifically that D extends M<Integer,Integer>,F<Integer,Integer>
WHICH IS EXACTLY WHAT C DOES. And yet, it still does not work?
Which brings me here. What am I missing? And again, would like some help crafting a better title, if anyone is willing to make suggestions.
Finally, here is the code in full, in case it was harder to follow along.
public class DoublyNestedGenerics
{
interface M<K, V> {}
interface F<I, O> {}
record A <A1, A2> (A1 a1, A2 a2)
implements
M<A1, A2>,
F<A1, A2>
{}
record N (Integer n1, Integer n2)
implements
M<Integer, Integer>,
F<Integer, Integer>
{}
interface C<C1, C2> extends M<C1, C2>, F<C1, C2> {}
record N2 (Integer n1, Integer n2) implements C<Integer, Integer> {}
private
static
<
D1,
D2,
D extends
M<D1, D2>
& F<D1, D2>
>
D
canItWork(final D1 d1, final D2 d2)
{
// return new A<>(d1, d2);
throw new UnsupportedOperationException();
}
private
static
<
D extends
M<Integer, Integer>
& F<Integer, Integer>
>
D
canItWorkAttempt2(final Integer d1, final Integer d2)
{
final C<Integer, Integer> blah = new N2(d1, d2);
return blah;
// return new N(d1, d2);
// throw new UnsupportedOperationException();
}
}
Apologies for the poor title. I would appreciate some help in crafting a better one, but I don't even know how to describe my problem.
Let's say that I have the following types.
interface M<K, V> {}
interface F<I, O> {}
And let's say that I make a class that implements both of these interfaces.
record A<A1, A2> (A1 a1, A2 a2)
implements
M<A1, A2>,
F<A1, A2>
{}
Ok, cool. I can now use A
anywhere that I otherwise would have used M
or F
.
Now here is my problem. I am trying to do the following, and failing with compiler errors.
public class DoublyNestedGenerics
{
interface M<K, V> {}
interface F<I, O> {}
record A <A1, A2> (A1 a1, A2 a2)
implements
M<A1, A2>,
F<A1, A2>
{}
private
static
<
D1,
D2,
D extends
M<D1, D2>
& F<D1, D2>
>
D
canItWork(final D1 d1, final D2 d2)
{
// return new A(d1, d2);
throw new UnsupportedOperationException();
}
}
Ok, so everything compiles. But if I uncomment the return and replace the exception, I get the following compilation error.
DoublyNestedGenerics.java:28: warning: [rawtypes] found raw type: A
return new A(d1, d2);
^
missing type arguments for generic class A<A1,A2>
where A1,A2 are type-variables:
A1 extends Object declared in record A
A2 extends Object declared in record A
Ok, makes sense. Let me change the return to be this instead.
return new A<>(d1, d2);
Pressing compile gives me the following compiler error.
DoublyNestedGenerics.java:28: error: incompatible types: cannot infer type arguments for A<>
return new A<>(d1, d2);
^
reason: no instance(s) of type variable(s) A1,A2 exist so that A<A1,A2> conforms to D
where A1,A2,D,D1,D2 are type-variables:
A1 extends Object declared in record A
A2 extends Object declared in record A
D extends M<D1,D2>,F<D1,D2> declared in method <D1,D2,D>canItWork(D1,D2)
D1 extends Object declared in method <D1,D2,D>canItWork(D1,D2)
D2 extends Object declared in method <D1,D2,D>canItWork(D1,D2)
1 error
I did not fully understand the error, so I decided to simplify the problem.
I changed the method to instead use some hardcoded types, instead of just D1
and D2
. Here is what the new method looks like.
private
static
<
D extends
M<Integer, Integer>
& F<Integer, Integer>
>
D
canItWorkAttempt2(final Integer d1, final Integer d2)
{
// return new A<>(d1, d2);
throw new UnsupportedOperationException();
}
Swapping out the comments and pressing compile gave me the following compiler error.
DoublyNestedGenerics.java:44: error: incompatible types: cannot infer type arguments for A<>
return new A<>(d1, d2);
^
reason: no instance(s) of type variable(s) A1,A2 exist so that A<A1,A2> conforms to D
where A1,A2,D are type-variables:
A1 extends Object declared in record A
A2 extends Object declared in record A
D extends M<Integer,Integer>,F<Integer,Integer> declared in method <D>canItWorkAttempt2(Integer,Integer)
1 error
Ok, much smaller, and therefore, easier to parse.
One thing that stood out to me was that it said A1 extends Object declared in record A
.
Well that's not right -- it should be Integer
, not Object
. Maybe the inference need some help. So, I changed the return to be this instead.
return new A<Integer, Integer>(d1, d2);
Which resulted in the following compiler error.
DoublyNestedGenerics.java:44: error: incompatible types: A<Integer,Integer> cannot be converted to D
return new A<Integer, Integer>(d1, d2);
^
where D is a type-variable:
D extends M<Integer,Integer>,F<Integer,Integer> declared in method <D>canItWorkAttempt2(Integer,Integer)
1 error
Progress! I now started to have suspicions, and to confirm them, I decided to simplify my problem significantly. I created the following class.
record N (Integer n1, Integer n2)
implements
M<Integer, Integer>,
F<Integer, Integer>
{}
Compiled with no issues. Beautiful, let's try to change the return type to use this instead. Here is what I changed the return to.
return new N(d1, d2);
With high hopes, I pressed compile.
DoublyNestedGenerics.java:50: error: incompatible types: N cannot be converted to D
return new N(d1, d2);
^
where D is a type-variable:
D extends M<Integer,Integer>,F<Integer,Integer> declared in method <D>canItWorkAttempt2(Integer,Integer)
1 error
Very disappointing. But something else popped out to me. In the error, it said where D is a type-variable: D extends M<Integer,Integer>,F<Integer,Integer>
.
They used the word extends. Out of desperation, I tried to add the following types.
interface C<C1, C2> extends M<C1, C2>, F<C1, C2> {}
record N2 (Integer n1, Integer n2) implements C<Integer, Integer> {}
And then, from there, I changed my return to say this.
final C<Integer, Integer> blah = new N2(d1, d2);
return blah;
Then I pressed compile.
DoublyNestedGenerics.java:55: error: incompatible types: C<Integer,Integer> cannot be converted to D
return blah;
^
where D is a type-variable:
D extends M<Integer,Integer>,F<Integer,Integer> declared in method <D>canItWorkAttempt2(Integer,Integer)
1 error
At this point, I am annoyed.
The error message is somehow telling me that C<Integer,Integer> cannot be converted to D
. And it also is telling me that D is a type-variable
, specifically that D extends M<Integer,Integer>,F<Integer,Integer>
WHICH IS EXACTLY WHAT C DOES. And yet, it still does not work?
Which brings me here. What am I missing? And again, would like some help crafting a better title, if anyone is willing to make suggestions.
Finally, here is the code in full, in case it was harder to follow along.
public class DoublyNestedGenerics
{
interface M<K, V> {}
interface F<I, O> {}
record A <A1, A2> (A1 a1, A2 a2)
implements
M<A1, A2>,
F<A1, A2>
{}
record N (Integer n1, Integer n2)
implements
M<Integer, Integer>,
F<Integer, Integer>
{}
interface C<C1, C2> extends M<C1, C2>, F<C1, C2> {}
record N2 (Integer n1, Integer n2) implements C<Integer, Integer> {}
private
static
<
D1,
D2,
D extends
M<D1, D2>
& F<D1, D2>
>
D
canItWork(final D1 d1, final D2 d2)
{
// return new A<>(d1, d2);
throw new UnsupportedOperationException();
}
private
static
<
D extends
M<Integer, Integer>
& F<Integer, Integer>
>
D
canItWorkAttempt2(final Integer d1, final Integer d2)
{
final C<Integer, Integer> blah = new N2(d1, d2);
return blah;
// return new N(d1, d2);
// throw new UnsupportedOperationException();
}
}
Share
Improve this question
asked Feb 14 at 5:06
davidalayachewdavidalayachew
1,0553 gold badges16 silver badges38 bronze badges
1 Answer
Reset to default 5What you're missing is that D
is not parameterized by the canItWork
method. It's parameterized by the caller of the method. You're right that A
meets the bounds of D
, but so could any other type. The canItWork
method can't know which type the caller expects.
Examine the following:
public class GenericTesting {
interface Foo<T, U> {}
interface Bar<T, U> {}
record FooBar<T, U>(T t, U u) implements Foo<T, U>, Bar<T, U> {}
record OtherFooBar<T, U>(T t, U u) implements Foo<T, U>, Bar<T, U> {}
static <T, U, R extends Foo<T, U> & Bar<T, U>> R test(T t, U u) {
// ERROR - Type mismatch: cannot convert from GenericTesting.FooBar<T,U> to R
return new FooBar<T, U>(t, u);
}
public static void main(String[] args) {
// Note: Unnamed variables ('_') require Java 22+
OtherFooBar<String, String> _ = test("Hello", "World!");
}
}
Here, the caller of test
expects an instance of OtherFooBar<String, Stirng>
but will get an instance of FooBar<String, String>
instead. Those two types are incompatible. This scenario is why return new FooBar<T, U>(t, u)
fails to compile.
The OtherFooBar<String, String> _ = test("Hello", "World!");
line is perfectly legal according to the Java Language Specification. From that fact, it follows that the implementation of test
cannot be allowed to compile. Otherwise you'd end up with code that is not type-safe, which nullifies the primary reason for having a statically typed language (like Java) in the first place. So, since there's at least one case where return new FooBar<T, U>(t, u);
would result in type-unsafe code, it must be rejected unconditionally (i.e., fail to compile).
Unchecked cast
You can get the above example to compile (with a warning) by changing test
to cast the result:
static <T, U, R extends Foo<T, U> & Bar<T, U>> R test(T t, U u) {
// WARNING - Type safety: Unchecked cast from GenericTesting.FooBar<T,U> to R
return (R) new FooBar<T, U>(t, u);
}
But there's a reason that gives a warning, as now you can easily, and will without any other changes to the example, run into a ClassCastException
at run-time. Worse, the cast is implicit for the caller, making it seem like the code is safe even though it's not. You should only use unchecked casts when you know it will succeed in all cases (there are times where we know better than the compiler, which is why features like casting exist).