Javaの文化について言えることはたくさんありますが、今直面している場合には、いくつかの重要な側面があると思います。
- ライブラリコードは一度書かれますが、より頻繁に使用されます。ライブラリを記述するオーバーヘッドを最小限に抑えるのは良いことですが、おそらく長期的にはライブラリを使用するオーバーヘッドを最小限に抑える方法で記述する方が価値があります。
- それは、自己文書化型が優れていることを意味します。メソッド名は、何が起こっているのか、オブジェクトから何を取得しているのかを明確にするのに役立ちます。
- 静的型付けは、特定のクラスのエラーを排除するための非常に便利なツールです。それは確かにすべてを修正するわけではありません(Haskellについて、型システムがコードを受け入れるようになると、おそらく正しいと冗談を言うのが好きです)が、特定の種類の間違ったことを不可能にするのは非常に簡単です。
- ライブラリコードの記述は、コントラクトの指定に関するものです。引数および結果の型のインターフェイスを定義すると、契約の境界がより明確に定義されます。何かがタプルを受け入れたり生成したりする場合、それが実際に受け取ったり生成したりするタプルの種類であるかどうかは言うまでもなく、そのようなジェネリック型に対する制約はほとんどありません(適切な数の要素さえありますか?彼らはあなたが期待していたタイプの?)。
フィールドを持つ「構造」クラス
他の回答で言及したように、パブリックフィールドを持つクラスを使用できます。これらをfinalにすると、不変クラスを取得し、コンストラクターでそれらを初期化します。
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には常に何らかの値が必要です。isLessThanOneMilliが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では、1つのタプルは別のタプルと実際には区別されません。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であるかどうかを確認できます。trueの場合は、IllegalStateException(たとえば)をスローします。
間違ったことをするのを難しくする
上記のインターフェイスの例では、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には、おそらく明確に定義された型があるはずです。