Search code examples
javaintegerwrapperprimitive

Java -- Primitive Counterpart of Byte, Integer, Long, etc. in template


BACKGROUND:

I am trying to implement a tiny template, i.e. generic class, which would allow me to achieve a pass-by-reference functionality as follows.

public static class Ref<T> {
    T value;
    public Ref(T InitValue)  { this.set(InitValue); }
    public void set(T Value) { this.value = Value; }
    public T    get()        { return this.value; }
}

So, I could define a function that takes a 'Ref' where the value can actually be changed, e.g.

public static void function(Ref<Byte> x)
{
    x.set((byte)0x7E);
}

The initialization of the variable to be passed by reference looks not so elegant.

Ref<Byte>  to_be_changed = new Ref<Byte>((byte)0);
...
function(to_be_changed); 
...
Byte       result = to_be_changed.get()

QUESTION:

Is there a way in Java to do it better? Can the constructor initialize directly a '0' according to the primitive type related to the wrapper type which is passed as template type? I.e something like

 ...
 public Ref() { this.value = (T.relatedPrimitiveClass())0; }
 ...

where Integer.relatedPrimitiveClass() shall deliver int; Byte.relatedPrimitiveClass() delivers byte.


Solution

  • First and the most important thing to understand is that java generics are not templates. Generics are classes/interfaces that are parameterized over types. I recommend reading generics tutorial from oracle: https://docs.oracle.com/javase/tutorial/java/generics/types.html

    It is possible to use reflection to get the parametrized type T of your Ref class and use that to determinate the initial value for default construtor, but I would not recommend doing so.

    Instead of reflection you can create subclasses for types, that require default constructors (e.g. object versions of primitives):

    public static class ByteRef extends Ref<Byte> {
        public ByteRef() {
            super((byte)0);
        }
    
        public ByteRef(byte value) {
            super(value);
        }
    
        // I'm not sure, if I like this one :-)
        public void set(int value) {
            super.set((byte)value);
        }
    }
    

    Instead of subclassing, you can also add new methods to Ref class:

    public static class Ref<T> {
        T value;
    
        public Ref(T initValue) {
            this.set(initValue);
        }
    
        public void set(T Value) {
            this.value = Value;
        }
    
        public T get() {
            return this.value;
        }
    
        public static Ref<Byte> createByteRef() {
            return new Ref<Byte>((byte)0);
        }
    
        public static Ref<Byte> createByteRef(byte value) {
            return new Ref<Byte>(value);
        }
    }
    

    Or you could create separate factory classes: public class Refs { public static Ref createByteRef() { return new Ref((byte)0); }

        public static Ref<Byte> createByteRef(byte value) {
            return new Ref<Byte>(value);
        }
    }
    

    The last option is to use reflection to get the parameterized type. I personally would not use this solution, because number of primitive classes is finite and you have option to create much neater interfaces with subclassing

    public abstract static class PrimitiveNumberRef<T extends Number> extends
            Ref<T> {
        private Class<T> type;
    
        public PrimitiveNumberRef() {
            // This requires default constructor for Ref class
            type = getGenericType(getClass());
            super.set((T) getInitialValue(type));
        }
    
        @Override
        public void set(T value) {
            if (value == null) {
                throw new IllegalArgumentException(
                        "Null value is not allowed for PrimitiveNumerRef type: "
                                + type);
            }
            if (!type.isInstance(value)) {
                throw new IllegalArgumentException("Unsupported value type: "
                        + value.getClass());
            }
            super.set(value);
        }
    
        @SuppressWarnings("unchecked")
        private static <T> Class<T> getGenericType(Class<?> clz) {
            return (Class<T>) ((ParameterizedType) clz.getGenericSuperclass())
                    .getActualTypeArguments()[0];
        }
    
        private static <T> T getInitialValue(Class<T> clz) {
            if (clz == Byte.class) {
                return clz.cast((byte) 0);
            } else if (clz == Short.class) {
                return clz.cast((short) 0);
            } else if (clz == Integer.class) {
                return clz.cast((int) 0);
            } else if (clz == Double.class) {
                return clz.cast((double) 0);
            } else if (clz == Float.class) {
                return clz.cast((float) 0);
            } else if (clz == Long.class) {
                return clz.cast((long) 0);
            } else {
                throw new IllegalArgumentException("Unsupported type: "
                        + clz.getName());
            }
        }
    }
    

    PrimitiveNumberRef is instantiated as follows:

    Ref<Long> val1 = new PrimitiveNumberRef<Long>() { };