コンパイル時の値パラメーターを使用したJavaクラスの生成


10

クラスが同じ基本的な動作、メソッドなどを実装しているが、そのクラスの複数の異なるバージョンが異なる用途に存在する可能性がある状況を考えてみましょう。私の特定のケースでは、ベクトル(リストではなく幾何学的ベクトル)があり、そのベクトルは任意のN次元ユークリッド空間(1次元、2次元など)に適用できます。このクラス/タイプはどのように定義できますか?

これは、クラステンプレートがパラメーターとして実際の値を持つことができるC ++では簡単ですが、Javaにはそのような贅沢はありません。

この問題を解決するために私が考えることができる2つのアプローチは次のとおりです。

  1. コンパイル時に可能な各ケースの実装を持つ。

    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行を超えるコードがあります。

  2. 実行時にパラメーターを指定する。

    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ソリューションが利用可能であれば、それらも受け入れられます。


Xtendを使用しているので、Xtext DSLのコンテキスト内でこれを実行していますか?
Dan1701 2016

2
DSLはコード生成アプリケーションに最適です。簡単に言うと、小さな言語文法、その言語のインスタンス(この場合はさまざまなベクトルを記述)、およびインスタンスの保存時に実行されるコード(Javaコードの生成)を作成します。Xtextサイトには、たくさんのリソースと例があります
Dan1701 2016

2
依存型を使用してこの問題を完全に解決する方法があります(多かれ少なかれそれらが作成されたものです)が、残念ながらJavaでは利用できません。クラスの数が固定されている場合(たとえば、1次元、2次元、および3次元のベクトルのみを使用する場合)は最初のソリューションを使用し、それ以上の場合は後者のソリューションを使用します。明らかに、コードを実行せずに確かなことを言うことはできませんが、心配されているパフォーマンスへの影響はないと思います
Gardenhead

1
これらの2つのクラスには同じインターフェースがありません。それらは多態性ではありませんが、それらを多態的に使用しようとしています。
Martin Spamer

1
線形代数の数学を書いていて、パフォーマンスを心配しているなら、なぜJavaなのか。その中の問題以外は何も見えません。
ソペル2017年

回答:


1

このような場合は、コード生成を使用します。

実際のコードを生成するJavaアプリケーションを作成します。これにより、forループを使用してさまざまなバージョンを簡単に生成できます。私はJavaPoetを使用しているため、実際のコードを作成するのは非常に簡単です。次に、コード生成の実行をビルドシステムに統合できます。


0

私のアプリケーションには非常によく似たモデルがあり、私たちの解決策は、あなたの解決策2と同様に、動的サイズのマップを単に保持することでした。

そのようなJava配列プリミティブでパフォーマンスについて心配する必要はないでしょう。上限サイズが100列(読み取り:100次元のベクトル)×10,000行の行列を生成し、ソリューションよりもはるかに複雑なベクトルタイプで良好なパフォーマンスを実現しました。2。クラスをシールするか、メソッドを最終としてマークしてみてください。それをスピードアップするために、私はあなたが時期尚早に最適化していると思います。

コードを共有する基本クラスを作成することで、(パフォーマンスを犠牲にして)コードを節約できます。

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からそのゲッター/セッターを呼び出すだけです。


私の主な関心事はパフォーマンスではなく、コンパイル時のチェックです。ベクターのサイズとベクターに対して実行できる操作がコンパイル時に決定されるソリューション(C ++テンプレートの場合など)が本当に必要です。あなたはサイズ1000個のコンポーネントまでの可能性が行列を扱っている場合はおそらく、あなたのソリューションが最適ですが、この場合には、私は唯一の1の大きさとベクトルを扱っています- 10
パーカーHoyes

最初または2番目のソリューションのようなものを使用する場合、それらのサブクラスを作成できます。今、私はXtendについても読んでいますが、Kotlinにかなり似ているようです。Kotlinでは、おそらくdata classオブジェクトを使用して、10個のベクトルサブクラスを簡単に作成できます。Javaでは、すべての機能を基本クラスに取り込むことができると仮定すると、各サブクラスは1〜10行かかります。基本クラスを作成しませんか?
Groostav、2016

