具有泛型返回类型的自引用方法,适用于多个继承类。

11

可能有点难以描述,但我会尝试一下 ;)

按照流畅的风格,一个类的方法通常返回类实例本身(this)。

public class A {

    public A doSomething() {

        // do something here

        return this;
    }

}

当扩展这样一个流畅风格的类时,可以通过在第一次继承步骤中使用通用类型,并在超类中将返回类型强制转换为此通用类型来轻松实现。

public class A<T extends A<T>> {

    public T doSomething() {

        // do something here

        return (T) this;
    }

}

public class B extends A<B> {

    // an extended class of class A

}

然而,在对这个扩展类进行另一次继承时,我在尝试定义方法的泛型返回类型(在上层类)和类描述本身时遇到了麻烦,例如,超级-超级类中的方法将不会返回扩展类的类型,而是返回超级类的类型。然而,我的意图是这些流式风格的方法应始终返回当前类的类型(而不是其上层类的类型)。

那么,有没有可能通过利用泛型来定义解决方案呢?

PS: 我知道,一个简单的解决方法可能是在扩展类中覆盖所有这些方法并将它们强制转换为当前类型。但是,我对更优雅的解决方案很感兴趣 ;)


1
可能相关的问题:https://dev59.com/nGsz5IYBdhLWcg3w3r0J - Michael McGowan
4个回答

5
你可以尝试以下方法:
class A<T> { }

class B<T extends A<? super T>> extends A<T> { }

class C extends B<C> { }

2
你可以像这样做:

你可以这样做:

public class TestFluent<T extends TestFluent<?>> {

    public T get() {
        return (T) this;
    }

    public static void main(final String[] args) {
        TestFluent2<TestFluent2<?>> f2 = new TestFluent2<TestFluent2<?>>();
        TestFluent2<?> result2 = f2.get();

        TestFluent3<TestFluent3<?>> t3 = new TestFluent3<TestFluent3<?>>();
        TestFluent3<?> result3 = t3.get();

        System.out.println(result2);
        System.out.println(result3);
    }
}

class TestFluent2<T extends TestFluent2<?>> extends TestFluent<T> {
}

class TestFluent3<T extends TestFluent3<?>> extends TestFluent2<T> {
}

这将返回:
TestFluent2@7919298d
TestFluent3@62f72617

是的,这看起来有点有希望。我之前也尝试过使用通配符,但是我还没有找到一个适用的解决方案。 - zazi

1

通常情况下,无法“返回当前类的类型”。

即使是您上面发布的代码也不安全:public class C extends A<B> 是合法的,但在其上调用 doSomething() 并期望得到一个 B 将会导致崩溃。


因为它是 public class B extends A<B> 而不是 public class C extends A<B> ;) - zazi
我是说在你定义了public class B extends A<B>之后,定义public class C extends A<B>是合法的,不是吗? - newacct
当然,这是合法的。但是,它没有引起预期的行为,对吧?(无论如何...) - zazi
当你执行C foo = ...; B bar = foo.doSomething();时,程序会崩溃。因此这不是安全的。 - newacct
再说一遍,那不是我想做的:我想做的是:B foo = ...; foo.doSomething().doSomethingElse().doSomethingDifferent(). ... 所以它总是返回 B(自引用)。所以请不要将所描述的用例与其他事物混淆。 - zazi
@zazi 不幸的是,这不仅仅关乎用例,也不仅仅关乎您(作为此代码的开发者)会做什么。它关乎其他人将如何使用您的API,以及如何防止非法使用(从而防止错误和故障)。否则,还有大量更简单的解决方案。请注意:发布代码后,即使您自己也将成为“其他人”的一员!!!人们会犯意外的错误。 - hkoosha

0
使用委托来实现可扩展的流畅接口。优点:无需转换。缺点:冗长。
public interface FluentGranddad< C extends FluentGranddad< C > > {
    C appendFoo( Foo foo );
}
public interface FluentDad< C extends FluentDad< C > > extends FluentGranddad< C > {
    C appendBar( Bar b );
}
public interface FluentKid< C extends FluentKid< C > > extends FluentDad< C > {
    C appendSplat( Splat s );
}

public class Babbler implements FluentKid< Babbler > {
    FluentDad< ? > dad;
    public Babbler( FluentDad< ? > dad ) {
        this.dad = dad;
    }

    // delegation methods
    @Override public Babbler appendFoo( Foo foo ) {
        dad.appendFoo( foo );
        return this;
    }
    @Override public Babbler appendBar( Bar bar ) {
        dad.appendBar( bar );
        return this;
    }

    // example instance method
    @Override public Babbler appendSplat( Splat s ) {
        dad.getState().append( s.toString() );
        return this;
    }
}

是的,我知道这是目前我采用的简单解决方法。然而,每次引入新的扩展层时都要重复这个过程有点繁琐(在我看来)。 - zazi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接