クラスが同じ基本的な動作、メソッドなどを実装しているが、そのクラスの複数の異なるバージョンが異なる用途に存在する可能性がある状況を考えてみましょう。私の特定のケースでは、ベクトル(リストではなく幾何ベクトル)があり、そのベクトルは任意のN次元ユークリッド空間(1次元、2次元など)に適用できます。このクラス/タイプはどのように定義できますか?
これは、クラステンプレートがパラメータとして実際の値を持つことができるC++では簡単ですが、Javaにはそのような贅沢はありません。
この問題を解決するために私が考えることができる2つのアプローチは次のとおりです。
コンパイル時に可能な各ケースの実装を持っています。
public interface Vector {
public double magnitude();
}
public class Vector1 implements Vector {
public final double x;
public Vector1(double x) {
this.x = x;
}
@Override
public double magnitude() {
return x;
}
public double getX() {
return x;
}
}
public class Vector2 implements Vector {
public final double x, y;
public Vector2(double x, double y) {
this.x = x;
this.y = y;
}
@Override
public double magnitude() {
return Math.sqrt(x * x + y * y);
}
public double getX() {
return x;
}
public double getY() {
return y;
}
}
このソリューションは、明らかに非常に時間がかかり、コーディングが非常に退屈です。この例ではそれほど悪くないように見えますが、実際のコードでは、最大4次元(x、y、z、およびw)でそれぞれが複数の実装を持つベクトルを処理しています。各ベクトルには本当に500行しか必要ありませんが、現在2,000行を超えるコードがあります。
実行時にパラメーターを指定する。
public class Vector {
private final double[] components;
public Vector(double[] components) {
this.components = components;
}
public int dimensions() {
return components.length;
}
public double magnitude() {
double sum = 0;
for (double component : components) {
sum += component * component;
}
return Math.sqrt(sum);
}
public double getComponent(int index) {
return components[index];
}
}
残念ながら、このソリューションはコードのパフォーマンスを低下させ、以前のソリューションよりも厄介なコードを生成し、コンパイル時に安全ではありません(処理するベクトルが実際に2次元であることはコンパイル時に保証できません。例えば)。
私は現在Xtendで開発を行っているので、Xtendソリューションが利用可能であれば、それらも受け入れられます。
このような場合は、コード生成を使用します。
実際にコードを生成するJavaアプリケーションを記述します。このようにして、forループを使用してさまざまなバージョンの束を簡単に生成できます。- JavaPoet を使用すると、実際のコードをビルドするのは非常に簡単で、コード生成の実行をビルドシステムに統合できます。
オプション2に基づいて、なぜこれを実行しないのですか?生ベースを使用したくない場合は、抽象化できます。
class Vector2 extends Vector
{
public Vector2(double x, double y) {
super(new double[]{x,y});
}
public double getX() {
return getComponent(0);
}
public double getY() {
return getComponent(1);
}
}
私のアプリケーションには非常によく似たモデルがあり、私たちの解決策は、あなたの解決策2と同様に、動的サイズのマップを単に保持することでした。
Java array primative that that like。のようなパフォーマンスについて心配する必要はまったくありません。100列(読み取り:100次元ベクトル)×10,000行の上限サイズの行列を生成します。 、そしてソリューションよりもはるかに複雑なベクトル型で優れたパフォーマンスを発揮しました。2。クラスをシールしたり、メソッドをfinalとしてマークして高速化したりしますが、時期尚早に最適化していると思います。
コードを共有する基本クラスを作成することで、(パフォーマンスを犠牲にして)コードを節約できます。
public interface Vector(){
abstract class Abstract {
protected abstract double[] asArray();
int dimensions(){ return asArray().length; }
double magnitude(){
double sum = 0;
for (double component : asArray()) {
sum += component * component;
}
return Math.sqrt(sum);
}
//any additional behavior here
}
}
public class Scalar extends Vector.Abstract {
private double x;
public double getX(){
return x;
}
@Override
public double[] asArray(){
return new double[]{x};
}
}
public class Cartesian extends Vector.Abstract {
public double x, y;
public double getX(){ return x; }
public double getY(){ return y; }
@Override public double[] asArray(){ return new double[]{x, y}; }
}
もちろん、Java-8以降を使用している場合は、デフォルトのインターフェースを使用して、これをさらに厳密にすることができます。
public interface Vector{
default public double magnitude(){
double sum = 0;
for (double component : asArray()) {
sum += component * component;
}
return Math.sqrt(sum);
}
default public int dimensions(){
return asArray().length;
}
default double getComponent(int index){
return asArray()[index];
}
double[] asArray();
// giving up a little bit of static-safety in exchange for
// runtime exceptions, we can implement the getX(), getY()
// etc methods here,
// and simply have them throw if the dimensionality is too low
// (you can of course do this on the abstract-class strategy as well)
//document or use checked-exceptions to indicate that these methods throw IndexOutOfBounds exceptions (or a wrapped version)
default public getX(){
return getComponent(0);
}
default public getY(){
return getComponent(1);
}
//...
}
//as a general rule, defaulted interfaces should assume statelessness,
// so you want to avoid putting mutating operations
// as defaulted methods on an interface, since they'll only make your life harder
}
最終的には、JVMの選択肢がなくなります。もちろん、それらをC++で記述し、JNAのようなものを使用してそれらをブリッジすることができます-これは、FortranとIntelのMKLを使用する一部の高速行列演算のソリューションです-しかし、これは物事を遅くするだけですC++でマトリックスを記述し、Javaからそのゲッター/セッターを呼び出すだけです。
それぞれの名前付きベクターが、配列(パラメーター名で次元名などで初期化されているか、サイズの整数または空のコンポーネント配列-設計)で構成されるコンストラクターを持つ列挙型と、 getMagnitudeメソッド。 enumにsetComponents/getComponent(s)のインターフェースを実装させ、どのコンポーネントがどのコンポーネントであるかを確立するだけで、getXなどを排除できます。使用する前に、実際のコンポーネント値で各オブジェクトを初期化する必要があります。入力配列のサイズが次元名またはサイズと一致していることを確認してください。
次に、ソリューションを別の次元に拡張する場合は、列挙型とラムダを変更するだけです。
1つのアイデア:
これにより、一般的なケースで優れたパフォーマンスが得られ、一般的なケースを犠牲にすることなく、コンパイル時の安全性が向上します(まだ改善できます)。
コードスケルトン:
public abstract class Vector {
protected abstract int dimension();
protected abstract double getComponent(int i);
protected abstract void setComponent(int i, double value);
public double magnitude() {
double sum = 0.0;
for (int i=0; i<dimension(); i++) {
sum += getComponent(i) * getComponent(i);
}
return Math.sqrt(sum);
}
public void add(Vector other) {
for (int i=0; i<dimension(); i++) {
setComponent(i, getComponent(i) + other.getComponent(i));
}
}
public static Vector1 create(double x) {
return new Vector1(x);
}
public static Vector create(double... values) {
switch(values.length) {
case 1:
return new Vector1(values[0]);
default:
return new DynVector(values);
}
}
}
class Vector1 extends Vector {
private double x;
public Vector1(double x) {
super();
this.x = x;
}
@Override
public double magnitude() {
return Math.abs(x);
}
@Override
protected int dimension() {
return 1;
}
@Override
protected double getComponent(int i) {
return x;
}
@Override
protected void setComponent(int i, double value) {
x = value;
}
@Override
public void add(Vector other) {
x += ((Vector1) other).x;
}
public void add(Vector1 other) {
x += other.x;
}
}
class DynVector extends Vector {
private double[] values;
public DynVector(double[] values) {
this.values = values;
}
@Override
protected int dimension() {
return values.length;
}
@Override
protected double getComponent(int i) {
return values[i];
}
@Override
protected void setComponent(int i, double value) {
values[i] = value;
}
}