【问题标题】:Dynamically return with the type of calling class using generics in Java在 Java 中使用泛型动态返回调用类的类型
【发布时间】:2019-09-23 05:35:03
【问题描述】:

考虑以下类。

public class ClassA {

    public ArrayList<String> v1 = new ArrayList<>();

    public ClassA addOnce(String s1) {
        v1.add(s1);
        return this;
    }

}


public class ClassB extends ClassA {

    public ClassB addTwice(String s2) {
        v1.add(s2);
        v1.add(s2);
        return this;
    }

}

现在,可以对ClassA进行如下一系列操作

new ClassA.addOnce("1").addOnce("2");

但仍然无法在ClassB 上实现相同的效果,因为new ClassB().addOnce("1").addTwice("2"); 无效,因为addOnce() 方法返回超类类型,并且必须再次转换为ClassB 才能使用addTwice() 方法。我尝试在addOnce() 方法中使用泛型来解决这个问题。

addOnce 的新实现:

public<R extends ClassA> R addOnce(String s1) {
    v1.add(s1);
    return (R)this;
}

但编译器仍然要求进行强制转换,因为它无法仅根据下一个方法调用找到返回类型,因为该方法可能对多个类可用(addOnce() 对ClassA 和ClassB 都可用) 并且返回类型不会收敛。有没有办法以调用类的类型返回this,通常是在子类调用父类方法时?

【问题讨论】:

    标签: java generics this


    【解决方案1】:

    有两种方法可以做到这一点,但我都不推荐。

    方法一:泛型

    具体来说,递归的(指回泛型类本身):

    public class ClassA<R extends ClassA<R>> { // note the recursive nature
        public ArrayList<String> v1 = new ArrayList<>();
    
        @SuppressWarnings("unchecked")
        protected R self() {
          return (R) this;
        }
    
        public R addOnce(String s1) {
            v1.add(s1);
            return self();
        }
    }
    

    如果您不喜欢 SuppressWarnings(而且您不应该这样做!它们可以隐藏错误),您可以通过创建一个 abstract class AbstractClassA 来解决这个问题,每个子类都覆盖一个 protected abstract R self():

    // AbstractClassA looks like ClassA above; it contains addOnce
    class ClassA extends AbstractClassA<ClassA> {
        @Override
        public ClassA self() {
            return this;
        }
    }
    
    class ClassB extends AbstractClassA<ClassB> {
        ClassB addTwice(String s2) {
            ...
            return this;
        }
    }
    

    这确实意味着您的大多数用例将使用通配符 AbstractClassA&lt;?&gt; 类型。除了丑陋之外,如果您尝试序列化这些实例,这会变得很麻烦。 AbstractClassA.class 返回一个Class&lt;AbstractClassA&gt;,不是一个Class&lt;AbstractClassA&lt;?&gt;&gt;,可以是force you to do more unchecked casts。

    另外,AbstractClassA 的子类不继承 &lt;R&gt; 类型,所以如果你想定义保持模式的新方法(例如在 ClassB 中),你必须使用新的通用参数重复该模式。我的预测是你会后悔的。

    方法二:覆盖每个方法

    另一种方法是手动覆盖基类中的所有方法。当你重写一个方法时,你can have it return a subclass of the original method's return type。所以:

    public class ClassA {
    
        public ArrayList<String> v1 = new ArrayList<>();
    
        public ClassA addOnce(String s1) {
            v1.add(s1);
            return this;
        }
    
    }
    public class ClassB extends ClassA {
    
        public ClassB addOnce(String s2) {
            super.addOnce(s2);
            return this; // same reference as in the super method, but now typed to ClassB
        }
        ...
    
    }
    

    这种方法没有任何隐藏的陷阱,就像第一种方法一样。它就像你想象的那样工作。这只是一种痛苦,这意味着每次将方法添加到 ClassA 时,如果您希望事情继续工作,您也必须将该方法添加到 ClassB(和 ClassC 等)。如果您无法控制 ClassB/C/etc(例如,如果您正在发布 API),那么这可能会出现问题,并使您的 API 感觉不成熟。

    方法三:首先从更具体的子类开始链

    这不是您问题的真正答案,而是解决相同问题的模式。这个想法是让 ClassA 和 ClassB 保持原样,但始终首先调用 ClassB 的方法:

    new ClassB().addTwice("2").addOnce("1");
    

    这种方法效果很好,但有点尴尬。大多数时候,人们的想法是首先在基类上设置东西,然后在子类上设置子类特定的东西。如果您可以扭转这种情况,并且您真的想要这些链接方法,那么这可能是我会选择的方法。但我真正的建议是:

    我的建议:算了

    Java 只是不能很好地处理这些类型的方法链。我建议你忘记整件事,不要试图在这一点上与语言抗争。使方法返回void 并将每个调用放在自己的行上。在使用尝试过第一种和第三种方法的代码后,我发现它周围的丑陋很快就会变老,而且通常比它的价值更痛苦。 (我从来没有见过第二种方法有效,因为它太痛苦了,没有人想尝试它。:))

    【讨论】:

    • 我得到了覆盖方法,但我仍然不明白你的方法 1。不过,new ClassB().addOnce("1").addTwice("2") 不适用于方法 1。我错过了什么吗?顺便说一句,我也不会编写/建议编写这样的代码,但只是想知道实现这一点的可能性。
    • 哦,对不起,我有一个错字。 addOnce 应该返回 R,not ClassA。我会解决的。
    • 是的,我猜错了,固定的(返回类型为 R)甚至不起作用。
    • 在我的机器上工作(虽然我必须添加v1,我忘了从你的例子中复制)。不幸的是,我必须在晚上出去,所以我暂时无法进行进一步的编辑。祝你好运!
    • 方法 1 有效!我忘了做class ClassB extends ClassA&lt;ClassB&gt; 并且感谢您没有在您的答案中添加它,现在我探索并理解了它是如何工作的:)
    【解决方案2】:

    要使类型推断起作用,您需要在ClassA 中声明泛型参数。 ClassA 应该如下所示:

    public class ClassA<T extends ClassA<T>> {
      public ArrayList<String> v1 = new ArrayList<>();
    
      public T addOnce(String s1) {
        v1.add(s1);
        return (T) this;
      }
    }
    

    阅读更多关于Type Inference

    【讨论】:

      【解决方案3】:

      这个问题让我想起了方法overridding。

      class A {
      
          protected List<String> list = new LinkedList<>();
      
          public A addOnce(String s) {
              list.add(s);
              return this;
          }
      }
      
      class B extends A {
      
          public B addOnce(String s) {
              return (B) super.addOnce(s);
          }
      
          public B addTwice(String s) {
              super.addOnce(s);
              super.addOnce(s);
              return this;
          }
      }
      

      A 类为什么要知道B 类存在。这是B 的责任。在这个解决方案中,B 类将添加一次的功能委托给它的超类方法,但类型转换是由B 在获得返回值后执行的。在当前用例中确实不需要使用泛型。

      【讨论】:

        猜你喜欢
        • 2013-08-04
        • 2019-01-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-25
        • 1970-01-01
        • 2022-10-14
        相关资源
        最近更新 更多