最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

java - Having trouble with generics - Stack Overflow

programmeradmin4浏览0评论

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
Add a comment  | 

1 Answer 1

Reset to default 5

What 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).

发布评论

评论列表(0)

  1. 暂无评论