Java inherited Fluent method return type in multiple level hierarchies
Asked Answered
V

1

2

So following the solution described in Java - Inherited Fluent method return type to return incident class' type, not parent's. I want to extend it to multiple levels.

The solution works in one level obviously. Here is compiled and runnable code (no dependencies):

public enum X {
    ;
    static interface BaseFoo<T, S extends BaseFoo<T, S>> {
        S foo();
    }

    static interface Foo<T> extends BaseFoo<T, Foo<T>> {
        void foo1();
    }

    static abstract class AbstractFooBase<T, S extends BaseFoo<T, S>> implements BaseFoo<T, S> {
        abstract void internalFoo();
        @Override
        public S foo() {
            internalFoo();
            return (S)this;
        }
    }

    static class FooImpl<T> extends AbstractFooBase<T, Foo<T>> implements Foo<T> {
        @Override
        void internalFoo() {
            System.out.println("inside FooImpl::internalFoo()");
        }

        @Override
        public void foo1() {
            System.out.println("inside FooImpl::foo1()");
        }
    }

    public static void main(String[] args) {
        Foo<String> foo = new FooImpl<String>();
        foo.foo().foo1();
    }
}

However things getting difficult when I add an new level in the object inheritance hierarchy. The code below won't compile:

public enum X {
    ;
    static interface BaseFoo<T, S extends BaseFoo<T, S>> {
        S foo();
    }

    static interface Foo<T> extends BaseFoo<T, Foo<T>> {
        void foo1();
    }

    static interface BaseBar<T, S extends BaseBar<T, S>> extends BaseFoo<T, S> {
        S bar();
    }

    static interface Bar<T> extends BaseBar<T, Bar<T>> {
        void bar1();
    }

    static abstract class AbstractFooBase<T, S extends BaseFoo<T, S>> implements BaseFoo<T, S> {
        abstract void internalFoo();
        @Override
        public S foo() {
            internalFoo();
            return (S)this;
        }
    }

    static class FooImpl<T> extends AbstractFooBase<T, Foo<T>> implements Foo<T> {
        @Override
        void internalFoo() {
            System.out.println("inside FooImpl::internalFoo()");
        }

        @Override
        public void foo1() {
            System.out.println("inside FooImpl::foo1()");
        }
    }

    static abstract class AbstractBarBase<T, S extends BaseBar<T, S>> extends FooImpl<T> implements BaseBar<T, S> {
        abstract void internalBar();
        @Override
        public S bar() {
            internalBar();
            return (S)this;
        }
    }

    static class BarImpl<T> extends AbstractBarBase<T, Bar<T>> implements Bar<T> {
        @Override
        void internalBar() {
            System.out.println("inside BarImpl::internalBar()");
        }

        @Override
        public void bar1() {
            System.out.println("inside BarImpl::bar1()");
        }
    }

    public static void main(String[] args) {
        Foo<String> foo = new FooImpl<String>();
        foo.foo().foo1();

        Bar<Boolean> bar = new BarImpl<Boolean>();
        bar.foo().bar1();
    }
}

The compile time error message is:

