有什么方法可以声明内部 class 与外部具有相同的泛型类型?

Is there any way to declare that an inner class has the same generic type as the outer?

有没有办法让内部 class 重用外部 class 的通用参数?


我有以下class

public class TaggedUnionBuilder<L,R>{
    private final Class<L> leftClass;
    private final Class<R> rightClass;

    public TaggedUnionBuilder( Class< L > leftClass, Class< R > rightClass ) {
        this.leftClass = leftClass;
        this.rightClass = rightClass;
    }

    TaggedUnion<L,R> left( L aLeft ){
        return new TaggedUnion<>( true, aLeft  );
    }

    TaggedUnion<L,R> right( R aRight ){
        return new TaggedUnion<>( false, aRight  );
    }


    public class TaggedUnion<L,R>{
        private final boolean isLeftClass;
        private final Object value;


        private TaggedUnion( boolean isLeftClass, Object value ) {
            this.isLeftClass = isLeftClass;
            this.value = value;
        }

        L left(){
            //vvv compiler error vvv
            return leftClass.cast( value );
            //^^^ compiler error ^^^
        }

        R right(){
            //vvv compiler error vvv
            return rightClass.cast( value );
            //^^^ compiler error ^^^
        }

        boolean isLeft(){
            return isLeftClass;
        }

        boolean isRight(){
            return !isLeftClass;
        }
    }

}

但是,我遇到了错误:

java: incompatible types: L cannot be converted to L

java: incompatible types: R cannot be converted to R

left()right() 方法中。

似乎 LRTaggedUnion 中的声明隐藏了 TaggedUnionBuilder 中的声明。如果我删除 TaggedUnion 的泛型参数声明,我不能 return 它作为泛型类型。

如果我从 TaggedUnion 中删除通用参数,我会得到错误

java: type TaggedUnionBuilder.TaggedUnion does not take parameters

一个解决方法是使内部 class TaggedUnion 成为 static class 并显式传入 TaggedUnionBuilder 的实例。这将解决问题。

public class TaggedUnionBuilder<L,R>{
    private final Class<L> leftClass;
    private final Class<R> rightClass;

    public TaggedUnionBuilder( Class< L > leftClass, Class< R > rightClass ) {
        this.leftClass  = leftClass;
        this.rightClass = rightClass;
    }

    public TaggedUnion<L,R> left( L aLeft ){
        return new TaggedUnion<>( this, true, aLeft  );
    }

    public TaggedUnion<L,R> right( R aRight ){
        return new TaggedUnion<>( this, false, aRight  );
    }


    public static class TaggedUnion<L,R>{
        private final boolean isLeftClass;
        private final Object value;
        private final TaggedUnionBuilder<L,R> parent;


        private TaggedUnion( TaggedUnionBuilder<L,R> aParent, boolean isLeftClass, Object aValue ) {
            this.parent = aParent;
            this.isLeftClass = isLeftClass;
            this.value = aValue;
        }

        public L left(){
            return parent.leftClass.cast( value );
        }

        public R right(){
            return parent.rightClass.cast( value );
        }

        public boolean isLeft(){
            return isLeftClass;
        }

        public boolean isRight(){
            return !isLeftClass;
        }
    }

}

但有效地使 TaggedUnion 成为一个单独的 class(只需要为 TaggedUnionBuilder 上的 classes 创建访问器)。

问题的真正答案(由@lexicore 在评论中给出)是使用父名称限定返回的类型,如下所示:

public class TaggedUnionBuilder<A,B>{
    private final Class<A> classA;
    private final Class<B> classB;

    public TaggedUnionBuilder( Class< A > classA, Class< B > classB ) {
        this.classA = classA;
        this.classB = classB;
    }

    public TaggedUnionBuilder<A,B>.TaggedUnion left( A aA ){
        return new TaggedUnion( true, aA  );
    }

    public TaggedUnionBuilder<A,B>.TaggedUnion right( B aB ){
        return new TaggedUnion( false, aB  );
    }


    public class TaggedUnion{
        private final boolean isClassA;
        private final Object value;

        private TaggedUnion( boolean isClassA, Object aValue ) {
            this.isClassA = isClassA;
            this.value = aValue;
        }

        A left(){
            return classA.cast( value );
        }

        B right(){
            return classB.cast( value );
        }

        boolean isA(){
            return isClassA;
        }

        boolean isB(){
            return !isClassA;
        }
    }

}

但这意味着您不能在没有的情况下一般地引用TaggedUnion,包括类型中包含的class(即总是TaggedUnionBuilder<A,B>.TaggedUnion而不是TaggedUnion) 这在人体工程学上是残酷的,但回答了所述问题。

因为你有一个嵌套的 class,泛型类型也 "propagated" 到嵌套的 class 中。

public class Outer<A> {

    Inner in;

    Outer( A a ) {
        in = new Inner( a );
    }

    Inner get() {
        return in;
    }

    class Inner {
        A value;

         Inner( A value ) {
            this.value = value;
        }
    }

    public static void main( String[] args ) {
        String s = new Outer<>( "test" ).get().value;
        //And to declare an variable Inner
        Outer<String>.Inner i = new Outer<>( "test" ).get();
    }
}

声明一个 Outer<String> 将得到一个 Outer<String>.Innervalue 将是一个 String

如果你声明一个 Inner,你需要使用完整的 class 名称来指定泛型类型,有点冗长但正确的解决方案。

前言

首先让我怀疑 class 像你展示的那样的有用性。如果能够将一种类型转换为另一种类型,则至少有一种类型是接口,或者两种类型都处于继承关系中。在我看来,您可以使用 Class#isAssignableForm(Object obj), Class#isInstance(Object obj) and Class#cast(Object obj).

完成所有这些操作

部署 Builder Pattern 时,通常将构建器编写为实际类型的内部静态 class。这有助于提高可读性(重要的 class 是 POJO,而不是构建器)。此外,内部 (non-static) class 始终隐式引用周围的实例 class 它是从中创建的。


有没有办法让内部类重用外部类的泛型参数class?

是的。让我们看一下您的代码的一些简化版本。

public class TaggedUnionBuilder<A, B> {
    // ...
    public class TaggedUnion {
        // ...
    }
}

正如我们所知,我们通过首先实例化外部 class 的实例然后在该实例上调用 new <InnerClass>() 来实例化内部 classes:

TaggedUnionBuilder<String, Object> builder = new TaggedUnionBuilder<String, Object>();
TaggedUnionBuilder<String, Object>.TaggedUnion union = builder.new TaggedUnion();

写到这里,一看union的完整类型确实是TaggedUnionBuilder<String, Object>.TaggedUnion.


这对实施意味着什么?

我们可以在 TaggedUnion 中使用通用参数 AB。因此,我们可以将代码重构为:

public class TaggedUnionBuilder<A, B> {
    private final Class<A> classA;
    private final Class<B> classB;

    public TaggedUnionBuilder(Class<A> classA, Class<B> classB) {
        this.classA = classA;
        this.classB = classB;
    }

    TaggedUnion left(A a) {
        return new TaggedUnion(true, a);
    }

    TaggedUnion right(B b) {
        return new TaggedUnion(false, b);
    }

    public class TaggedUnion {
        private final boolean isClassA;
        private final Object  value;

        private TaggedUnion(boolean isClassA, Object value) {
            this.isClassA = isClassA;
            this.value = value;
        }

        A left() {
            return classA.cast(value);
        }

        B right() {
            return classB.cast(value);
        }

        boolean isA() {
            return isClassA;
        }

        boolean isB() {
            return !isClassA;
        }
    }
}