java - Java中使用泛型动态返回调用类的类型

标签 java generics this

考虑以下类。

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() 方法返回父类(super class)类型,并且必须再次向下转换为 ClassB 才能使用 addTwice() 方法。我尝试在 addOnce() 方法中使用泛型来解决这个问题。

addOnce 的新实现:

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

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

最佳答案

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

方法一:泛型

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

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<?>类型。除了丑陋之外,如果您尝试序列化这些实例,这会变得很棘手。 AbstractClassA.class返回 Class<AbstractClassA>不是 Class<AbstractClassA<?>> ,这可以force you to do more unchecked casts .

此外,AbstractClassA 的子类继承 <R>类型,因此如果您想定义保留该模式的新方法(例如,在 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并将每个调用放在自己的线路上。在使用了尝试过第一种和第三种方法的代码后,我发现它周围的丑陋很快就会变得陈旧,而且通常带来的痛苦超过了它的值(value)。 (我从来没有见过第二种方法有效,因为它太痛苦了以至于没有人愿意尝试。:))

关于java - Java中使用泛型动态返回调用类的类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58056196/

相关文章:

java - 如何更新 JLabel 内的图像并将其发布到屏幕

javascript - REPL和脚本之间的'this'不同

c++ - "this"指针只是编译时的东西吗?

java - Web 应用程序中的共享 session

java - 将 swingNode 添加到 javaFx

java - 根据未知数量的键对对象列表进行排序

java - 遍历数据结构的元素而不是集合

java - 具有继承和泛型的流畅 API

c# - 关于 C# 泛型的最重要的事情......经验教训

javascript - this.setState 不是函数,.bind(this) 也不是函数