不(真的)。
这里有两个基本问题。
基元不玩动态输入游戏。
如果你有例如一个StringCharacteristic(注意在java中我们是WriteTypesLikeThis,而不是likeThis)和一个LocalDateCharacteristic,它们都是对象引用,我们可以对其进行概括,创建一个超类型ObjectCharacteristic<T>。但是,泛型不能是原始的,没有办法对原始进行泛化。
现在,就是这样。 Valhalla 项目正在进行大量的积极开发(我很确定它在很大程度上是目前在 OpenJDK 积极开发的所有各种与语言相关的项目中最努力的),所以请密切关注你的天气JDK 更新的功能列表。
您可以通过使用包装器类型 (java.lang.Integer) 来解决它,但这是一个非常糟糕的替代:它们可以是令人讨厌的 null(int 不能),它们的顺序是CPU 和内存方面的效率都大大降低。它们也不一定兼容 - 自动拆箱只能到此为止。
您可以将这些方法放入超类中(如果您不想将其作为公共 API 的一部分,请将其设为私有),使用泛型:class Characteristic<T>,使用 T 而不是int/String 在那个超类的任何地方,然后那个超类就可以了。如果你真的坚持有一个显式命名的类型,你可以写public class IntCharacteristic extends Characteristic<Integer> {},也许添加一个构造函数,然后一天调用它,剩下的就处理好了(甚至是你在超类中声明为的字段private T value.
但是,这为您提供了一个使用Integer 而不是int 的特征类。没有办法让它使用int。直到瓦尔哈拉。
如果您不愿意接受这种低效率,您将不得不手动写出与int 相关的位。
self类型的问题
如果你们都接受使用Integer 而不是int 的负面影响,则不会出现此次要问题,并且您可以只使用一个public class Characteristic<T>,使用没有自定义子类。
类型层次结构和“流式 API”(返回自己的类型的 API 方法)的问题在于它们在这里不起作用。
插曲 - 让我们来写吧。
因为name位完全一样,除了setName方法的返回类型,我们可以这样做:
private abstract class Characteristic {
private String name;
public Characteristic setName(String name) {
this.name = name;
return this;
}
public String getName() {
return name;
}
}
然后您可以编写它的子类型,并且您可以跳过名称内容的所有代码,因为您的超类会负责:
public class IntCharacteristic extends Characteristic {
private int value;
public IntCharacteristic setValue(int value) {
...
}
}
但是,问题!
这里的一个问题是 setName 方法的返回类型错误。是的,它返回this,而this 将在新的IntCharacteristic 实例的情况下为instanceof IntCharacteristic,但是您的setName 方法的返回类型没有声明它,因此javac 不会如此对待它。因此,这将失败:
new IntCharacteristic().setName("Hello").setValue(5);
您可以使用自泛型 hack 使其成功。
这是个坏主意,您需要继续阅读以获得更好的解决方案。我将其包含在此处是因为在这种情况下,虽然有更好的解决方案可用,但有时却没有,而这个 hack 是最好的解决方案:
public class Characteristic<S extends Characteristic<S>> {
private String name;
@SuppressWarnings("unchecked")
protected S self() {
return (S) this;
}
public S setName(String name) {
this.name = name;
return self();
}
}
public class IntCharacteristic extends Characteristic<IntCharacteristic> {
}
此代码确保setName 的返回类型是 IntCharacteristic,这是您想要的。 不可能在没有强制转换的情况下完成这项工作,这会导致编译器发出警告说该操作实际上根本不做任何检查,但我们知道这将是“真实的”,因为这个奇怪的签名(S extends Self<S>)实际上意味着任何扩展它的东西都只能把自己放在<>中。
更好的解决方案
大多数情况下,整个“回归自我”的东西都被夸大了。它不是特别的 java 风格,这显示了它是如何导致问题的。更一般地说,如果你坚持制作“现代”API,那么就像你建造了一个厨房,里面有一个中世纪的烤箱和一个全新的微波炉。现代的观点涉及不变性。为什么你甚至允许在这里更改名称?它不应该在创造时就被固定下来吗?那么你就避免了这整个混乱!
public class Characteristic {
private final String name;
public Characteristic(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class IntCharacteristic extends Characteristic {
private final int value;
public IntCharacteristic(String name, int value) {
super(name);
this.value = value;
}
public int getValue() {
return value;
}
}
问题解决了。
顺便说一句,不要再返回自己了
基本上,return this; 不是一个好主意,除非可能在 final 类中。问题在于,显然您的意图只是让“链式”方法调用变得容易,但如果涉及层次结构,则基本上 这不起作用,除非您使用上述不能“隐藏”的 hack(泛型参数现在是您的公共 API 的一部分)。
您也没有真正使用它来返回计算结果,而这正是返回值的目的。
换句话说,当你这样做时,你正在破解语言。这样做是有原因的,但最好睁大眼睛这样做:每当你破解语言时,你都必须面对这样一个事实,即它最终会咬你一口。
REAL 解决方案是让 java-the-language 发展“自我类型”的概念,或者更好的是,让 java-the-language 发展“allow chaining”的概念.这个问题的更好的解决方案是以下两个真正的解决方案之一:
语言建议 A
chainable public void setName(String name) {
this.name = name;
}
chainable 关键字都要求返回类型为void(如果不是编译器错误),并且它允许调用者编写new Foo().setName("stuff").chainMoreMethodsHere();。它纯粹是javac 语法糖,除了作为标记之外,这些都不会在类文件中存在,所以javac 知道该怎么做。类文件中的方法签名保持其 VOID 返回类型,语言工具知道这一点,例如在这里忽略返回类型完全没问题。
语言建议 B
把它交给调用者而不是 API 编写者:
只要有一个void方法:public void setName() { ... },但是调用者可以选择链式:
Foo x = new Foo()..setName()..setSomethingElse();
这里的双点表示:调用方法,丢弃返回的任何内容,并将表达式解析为与接收者相同。 (x.foo()表示:调用foo,折腾返回类型,表达式解析为x)。
目前,OpenJDK 列表中没有关于这方面的任何流量。但也许今天不一样了。如果添加了这个,你的 API 现在就坏了,可能会永远过时。
我并不是说流畅的 API 完全是白痴。但接近它。你正在破解语言,通常,无论是因为自我输入的东西还是未来的语言改进,这意味着你总有一天会后悔的。