0

I struggle with the following inner class "inheriting" the generic parameter T from the outer class. The following lines are erroneous:

String string = "string";
new OuterClass<>()
    .inner(string)
    .call(s -> s.length());       // Cannot resolve method 'length' in 'Object'

Minimal and reproducible example of these classes:

public class OuterClass<T> {

    public InnerClass<T> inner(T object) {
        return new InnerClass<>(object);
    }

    @AllArgsConstructor(access = AccessLevel.PRIVATE)
    public static final class InnerClass<U> {

        U object;

        public final void call(Consumer<U> consumer) {
            consumer.accept(object);
        }
    }
}

Why does this happen? How am I supposed to specify the generic parameter through the creation of the inner class?

Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
  • Possible duplicate of [How do I get an inner class to inherit enclosing class' generic type?](https://stackoverflow.com/questions/8765211/how-do-i-get-an-inner-class-to-inherit-enclosing-class-generic-type), [Java generics with class and nested static interface](https://stackoverflow.com/questions/5211890/java-generics-with-class-and-nested-static-interface)? (Not the one to downvote!) – Naman Mar 24 '20 at 17:29
  • Java type resolution is not strong enough to deduce the required type in this case. Use `OuterClass()` instead of `OuterClass<>()`. – Alex Sveshnikov Mar 24 '20 at 17:35
  • I have edited the question. I have tried to replace `T` with `U` previously, yet the result remains the same. – Nikolas Charalambidis Mar 24 '20 at 17:35

1 Answers1

2

Essentially, this is because inference of type in the <> does not take into account the subsequent methods that you chain.

For example, this creates an ArrayList<Object>:

new ArrayList<>().add("");

If I insert the word String inside the <>, IntelliJ will not complain that the word String is redundant.

Similarly, this produces an error:

OuterClass<String>.InnerClass i = new OuterClass<>().inner(string);

Because the right hand side produces a OuterClass<Object>.Inner, while the left hand side is a OuterClass<String>.InnerClass.

To fix this, you need to specify the generic parameter:

new OuterClass<String>()
    .inner(string)
    .call(s -> s.length()); 

Regarding your edit, the edited inner method still depends on the generic type of Outer, which cannot be inferred correctly.

You can make the Inner object created not depend on the generic type of Outer by adding another generic parameter:

public <U> InnerClass<U> inner(U object) {
    return new InnerClass<>(object);
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Right, my mistake. I have completely forgotten this issue as I started to use inner classes. Thanks for the help. – Nikolas Charalambidis Mar 24 '20 at 17:37
  • @Sweeper the last suggestion leaves the `T` in `OuterClass` unused in the current context. I would consider a cleaner way to represent the intended code would be `OuterClass outerClass = new OuterClass<>(); OuterClass.InnerClass innerClass = outerClass.inner(string); innerClass.call(String::length);` – Naman Mar 24 '20 at 17:42
  • @Naman Yeah. Without context, we don't know what these types represent. They might be used somewhere else. And after the OP's edit, it seems like the operation `new OuterClass<>().inner(string).call(...)` doesn't need to depend on `T` anymore, because OP introduced `U`. – Sweeper Mar 24 '20 at 17:45
  • @Naman I thought OP's intention was to chain methods. If you were to write the operation in separate statements. then no modification to the classes is needed, right? – Sweeper Mar 24 '20 at 17:49
  • If you mean the code before the edit, then `OuterClass outerClass = new OuterClass<>(); outerClass.inner(string).call(String::length);` would have been sufficient to represent the chain we are talking about. The question mislead on an awkward time with an acceptance of a half-written answer. Only @Nikolas can tell what the requirement was and is. – Naman Mar 24 '20 at 17:58