java - 在 Java 中使用泛型动态返回调用类的类型
问题描述
考虑以下类。
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
and都可用ClassB
)并且返回类型不会收敛。有没有办法返回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
返回 a Class<AbstractClassA>
,而不是a Class<AbstractClassA<?>>
,这可能会迫使您进行更多未经检查的强制转换。
此外,AbstractClassA 的子类不继承该<R>
类型,因此如果要定义保持模式的新方法(例如在 ClassB 中),则必须在每个方法中使用新的泛型参数重复该模式。我的预测是你会后悔的。
方法二:覆盖每个方法
另一种方法是手动覆盖基类中的所有方法。当你重写一个方法时,你可以让它返回原始方法的返回类型的子类。所以:
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
并将每个调用放在自己的行上。在使用了尝试过第一种和第三种方法的代码后,我发现它周围的丑陋很快就会变老,而且通常比它的价值更痛苦。(我从未见过第二种方法有效,因为它是如此痛苦以至于没有人想尝试它。:))
推荐阅读
- sql - Knex:如何表达“SELECT Genre, count(*) FROM movies GROUP BY Genre;”?
- reactjs - 我对“getStaticPaths”函数有疑问。在 getStaticPaths 中未将必需参数 (id) 作为字符串提供
- go - 全局标志和子命令
- python - 栅格数据提取代码的索引越界错误
- reactjs - 无法返回调用 React Router - Redirect 的原始页面
- code-coverage - cypress 代码覆盖中的项目相对路径
- datetime - 使用 opencsv 从 csv 解析 OffsetDateTime:2021-05-09T12:23:55+0100
- c - Openssl 从 C 证书包中挑选证书(.pem 格式)
- java - Android NLTM 身份验证
- javascript - 为什么我在编辑 html 文件时收到 JS IntelliSense 建议