具有不同类型变量的Java继承



使用下面的代码,有没有办法让这两个极其相似的类从单个超类继承?它们在变量类型上几乎完全不同。如果没有,是否有一种设计模式似乎最适合这种情况?我一直在考虑使用工厂模式,但我不确定这是否最适合这种情况。

class stringCharacteristic {
String name = "";
String value = "";
public String getValue() {
return value;
}
public stringCharacteristic setValue(String value) {
this.value = value;
return this;
}
public String getName() {
return name;
}
public stringCharacteristic setName(String name) {
this.name = name;
return this;
}
}
class intCharacteristic {
String name = "";
int value = 0;
public int getValue() {
return value;
}
public intCharacteristic setValue(int value) {
this.value = value;
return this;
}
public String getName() {
return name;
}
public intCharacteristic setName(String name) {
this.name = name;
return this;
}
}

编辑:使用泛型找到了一个对我来说相当好的解决方案,可能不是最有效的,但它完成了任务。此外,我知道代码在流体界面和其他方面看起来有点不对劲,这并不是我实际上写的代码,只是一个简化的东西来显示问题,而不是复制粘贴一百行只会分散对主要问题的注意力的东西。

class characteristic<E> {
String name;
E value;
public E getValue() {
return value;
}
public characteristic<E> setValue(E value) {
this.value = value;
return this;
}
public String getName() {
return name;
}
public characteristic<E> setName(String name) {
this.name = name;
return this;
}
public characteristic<E> setMin(int min) {
this.min = min;
return this;
}
public characteristic<E> setMax(int max) {
this.max = max;
return this;
}
}

否(t真的)。

这里有两个基本问题。

基本体不玩动态打字游戏

例如,如果你有一个StringCharacteristic(请注意,在java中我们是WriteTypesLikeThis,而不是likeThis)和LocalDateCharacteristic,它都是对象引用,我们可以对此进行推广,生成一个超类型ObjectCharacteristic<T>。但是,泛型不可能是基元的,也没有办法对基元进行泛化。

现在,也就是说。Project 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。直到valhalla。

如果你不愿意接受这种低效率,你将不得不手动写出与int相关的位。

自我类型的问题

如果您都接受使用Integer而不是int的负面影响,那么这个次要问题就不会出现——您可以只使用一个public class Characteristic<T>,没有自定义子类。

类型层次结构和"fluent API"(返回自己类型的API方法)的问题在于它们在此处不起作用

间奏——让我们来写吧

因为名称位完全相同,除了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,在新的IntCharacteristic实例的情况下,this将是instanceof IntCharacteristic,但setName方法的返回类型没有声明它,因此javac不会这样对待它。因此,这将失败

new IntCharacteristic().setName("Hello").setValue(5);

你可以让它成功,使用自己的泛型破解。

这是个坏主意,您需要继续阅读以获得更好的解决方案。我把它包括在这里是因为虽然在这种情况下有更好的解决方案可用,但有时没有,而这种破解是最好的解决方案:

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,这正是您想要的。如果没有强制转换,编译器将发出一个警告,说明该操作实际上根本没有进行任何检查,但我们知道它将是"true",因为这个奇怪的签名(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类中。问题是,很明显您的意图只是让方法调用"连锁"到这件事,但从根本上讲,如果涉及层次结构,这是不可行的,除非您使用上述不能"隐藏"的破解(该泛型参数现在是您的公共API的一部分)。

你也没有真正使用它来返回计算结果,这就是返回值的目的

换句话说,当你这样做的时候,你就是在入侵语言。这样做是有原因的,但最好睁大眼睛:无论何时你对语言进行攻击,你都必须面对这样一个事实,即它最终会咬你的屁股。

REAL解决方案要么是让java语言发展"self-type"的概念,要么是让java语言发展"allow chaining"的概念。这个问题的更好的解决方案是这两个真正的解决方案之一:

语言方案A

chainable public void setName(String name) {
this.name = name;
}

chainable关键字都要求(如果不是编译器错误)返回类型为void的情况下,AND允许调用方写入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完全是愚蠢的。但很接近。你正在破解语言,通常,无论是因为自我类型的问题还是未来语言的改进,这意味着你总有一天会后悔的。

这到底有多有用取决于客户端想对Characteristic实例做什么,也就是说,当它无法判断它们是什么具体类时。

如果在某些情况下(例如)您想要显示一组Characteristic的名称,也许还有它们值的toString,那么这将非常有用。

你可以这样做:

interface Characteristic<T> {
public T getValue();
public String getName();
Characteristic<T> setValue(T value);
Characteristic<T> setName(String name);
}
class StringCharacteristic implements Characteristic<String>{
String name = "";
String value = "";
public String getValue() {
return value;
}
public StringCharacteristic setValue(String value) {
this.value = value;
return this;
}
public String getName() {
return name;
}
public StringCharacteristic setName(String name) {
this.name = name;
return this;
}
}
class IntCharacteristic implements Characteristic<Integer> {
String name = "";
int value = 0;
public Integer getValue() {
return value;
}
public IntCharacteristic setValue(Integer value) {
this.value = value;
return this;
}
public String getName() {
return name;
}
public IntCharacteristic setName(String name) {
this.name = name;
return this;
}
}

(顺便说一下,请使用正确的Java命名约定)

您可以使用一个抽象类来考虑常见的实现:

abstract class AbstractCharacteristic<T> implements Characteristic<T> {
String name = "";
T value;
public AbstractCharacteristic(T value) {
this.value = value;
}

public T getValue() {
return value;
}
public Characteristic<T> setValue(T value) {
this.value = value;
return this;
}
public String getName() {
return name;
}
public Characteristic<T> setName(String name) {
this.name = name;
return this;
}
}
class StringCharacteristic extends AbstractCharacteristic<String>{
public StringCharacteristic() {
super("");
}
}
class IntCharacteristic extends AbstractCharacteristic<Integer> {
public IntCharacteristic() {
super(0);
}
}

(我们需要构造函数参数,因为默认情况下,字符串字段通常初始化为null,而不是"")

最新更新