X.java:40: X.BaseFoo cannot be inherited with different arguments: <T,S> and <T,X.Foo<T>>
    static abstract class AbstractBarBase<T, S extends BaseBar<T, S>> extends FooImpl<T> implements BaseBar<T, S> {
                    ^
X.java:49: X.BaseFoo cannot be inherited with different arguments: <T,X.Bar<T>> and <T,X.Foo<T>>
    static class BarImpl<T> extends AbstractBarBase<T, Bar<T>> implements Bar<T> {
           ^
Note: X.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
2 errors

Any idea how to work around it?

Versicolor answered 17/10, 2013 at 2:5 Comment(1)
Looks like no one loves long code in the questions. I posted another question which simplified this one: #19436915Versicolor
C
2

This is your inheritance hierarchy:

inheritance hierarchy

As you can see, some of these types are inheriting the same interface type more than once. In fact, BarImpl implements BaseFoo four times over, and some of the inheritance chains provide differing arguments for its type parameter S. It can be said that BarImpl implements the following:

  • BaseFoo<T, Foo<T>> (via Foo)
  • BaseFoo<T, Foo<T>> (via FooImpl)
  • BaseFoo<T, Bar<T>> (via Bar)
  • BaseFoo<T, Bar<T>> (via BarImpl)

The same interface cannot be implemented with different type arguments, so you get a compiler error.

As I pointed out on your followup question, my answer here discusses how to properly emulate the "self-type" to implement a hierarchical fluent builder pattern like you're trying to do. In it, I point out the need to maintain a variable "self-type" (S in your code) in all intermediate types - only resolving it with a "leaf class" that is understood to be final. Your code is violating that rule because the intermediate types Foo, Bar, and FooImpl are prematurely resolving S.

The following solution resolves the issue:

static interface BaseFoo<T, S extends BaseFoo<T, S>> {
    S foo();
}

static interface Foo<T, S extends Foo<T, S>> extends BaseFoo<T, S> {
    void foo1();
}

static interface BaseBar<T, S extends BaseBar<T, S>> extends BaseFoo<T, S> {
    S bar();
}

static interface Bar<T, S extends Bar<T, S>> extends BaseBar<T, S> {
    void bar1();
}

static abstract class AbstractFooBase<T, S extends BaseFoo<T, S>> implements BaseFoo<T, S> {
    abstract void internalFoo();
    @Override
    public S foo() {
        internalFoo();
        return (S)this;
    }
}

static abstract class AbstractIntermediateFoo<T, S extends AbstractIntermediateFoo<T, S>> extends AbstractFooBase<T, S> implements Foo<T, S> {
    @Override
    void internalFoo() {
        System.out.println("inside FooImpl::internalFoo()");
    }

    @Override
    public void foo1() {
        System.out.println("inside FooImpl::foo1()");
    }
}

static final class FooImpl<T> extends AbstractIntermediateFoo<T, FooImpl<T>> { }

static abstract class AbstractBarBase<T, S extends AbstractBarBase<T, S>> extends AbstractIntermediateFoo<T, S> implements BaseBar<T, S> {
    abstract void internalBar();
    @Override
    public S bar() {
        internalBar();
        return (S)this;
    }
}

static final class BarImpl<T> extends AbstractBarBase<T, BarImpl<T>> implements Bar<T, BarImpl<T>> {
    @Override
    void internalBar() {
        System.out.println("inside BarImpl::internalBar()");
    }

    @Override
    public void bar1() {
        System.out.println("inside BarImpl::bar1()");
    }
}

public static void main(String[] args) {
    FooImpl<String> foo = new FooImpl<String>();
    foo.foo().foo1();

    BarImpl<Boolean> bar = new BarImpl<Boolean>();
    bar.foo().bar1();
}

My changes were the following:

  • Maintain S in Foo
  • Maintain S in Bar
  • Split FooImpl into the following:
    • AbstractIntermediateFoo, which is abstract, maintains S, and implements internalFoo and foo1.
    • FooImpl, which is concrete, final, and resolves S.
  • Make BarImpl final.
  • In main, declare foo and bar as FooImpl and BarImpl - coding to interface isn't feasible here.
Cardboard answered 19/10, 2013 at 22:38 Comment(5)
The reason I split BaseFoo from Foo is that I don't want the user application use two generic type when they want to use Foo, say, I just need this code Foo<String> foo = new MyFoo<String>(), instead of Foo<String, MyFoo> foo = new MyFoo<String>(); thus I want to make Foo a leaf inteface, which should not have two generic type in the declarationVersicolor
Coding to interface just isn't going to work with a hierarchical fluent builder. Think about what "leaf interface" implies - it's an inherent contradiction.Cardboard
The BaseStream and Stream in Java 8 API is a good example of the SELF interface and LEAF interface. The only thing is it doesn't scale to multiple level, I think this is mostly because Java doesn't support mixinVersicolor
I stand corrected - you're right that a leaf interface is possible. I had been thinking of a leaf type as the terminus of implementation, but you've shown me that's not necessary. I think the crux of your issue is that you want to code to interface with Foo, but also have Foo implemented by other types that "change their mind" about what S is. The only solution is to split Foo somehow, like I did with FooImpl. Does that help at all?Cardboard
No, consider I am doing something like Java API's Collection, List, implementing the leaf as a concrete (and even final) class is not acceptable. I guess I will have to go back to the original approach that is described in #19257311. I give you a UP VOTE anyway to thank you for your effort spending here ;-)Versicolor

© 2022 - 2024 — McMap. All rights reserved.