关于Java文化,可以说很多事情,但是我认为在您现在正面临的情况下,有一些重要方面:
- 库代码只编写一次,但使用频率更高。虽然可以最大程度地减少编写库的开销,但是从长远来看,以使使用库的开销降至最低的方式进行编写可能更值得。
- 这意味着自我记录类型非常有用:方法名称有助于清楚地说明正在发生的事情以及您从对象中得到的结果。
- 静态类型化是消除某些错误类别的非常有用的工具。它当然不能解决所有问题(人们喜欢开玩笑说Haskell,一旦您使类型系统接受您的代码,那可能是正确的),但是它使得很容易使某些错误的事情变得不可能。
- 编写库代码是关于指定合同的。为参数和结果类型定义接口可以使合同的界限更加明确。如果某个东西接受或产生一个元组,则不必说这是您实际上应该接收还是产生的元组,并且对这样的泛型类型的约束很少(它甚至具有正确数量的元素吗?他们是您期望的类型?)。
具有字段的“结构”类
正如其他答案所提到的,您可以只使用带有公共字段的类。如果将它们定型,则将获得一个不可变的类,并使用构造函数对其进行初始化:
class ParseResult0 {
public final long millis;
public final boolean isSeconds;
public final boolean isLessThanOneMilli;
public ParseResult0(long millis, boolean isSeconds, boolean isLessThanOneMilli) {
this.millis = millis;
this.isSeconds = isSeconds;
this.isLessThanOneMilli = isLessThanOneMilli;
}
}
当然,这意味着您与特定的类相关联,任何需要产生或使用解析结果的内容都必须使用该类。对于某些应用程序,这很好。对于其他人,这可能会引起一些痛苦。许多Java代码都是关于定义合同的,通常会将您带入接口。
另一个陷阱是,使用基于类的方法时,您将暴露字段,并且所有这些字段都必须具有值。例如,即使isLessThanOneMilli为true,isSeconds和millis始终必须具有某些值。当isThanOneMilli为true时,对Millis字段的值的解释应该是什么?
“结构”作为接口
使用接口中允许使用的静态方法,实际上相对容易地创建不可变的类型,而没有大量的语法开销。例如,我可能会像这样实现您正在谈论的结果结构:
interface ParseResult {
long getMillis();
boolean isSeconds();
boolean isLessThanOneMilli();
static ParseResult from(long millis, boolean isSeconds, boolean isLessThanOneMill) {
return new ParseResult() {
@Override
public boolean isSeconds() {
return isSeconds;
}
@Override
public boolean isLessThanOneMilli() {
return isLessThanOneMill;
}
@Override
public long getMillis() {
return millis;
}
};
}
}
我绝对同意,这仍然是很多样板,但是也有很多好处,而且我认为那些开始回答您的一些主要问题。
使用这种解析结果的结构,可以非常清楚地定义解析器的协定。在Python中,一个元组与另一个元组并没有真正的区别。在Java中,静态类型可用,因此我们已经排除了某些类型的错误。例如,如果您要使用Python返回一个元组,并且想要返回该元组(millis,isSeconds,isLessThanOneMilli),则可能会意外地执行以下操作:
return (true, 500, false)
当你的意思是:
return (500, true, false)
使用这种Java接口,您将无法编译:
return ParseResult.from(true, 500, false);
完全没有 你必须做:
return ParseResult.from(500, true, false);
通常,这是静态类型语言的好处。
这种方法也开始使您能够限制可以获取的值。例如,在调用getMillis()时,您可以检查isLessThanOneMilli()是否为true,如果是,则抛出IllegalStateException(例如),因为在这种情况下没有有意义的millis值。
很难做错事
在上面的接口示例中,您仍然会遇到问题,因为它们具有相同的类型,因此可能会意外交换isSeconds和isLessThanOneMilli参数。
在实践中,您实际上可能真的想利用TimeUnit和持续时间,以便得到类似以下的结果:
interface Duration {
TimeUnit getTimeUnit();
long getDuration();
static Duration from(TimeUnit unit, long duration) {
return new Duration() {
@Override
public TimeUnit getTimeUnit() {
return unit;
}
@Override
public long getDuration() {
return duration;
}
};
}
}
interface ParseResult2 {
boolean isLessThanOneMilli();
Duration getDuration();
static ParseResult2 from(TimeUnit unit, long duration) {
Duration d = Duration.from(unit, duration);
return new ParseResult2() {
@Override
public boolean isLessThanOneMilli() {
return false;
}
@Override
public Duration getDuration() {
return d;
}
};
}
static ParseResult2 lessThanOneMilli() {
return new ParseResult2() {
@Override
public boolean isLessThanOneMilli() {
return true;
}
@Override
public Duration getDuration() {
throw new IllegalStateException();
}
};
}
}
这越来越成为一个很多更多的代码,但是你只需要编写一次,和(假设你已经正确记录的东西),谁落得人使用你的代码没有什么结果意味着猜了,不能偶然地做那些result[0]
刻薄的事情result[1]
。您仍然可以非常简洁地创建实例,并且从其中获取数据也不是那么困难:
ParseResult2 x = ParseResult2.from(TimeUnit.MILLISECONDS, 32);
ParseResult2 y = ParseResult2.lessThanOneMilli();
请注意,您实际上也可以使用基于类的方法执行类似的操作。只需为不同情况指定构造函数。但是,仍然存在将其他字段初始化为什么的问题,并且无法阻止对它们的访问。
另一个答案提到Java的企业类型性质意味着在很多时候,您是在组合其他已经存在的库,或者编写供其他人使用的库。你的公共API不应该需要大量的时间咨询文件破译结果类型,如果能够避免它。
您只需编写一次这些结构,但是您会多次创建它们,因此您仍然希望进行简洁的创建(您会得到)。静态类型可确保您从中获取的数据符合您的期望。
现在,尽管如此,仍然有一些地方可以让简单的元组或列表有意义。返回数组的内容可能会减少开销,如果是这种情况(并且开销很大,您可以通过分析确定),则在内部使用简单的值数组可能很有意义。您的公共API可能仍应具有明确定义的类型。