动态 return 调用类型 class 在 Java 中使用泛型
Dynamically return with the type of calling class using generics in Java
考虑以下 classes.
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()
方法 return 是超级 class 类型和必须再次转换为 ClassB 才能使用 addTwice()
方法。我试图在 addOnce()
方法中使用泛型来解决这个问题。
addOnce 的新实现:
public<R extends ClassA> R addOnce(String s1) {
v1.add(s1);
return (R)this;
}
但是编译器仍然要求强制转换,因为它无法仅基于下一个方法调用找到 return 类型,因为该方法可能可用于多个 classes (addOnce()
可用于 ClassA
和 ClassB
) 并且 return 类型不会收敛。有没有办法 return this
使用调用类型 class,通常是当子 class 调用父 class 方法时?
要使类型推断起作用,您需要在 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
有两种方法可以做到这一点,但我都不推荐。
方法一:泛型
具体来说,递归的(指回泛型 class 本身):
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()
=60=] 覆盖:
// 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
return一个Class<AbstractClassA>
,一个一个Class<AbstractClassA<?>>
,可以.
此外,AbstractClassA 的子classes 不 继承那个<R>
类型,所以如果你想定义保持模式的新方法(例如,在 ClassB 中),您必须在每个模式中使用一个新的通用参数来重复该模式。我的预测是你会后悔的。
方法二:覆盖每个方法
另一种方法是手动覆盖基础 class 中的所有方法。当你覆盖一个方法时,你 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 感觉不成熟。
方法三:首先从更具体的子class开始链
这并不是您问题的真正答案,而是解决同一问题的模式。这个想法是让 ClassA 和 ClassB 保持原样,但总是先调用 ClassB 的方法:
new ClassB().addTwice("2").addOnce("1");
这种方法很好用,但有点笨拙。大多数时候,人们认为首先在基础 class 上设置东西,然后在子class 上设置子class 特定的东西。如果您愿意颠倒它,并且您真的想要这些链接的方法,那么这可能是我会选择的方法。但我真正的建议是:
我的建议:算了
Java 只是不能很好地处理这些类型的方法链。我建议你忘记整件事,不要试图在这一点上与语言作斗争。使方法 return void
并将每个调用放在自己的行上。在使用尝试过第一种和第三种方法的代码后,我发现围绕它的丑陋之处很快就会过时,而且通常带来的痛苦超过了它的价值。 (我从未见过第二种方法有效,因为它太痛苦了,没有人愿意尝试。:))
这个问题让我想起了方法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;
}
}
为什么 class A
甚至应该知道 class B
存在。这是 B
的责任。在此解决方案中,class B
将添加一次的功能委托给其超级 class 方法,但类型转换由 B
在它具有返回值后执行。在当前用例中确实不需要使用泛型。
考虑以下 classes.
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()
方法 return 是超级 class 类型和必须再次转换为 ClassB 才能使用 addTwice()
方法。我试图在 addOnce()
方法中使用泛型来解决这个问题。
addOnce 的新实现:
public<R extends ClassA> R addOnce(String s1) {
v1.add(s1);
return (R)this;
}
但是编译器仍然要求强制转换,因为它无法仅基于下一个方法调用找到 return 类型,因为该方法可能可用于多个 classes (addOnce()
可用于 ClassA
和 ClassB
) 并且 return 类型不会收敛。有没有办法 return this
使用调用类型 class,通常是当子 class 调用父 class 方法时?
要使类型推断起作用,您需要在 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
有两种方法可以做到这一点,但我都不推荐。
方法一:泛型
具体来说,递归的(指回泛型 class 本身):
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()
=60=] 覆盖:
// 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
return一个Class<AbstractClassA>
,一个一个Class<AbstractClassA<?>>
,可以
此外,AbstractClassA 的子classes 不 继承那个<R>
类型,所以如果你想定义保持模式的新方法(例如,在 ClassB 中),您必须在每个模式中使用一个新的通用参数来重复该模式。我的预测是你会后悔的。
方法二:覆盖每个方法
另一种方法是手动覆盖基础 class 中的所有方法。当你覆盖一个方法时,你 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 感觉不成熟。
方法三:首先从更具体的子class开始链
这并不是您问题的真正答案,而是解决同一问题的模式。这个想法是让 ClassA 和 ClassB 保持原样,但总是先调用 ClassB 的方法:
new ClassB().addTwice("2").addOnce("1");
这种方法很好用,但有点笨拙。大多数时候,人们认为首先在基础 class 上设置东西,然后在子class 上设置子class 特定的东西。如果您愿意颠倒它,并且您真的想要这些链接的方法,那么这可能是我会选择的方法。但我真正的建议是:
我的建议:算了
Java 只是不能很好地处理这些类型的方法链。我建议你忘记整件事,不要试图在这一点上与语言作斗争。使方法 return void
并将每个调用放在自己的行上。在使用尝试过第一种和第三种方法的代码后,我发现围绕它的丑陋之处很快就会过时,而且通常带来的痛苦超过了它的价值。 (我从未见过第二种方法有效,因为它太痛苦了,没有人愿意尝试。:))
这个问题让我想起了方法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;
}
}
为什么 class A
甚至应该知道 class B
存在。这是 B
的责任。在此解决方案中,class B
将添加一次的功能委托给其超级 class 方法,但类型转换由 B
在它具有返回值后执行。在当前用例中确实不需要使用泛型。