Generazione di classi Java con parametri di valore in fase di compilazione

10

Considera una situazione in cui una classe implementa lo stesso comportamento di base, i metodi, eccetera, ma possono esistere più versioni differenti di quella classe per usi diversi. Nel mio caso particolare, ho un vettore (un vettore geometrico, non una lista) e quel vettore potrebbe applicarsi a qualsiasi spazio euclideo N-dimensionale (1 dimensionale, 2 dimensionale, ...). Come si può definire questa classe / tipo?

Questo sarebbe facile in C ++ dove i modelli di classe possono avere valori reali come parametri, ma non abbiamo quel lusso in Java.

I due approcci a cui posso pensare che possano essere presi per risolvere questo problema sono:

  1. Avere un'implementazione di ogni caso possibile al momento della compilazione.

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

    Questa soluzione è ovviamente molto dispendiosa in termini di tempo ed estremamente noiosa per codificare. In questo esempio non sembra troppo male, ma nel mio codice attuale mi occupo di vettori che hanno implementazioni multiple ciascuna, con un massimo di quattro dimensioni (x, y, z e w). Al momento ho oltre 2000 linee di codice, anche se ogni vettore ne ha davvero bisogno solo 500.

  2. Specifica dei parametri in fase di runtime.

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

    Sfortunatamente questa soluzione danneggia le prestazioni del codice, genera un codice più messistico rispetto alla precedente soluzione e non è sicura al momento della compilazione (non è possibile garantire in fase di compilazione che il vettore di cui si ha effettivamente a che fare sia 2 -dimensionale, per esempio).

Attualmente sto attualmente sviluppando in Xtend, quindi se sono disponibili soluzioni Xtend, sarebbero anche accettabili.

    
posta Parker Hoyes 15.02.2016 - 23:44
fonte

5 risposte

1

In casi come questo, utilizzo la generazione del codice.

Scrivo un'applicazione java che genera il codice vero e proprio. In questo modo puoi facilmente utilizzare un ciclo for per generare un sacco di versioni diverse. Io uso JavaPoet , che rende abbastanza semplice creare il codice reale. Quindi puoi integrare l'esecuzione della generazione del codice nel tuo sistema di compilazione.

    
risposta data 20.11.2017 - 17:31
fonte
0

Ho un modello molto simile sulla mia applicazione e la nostra soluzione consisteva semplicemente nel mantenere una mappa di dimensioni dinamiche, simile alla tua soluzione 2.

Semplicemente non avrai bisogno di preoccuparti delle prestazioni con un primitivo java array come quello. Generiamo matrici con dimensioni limite superiori di 100 colonne (leggi: 100 vettori dimensionali) per 10.000 righe e abbiamo ottenuto buone prestazioni con tipi di vettore molto più complessi rispetto alla soluzione 2. Potresti provare a sigillare i metodi di classe o di marcatura come finali per velocizzarlo, ma penso che tu stia ottimizzando prematuramente.

Puoi ottenere un certo risparmio di codice (a scapito delle prestazioni) creando una classe base per condividere il tuo codice:

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

Quindi, ovviamente, se utilizzi Java-8 +, puoi utilizzare le interfacce predefinite per renderlo ancora più stretto:

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
}

In definitiva, oltre a ciò, hai esaurito le opzioni con la JVM. Ovviamente puoi scriverli in C ++ e usare qualcosa come JNA per metterli a confronto: questa è la nostra soluzione per alcune delle operazioni a matrice veloce, dove usiamo fortran e l'MKL di Intel, ma questo rallenterà le cose solo se scrivi semplicemente la tua matrice in C ++ e chiami i suoi getter / setter da java.

    
risposta data 16.02.2016 - 01:56
fonte
0

Considera un enum con ciascun Vettore con un costruttore che consiste in un array (inizializzato nell'elenco dei parametri con i nomi delle dimensioni o simili, o forse solo un numero intero per le dimensioni o un array di componenti vuoto - il tuo progetto), e una lambda per il metodo getMagnitude. Potresti avere l'enum anche implementare un'interfaccia per setComponents / getComponent (s), e solo stabilire quale componente era il suo utilizzo, eliminando getX, et al. Dovresti inizializzare ogni oggetto con i suoi effettivi valori del componente prima dell'uso, controllando eventualmente che la dimensione dell'array di input corrisponda ai nomi o alle dimensioni della dimensione.

Quindi se estendi la soluzione a un'altra dimensione, devi solo modificare enum e lambda.

    
risposta data 16.02.2016 - 17:19
fonte
0

In base alla tua opzione 2, perché non farlo semplicemente? Se vuoi impedire l'uso della base grezza, puoi renderla astratta:

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);
  }
}
    
risposta data 15.07.2016 - 22:13
fonte
0

Un'idea:

  1. Una classe di base astratta Vector che fornisce una dimensione variabile implementazioni basate su un metodo getComponent (i).
  2. Sottoclassi individuali Vector1, Vector2, Vector3, che copre il tipico casi, ignorando i metodi vettoriali.
  3. Una sottoclasse DynVector per il caso generale.
  4. Metodi di fabbrica con elenchi di argomenti a lunghezza fissa per il tipico casi, dichiarato di restituire Vector1, Vector2 o Vector3.
  5. Un metodo factory var-args, dichiarato per restituire Vector, istanziazione Vector1, Vector2, Vector3 o DynVector, a seconda dell'arretrato di lunghezza.

Questo ti dà buone prestazioni in casi tipici e qualche sicurezza in fase di compilazione (può ancora essere migliorata) senza sacrificare il caso generale.

Scheletro del codice:

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

}
    
risposta data 23.06.2017 - 14:36
fonte

Leggi altre domande sui tag