10

Consider a situation where a class implements the same basic behavior, methods, et cetera, but multiple different versions of that class could exist for different uses. In my particular case, I have a vector (a geometric vector, not a list) and that vector could apply to any N-dimensional Euclidean space (1 dimensional, 2 dimensional, ...). How can this class / type be defined?

This would be easy in C++ where class templates can have actual values as parameters, but we don't have that luxury in Java.

The two approaches I can think of that could be taken to solve this problem are:

  1. Having an implementation of each possible case at compile time.

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

    This solution is obviously very time consuming and extremely tedious to code. In this example it doesn't seem too bad, but in my actual code I'm dealing with vectors that have multiple implementations each, with up to four dimensions (x, y, z, and w). I currently have over 2,000 lines of code, even though each vector only really needs 500.

  2. Specifying parameters at 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];
        }
    }
    

    Unfortunately this solution hurts code performance, results in messier code than the former solution, and is not as safe at compile-time (it can't be guaranteed at compile-time that the vector you're dealing with actually is 2-dimensional, for example).

I am currently actually developing in Xtend, so if any Xtend solutions are available, they would also be acceptable.

Dan1701
  • 3,108

5 Answers5

1

In cases like this, I use code generation.

I write a java application that generates the actual code. That way you can easily use a for loop to generate a bunch of different versions. I use JavaPoet, which makes it pretty straightforward to build up the actual code. Then you can integrate running the code generation into your build system.

Winston Ewert
  • 25,052
0

I have a very similar model on my application and our solution was to simply keep a map of a dynamic size, similar to your solution 2.

You simply aren't going to need to worry about performance with a java array primative like that. We generate matrixes with upper-bound sizes of 100 columns (read: 100 dimensional vectors) by 10,000 rows, and we have had good performance with much more complex vector types than your solution 2. You might try sealing the class or marking methods as final to speed it up, but I think you're optimizing prematurely.

You can get some code-savings (at the cost of performance) by creating a base class to share your code:

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

Then of course, if you're on Java-8+, you can use defaulted interfaces to make this even tighter:

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
}

Ultimately beyond that you're out of options with the JVM. You can of course write them in C++ and use something like JNA to bridge them in --this is our solution for some of the fast matrix operations, where we use fortran and intel's MKL-- but this is only going to slow things down if you simply write your matrix in C++ and call its getters/setters from java.

Groostav
  • 257
0

Consider an enum with each named Vector having a constructor that consists of an array (initialized in the parameter list with the dimension names or similar, or perhaps just an integer for the size or an empty components array - your design), and a lambda for the getMagnitude method. You could have the enum also implement an interface for setComponents/getComponent(s), and just establish which component was which in its usage, eliminating getX, et al. You would need to initialize each object with its actual component values before use, possibly checking that the input array size matches the dimension names or size.

Then if you extend the solution to another dimension, you just modify the enum and lambda.

0

Based on your option 2, why not simply do this? If you want to prevent use of the raw base you can make it abstract:

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);
  }
}
JimmyJames supports Canada
  • 30,578
  • 3
  • 59
  • 108
0

One idea:

  1. An abstract base class Vector providing variable-dimension implementations based on a getComponent(i) method.
  2. Indiviual subclasses Vector1, Vector2, Vector3, covering the typical cases, overriding the Vector methods.
  3. A DynVector subclass for the general case.
  4. Factory methods with fixed-length argument lists for the typical cases, declared to return Vector1, Vector2 or Vector3.
  5. A var-args factory method, declared to return Vector, instantiating Vector1, Vector2, Vector3, or DynVector, depending on the arglist length.

This gives you good performance in typical cases and some compile-time safety (can still be improved) without sacrificing the general case.

Code skeleton:

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

}