Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access static field of generic type

Can I require classes implementing an interface to have a certain static field or method and access/invoke that field or method through a generic type argument?

I have an interface, Arithmetical<T>, which specifies several functions like T plus(T o) and T times(T o). I have as well a Vector<N extends Arithmetical<N>> class, which is intended for vectors (of variable dimension) with components of type N. I ran into an issue, however, when trying to implement the dot product.

I want to implement the method N dot(Vector<N> o). For this, I plan to start with whatever N's zero is and iterate through both Vector<N>s' List<N>s, adding the product of each pair of elements to my total. Is there a way to specify in Arithmetical<T> that all implementing classes must have a static (and preferably final) field ZERO and start dot(Vector<N> o)'s body with something along the lines of N sum = N.ZERO;?

If not, what other approaches might there be to this problem? I want to allow 0-dimensional vectors, so I can't just begin by multiplying the vectors' first components. Is there a way to instantiate an object of a generic type, so I can merely specify a T zero() method in Arithmetical<T>?

I have a reason for not using Java's numerical types—I want to have vectors with complex components.

Here's Arithmetical:

public interface Arithmetical<T> {
    public T plus(T o);
    public T minus(T o);
    public T negate();
    public T times(T o);
    public T over(T o);
    public T inverse();
    // Can I put a line here that requires class Complex (below) to define ZERO?
}

Vector:

public class Vector<N extends Arithmetical<N>> {

    private List<N> components;
    public Vector<N>(List<N> cs) {
        this.components = new ArrayList<N>(cs);
    }

    public N dot(Vector<N> o) {
        // Here's where I need help.
    }

}

And Complex:

public class Complex implements Arithmetical<Complex> {

    public static final Complex ZERO = new Complex(0, 0); // Can I access this value through N if <N extends Arithmetical<N>>?

    private double real;
    private double imag;
    public Complex(double r, double i) {
        this.real = r;
        this.imag = i;
    }

    /* Implementation of Arithmetical<Complex> (and some more stuff) not shown... */

}

I'm quite new to Java (and programming in general); I will likely not understand complex (ha) explanations and workarounds.

Thanks!

(Python is a suggested tag... Huh.)

like image 753
Khuldraeseth na'Barya Avatar asked Mar 14 '18 21:03

Khuldraeseth na'Barya


3 Answers

You need a "zero" for every possible implementation type. A constant in the interface won't do, because a constant cannot be overridden and must remain the same.

The solution is to add a new method to your Arithmetical interface:

public T zero();

Each implementation is forced to implement this and return its own version of zero. In this case, you're using it as a starting point for adding; it's the additive identity.

The Complex class implementation would look like this.

@Override
public Complex zero() {
    return ZERO;
}

If your instances are mutable, then don't use a constant; just return new Complex(0, 0).

Another idea is to borrow from what Streams do when reduce-ing items and combining them to one single item -- take an identity value that represents the initial state, i.e. no items collected yet -- zero.

public N dot(Vector<N> o, N identity) {
    N dotProduct = identity;
    // Perform operations on each item in your collection
    // to accumulate and return a dot product.
}

The caller will have to supply the identity value.

Complex dotProduct = vectorOfComplex.dotProduct(otherVector, new Complex(0, 0));
like image 158
rgettman Avatar answered Oct 06 '22 00:10

rgettman


Can I put a line here that requires class Complex (below) to define ZERO?

No. The best you can do is to define an interface, for example:

interface ZeroProvider<A extends Arithmetical<A>> {
  A zero();
}

and then supply a compatible instance of that where you need to provide a zero, for example:

class ComplexZeroProvider implements ZeroProvider<Complex> {
  public Complex zero() { return new Complex(0, 0); }
}
like image 25
Andy Turner Avatar answered Oct 05 '22 23:10

Andy Turner


There's something you can do sometimes using reflection in situations like this. If you put the following method in the Vector class, it will invoke a static method N.zero() (with caveats, below):

protected N zero() {
    try {
        Type s = getClass().getGenericSuperclass();
        @SuppressWarnings("unchecked")
        Class<N> n = (Class<N>) ((ParameterizedType) s).getActualTypeArguments()[0];
        Method zero = n.getMethod("zero");
        return n.cast(zero.invoke(null));
    } catch (RuntimeException | ReflectiveOperationException x) {
        // probably better to make a custom exception type
        throw new IllegalArgumentException("illegal type argument", x);
    }
}

However, it's important to understand what this is actually doing. This is getting the type argument from the class file of the direct superclass of this. In other words, there must actually be a superclass of this with an actual type argument (which is a class).

The usual idiom then is that you'd create all of your vectors like this:

new Vector<Complex>() {}

instead of this:

new Vector<Complex>()

Or you'd declare subclasses like this:

public class Vector<N> {
    // ...
    public static class OfComplex extends Vector<Complex> {
    }
}

Since you need an actual superclass with a type argument which is a class, instantiations like in the following examples will fail:

new Vector<Complex>()
new Vector()    // never use this anyway
new Vector() {} // never use this anyway

// also, you can't do stuff like this:
public Vector<T> copy() {
    return new Vector<T>(this) {};
}

In your case I think the suggestions in the other answers are better, but I wanted to post this answer along with the proper explanation and caveats which are sometimes not included. There are cases where this technique is actually good, mainly when you have pretty tight restrictions on how the class in question is extended. Guava TypeToken will also do some of the reflection for you.

Also, this is the best Java can do at doing exactly what you're asking for (at the moment), so it's worthwhile to point out just as a comparison.

like image 25
Radiodef Avatar answered Oct 06 '22 01:10

Radiodef