私が提供した例は非常に単純化されており、私の実際のコードには、ベクトルの内積、コンポーネントごとの加算と乗算など、Vectorに対して定義された多くのメソッドがあります。基本クラスとあなたのasArrayメソッドを使用してこれらを実装することもできますが、これらのさまざまなメソッドはコンパイル時にチェックされません(スカラーとデカルトベクトルの間でドット積を実行でき、コンパイルは正常に行われますが、実行時に失敗します)。 。
Parker Hoyes、2016

0

それぞれの名前付きベクターが、配列(パラメーター名で次元名などで初期化されているか、おそらくサイズの整数または空のコンポーネント配列-デザイン)で構成されるコンストラクターを持つ列挙型と、 getMagnitudeメソッド。列挙型にsetComponents / getComponent(s)のインターフェイスを実装させ、どのコンポーネントがどのコンポーネントであるかを確立するだけで、getXなどを排除できます。使用する前に、各オブジェクトを実際のコンポーネント値で初期化する必要があります。おそらく、入力配列のサイズが次元名またはサイズと一致することを確認します。

次に、ソリューションを別の次元に拡張する場合は、列挙型とラムダを変更するだけです。


1
ソリューションの短いコードスニペットイラストを提供してください。
TulainsCórdova16年

0

オプション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」に似ています。ただし、ソリューションはコンパイル時に型の安全性を保証する方法を提供しますが、を作成するオーバーヘッドは、double[]2つのプリミティブを使用するだけの実装に比べて望ましくありませdoubleん。最小限の例では、これはマイクロ最適化のように見えますが、より多くのメタデータが関係し、問題の型の存続期間が短い、はるかに複雑なケースを検討してください。
Parker Hoyes

1
確かに、これは方法2に基づいています。彼の回答に関してGroostavと話し合ったところ、パフォーマンスには関心がなかったという印象を受けました。このオーバーヘッド、つまり1つではなく2つのオブジェクトを作成することを定量化しましたか?短寿命に関しては、最新のJVMはこのケースに最適化されており、長寿命のオブジェクトよりもGCコストが低い(基本的には0)はずです。メタデータがこれにどのように影響するかはわかりません。このメタデータはスカラーまたは次元ですか?
JimmyJames

私が取り組んでいる実際のプロジェクトは、超次元レンダラーで使用されるジオメトリフレームワークでした。これは、楕円体、オルソトープなどのベクトルよりもはるかに複雑なオブジェクトを作成していたことを意味します。通常、変換には行列が含まれます。より高次元のジオメトリでの作業は複雑であるため、オブジェクトの作成をできるだけ避けたいという強い要望があった一方で、行列とベクトルのサイズの型安全性が望まれていました。
Parker Hoyes

私が本当に探していたのは、メソッド1と同様のバイトコードを生成する、より自動化されたソリューションでした。これは、標準のJavaまたはXtendでは実際には不可能です。最終的にメソッド2を使用して、これらのオブジェクトのサイズパラメータを実行時に動的にする必要があり、これらのパラメータが静的な場合のために、より効率的で特殊な実装を手間をかけて作成しました。寿命が比較的長い場合、この実装は「動的」スーパータイプVectorをより特殊な実装(たとえばVector3)に置き換えます。
Parker Hoyes

0

1つのアイデア:

  1. getComponent(i)メソッドに基づいて可変次元の実装を提供する抽象基本クラスVector。
  2. 個々のサブクラスVector1、Vector2、Vector3。一般的なケースをカバーし、Vectorメソッドをオーバーライドします。
  3. 一般的な場合のDynVectorサブクラス。
  4. Vector1、Vector2、Vector3を返すように宣言された、一般的なケースの固定長引数リストを持つファクトリメソッド。
  5. 引数リストの長さに応じて、Vectorを返すように宣言され、Vector1、Vector2、Vector3、またはDynVectorをインスタンス化するvar-argsファクトリメソッド。

これにより、一般的なケースで優れたパフォーマンスが得られ、一般的なケースを犠牲にすることなく、コンパイル時の安全性が向上します(まだ改善できます)。

コードスケルトン:

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;
    }

}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.