Culpeo
Culpeo

Reputation: 11

Addition of two generics in Java (cast not working)

I am currently learning about generics in Java, coming from C++ it's quite a difference. I'd like to a vector addition, in C++, it would look something like this. (I know that this code is not well-written, it's just for a quick example to show what I want to do)

#include <iostream>
#include <vector>
template <typename T>
class Vect{
    public:
        std::vector<T> vect;
        Vect(std::vector<T> t){vect = t;}
        T index(int i){return vect[i];}
        void print(){
            for(int i = 0; i < vect.size(); ++i){
                std::cout << vect[i] << " ";
            }
            std::cout << std::endl;
        }
        void add(Vect<T> other){
            for(int i = 0; i < vect.size(); ++i){
                vect[i] = vect[i]+other.index(i);
            }
        }
};

int main(){
    std::vector<int> p1;
    p1.push_back(1);
    p1.push_back(2);
    p1.push_back(3);
    p1.push_back(4);
    Vect<int> vec1 = Vect<int>(p1);;
    Vect<int> vec2 = Vect<int>(p1);;
    vec1.print();
    vec2.print();
    vec1.add(vec2);
    vec1.print();

    return 0;
}

I am trying to do the same with Java but I can't get a way to add two generics T and put the value (a T) in the first vector. I am doing this :

public class Vect0<T extends Number> {

    //Attributs
    private T[] _vec;

    //Constructeur
    public Vect0(T[] vec){
        System.out.println("Construction du _vec !");
        _vec = vec;
    }
    //Getter
    public int get_length() {
        return _vec.length;
    }
    //Methodes
    public void print(){
        System.out.print("[");
        for (int i = 0; i < _vec.length; ++i){
            if (i != _vec.length-1) {
                System.out.print(_vec[i] + ", ");
            }
            else {
                System.out.print(_vec[i]);
            }
        }
        System.out.println("]");
    }
    public T index(int i) {
        return _vec[i];
    }
    public void sum(Vect0<T> other) {
        if (other.get_length() == this.get_length()) {
            for(int i = 0; i < this.get_length(); ++i) {
                Double res = (this.index(i).doubleValue() + other.index(i).doubleValue());
                System.out.print(res);
                T t = (T) res;
                _vec[i] =  t;
            }
        }
    }
}

So it does print a double, but then the casting doesn't work and I get an error :

Exception in thread "main" java.lang.ArrayStoreException: java.lang.Double at Vect0.sum(Vect0.java:37) at Main.main(Main.java:12)

I hope you can help me figure this out. Thank you very much.

Upvotes: 1

Views: 125

Answers (1)

wleao
wleao

Reputation: 2346

Take a look at the code below. The explanation of your problem is in the comments.

public class TestGenerics {

    public static void main(String[] args) {
        Integer[] ints = new Integer[] { 1 };
        Double[] doubles = new Double[] { 1.0 };

        // By not telling the compiler which types you're
        // expecting it will not be able to deduce if
        // the usage of the generic methods are going
        // to be ok or not, because the generics notation is
        // erased and is not available at runtime.
        // The only thing you will receive by doing this
        // is a warning telling you that:
        // "Vect0 is a raw type. 
        // References to generic type Vect0<T> should be parameterized"
        Vect0 rawVect0 = new Vect0(ints);
        Vect0 rawVect1 = new Vect0(doubles);

        // This will throw java.lang.ArrayStoreException 
        // because you're trying, at runtime, to cast
        // a Double object to Integer inside your sum method
        // The compiler doesn't have a clue about that so it 
        // will blow at runtime when you try to use it.
        // If you're only working with Integers, than you should not
        // cast the result to double and should always work with intValues
        rawVect0.sum(rawVect1);

        // In Java, when using generics, you should be 
        // explict about your types using the diamond operator
        Vect0<Integer> vect2 = new Vect0<>(ints);
        Vect0<Double> vect3 = new Vect0<>(doubles);

        // Now that you told the compiler what your types are
        // you will receive a compile time error:
        // "The method sum(Vect0<Integer>) in the type Vect0<Integer> 
        // is not applicable for the arguments (Vect0<Double>)"
        vect2.sum(vect3);
    }
}

If you read about the ArrayStoreException it will become clearer:

public class ArrayStoreException extends RuntimeException Thrown to indicate that an attempt has been made to store the wrong type of object into an array of objects. For example, the following code generates an ArrayStoreException:

 Object x[] = new String[3];
 x[0] = new Integer(0);

Whenever dealing with Generic methods you should think that the Producer should use extends and the Consumer should use super - PECS. This is a good thing to know. Check out this question: What is PECS (Producer Extends Consumer Super)?

One solution to your case, if you really want to be able to add different types of Number, is to always work inside the Vect0 object with one specific type (Double). Check out the code below:

public class Vect0<T extends Number> {
    // Attributs
    private Double[] _vec;

    // Constructeur
    public Vect0(T[] vec) {
        System.out.println("Construction du _vec !");
        if (vec instanceof Double[]) {
            _vec = (Double[]) Arrays.copyOf(vec, vec.length);
        } else {
            _vec = Arrays.stream(vec).map(Number::doubleValue).toArray(Double[]::new);
        }
    }

    // Getter
    public int get_length() {
        return _vec.length;
    }

    // Methodes
    public void print() {
        System.out.print("[");
        for (int i = 0; i < _vec.length; ++i) {
            if (i != _vec.length - 1) {
                System.out.print(_vec[i] + ", ");
            } else {
                System.out.print(_vec[i]);
            }
        }
        System.out.println("]");
    }

    public Double index(int i) {
        return _vec[i];
    }

    public void sum(Vect0<T> other) {
        if (other.get_length() == this.get_length()) {
            for (int i = 0; i < this.get_length(); ++i) {
                // Now you're only working with Doubles despite of the other object's true type
                _vec[i] = index(i) + other.index(i);
            }
        }
    }
}

And in order to use it, you should reference the objects using the super class Number, like this:

Vect0<Number> vect2 = new Vect0<>(ints1);
Vect0<Number> vect3 = new Vect0<>(doubles1);
// now it will be able to add two different types without complaining
vect2.sum(vect3);

If you want something more elaborate, you could start by checking out this question: How to add two java.lang.Numbers?

Cheers!

References:

Upvotes: 1

Related Questions