Search code examples
javagenericsabstract-class

Proper use of generics in abstract java class?


EDIT: This question is not well worded, and the provided answer is correct in a literal sense but did not teach me how to attain what I needed. If you are struggling with the same problem, this is what finally helped me: How to enforce child class behavior/methods when the return types of these methods depends on the child class?

I am trying to implement a basic matrix class from a boilerplate abstract class I wrote. There will be several implementations of this abstract class, each one using a different math library, which I will then test for speed.

Each implementation will hold its data in that library's native matrix data structure. I think this is a use case for generics. At this point I think I've read too many tutorials and watched too many videos, as I just can't seem to figure out all the right places to put the T Notation to make this work correctly.

So my question is twofold:

  1. Have I misused or missed the point of generics?
  2. If not, what is the correct syntax for their use?

I've read the docs plus about three different tutorials and still can't understand.

Here is what I've tried:

public abstract class BaseMatrix<T> {

    protected int[] shape;
    protected int nrows;
    protected int ncols;
    protected T data; // <--- Here is the generic data --->

    public BaseMatrix(int rows, int cols){
        this.nrows = rows;
        this.ncols = cols;
        this.shape = new int[]{nrows, ncols};
    }    

    public abstract BaseMatrix mmul(BaseMatrix other);

And here is my implementation:

public class ND4JDenseMatrix extends BaseMatrix{
    

    // private INDArray data;

    public ND4JDenseMatrix(int rows, int cols) {
        super(rows, cols);
        this.data = Nd4j.zeros(this.shape); <--- Here is the non-generic data --->
    }


    @Override
    public ND4JDenseMatrix mmul(ND4JDenseMatrix other) {
      ND4JDenseMatrix result = new ND4JDenseMatrix(nrows, ncols);
      result.data = data.mmul(other.data);
      return result;
    }

The error is: Method does not override method from its superclass.


Solution

  • hold its data in that library's native matrix data structure. I think this is a use case for generics.

    Generics serves to link things. You declared the type variable with <T>, and you've used it in, as far as your paste goes, exactly one place (a field, of type T). That's a red flag; generally, given that it links things, if you use it in only one place that's usually a bad sign.

    Here's what I mean: Imagine you want to write a method that says: This method takes 2 parameters and returns something. This code doesn't particularly care what you toss in here, but, the parameters must be the same type and I return something of that type too. You want to link the type of the parameter, the type of the other parameter, and the return type together.

    That is what generics is for.

    It may apply here, if we twist our minds a bit: You want to link the type of the data field to a notion that some specific implementation of BaseMatrix can only operate on some specific type, e.g. ND4JMatrix.

    However, mostly, no, this doesn't strike me as proper use of generics. You can avoid it altogether quite easily: Just.. stop having that private T data; field. What good is it doing you here? You have no idea what type that is, you don't even know if it is serializable. You know nothing about it, and the compiler confirms this: There is absolutely not one iota you can do with that object, except things you can do to all objects which are generally quite uninteresting. You can call .toString() on it, synchronize on it, maybe invoke .hashCode(), that's about it.

    Why not just ditch that field? The implementation can make the field, no need for it to be in base!

    public class ND4JDense extends BaseMatrix {
        private ND4JMatrix data; // why not like this?
    }
    

    (This code assumes 'ND4JMatrix' is the proper data type you desire here, a thing that can is the internal representation for the data in the ND4J impl).

    However, if you must, yeah, you can use generics here. You've type-varred BaseMatrix, and that means all usages of BaseMatrix must be parameterized. That's the part you messed up in your code. If we go with your plan of a type-parameterized BaseMatrix class and a field of type T, the right code is:

    public class ND4JDense extends BaseMatrix<ND4JMatrix> {
       ...
    }
    

    I wouldn't, however, do it this way (I'd go with having the impl have the field, much simpler, no need to bother anybody with the generics). Unless, of course, you DO have an actual need for that field and it IS part of BaseMatrix's API. For example, if you want this:

    public class BaseMatrix<T> {
        public T getData() { return data; }
    }
    

    then it starts to make more sense. With that, you can write the following and it'll all compile and work great:

    public class ND4JDense extends BaseMatrix<ND4JMatrix> {
        ...
        // no need to write a getData method here at all!
        ...
    }
    
    ND4JDense dense = new ND4JDense();
    ND4JMatrix matrix = dense.getData();
    

    But, clearly, this makes no sense if you intend for the ND4JMatrix to remain an implementation detail that users of the BaseMatrix API should probably not be touching.


    EDIT: You changed the question on me, later. Now you want the mmul method to take 'self' as argument, effectively: You want the same type to be passed in.

    You can sort of do that but it is a little tricky. You need the self-ref generics hack. It looks like this:

    public class BaseMatrix<T extends BaseMatrix<T>> {
       public abstract T mmul(T other);
    }
    

    In practice the only valid value for T is your own class, or at least, that is the intent. This works fine:

    public class ND4JDenseMatrix extends BaseMatrix<ND4JDenseMatrix> {
        public ND4JDenseMatrix mmul(ND4JDenseMatrix other) {
          .. impl here ..
        }
    }