1


I have a class that I want to be instantiated with either a String or int value, and I define the corresponding instance variable value as generic type T:

public class MathValue<T extends Object> {
    private boolean isOperand, isOperator;

    // a generic-typed instance variable:
    private T value;

    // constructor:
    public MathValue(int operand) {

        // compile-error -- "Incompatible types: required T, found Integer"
        this.value = new Integer(operand);

        this.isOperand = true;
        this.isOperator = false;
    }

    // constructor:
    public MathValue(String operator) {

        // compile-error -- "Incompatible types: required T, found String"
        this.value = operand;

        this.isOperand = false;
        this.isOperator = true;
    }
}


I could very well have a single constructor instead, with a formal parameter of type T, but I want to enforce the class' instantiation with a String or int argument:

public class MathValue<T extends Object> {
    private boolean isOperand, isOperator;

    // a generic-typed instance variable:
    private T value;

    // it totally works, but does not enforce specific-typed instantiation:
    public MathValue(T operandOrOperator) {
        this.value = operandOrOperator;

        if (operandOrOperator instanceof Integer) {
            this.isOperand = true;
            this.isOperator = false;

        } else if (operandOrOperator instanceof String) {
            this.isOperand = false;
            this.isOperator = true;
        }
    }
}


So despite the logical error of wanting to make a generic-typed class "not so generic", is it possible to instantiate a generic variable with a specific-typed value?

Ian Campbell
  • 2,678
  • 10
  • 56
  • 104
  • `T extends TheSpecificTypesMustUnifyHere`. – user2864740 Nov 21 '13 at 05:05
  • 3
    I don't think it is possible, as `String` and `Integer` are in a different type hierarchy, and only common supertype is `Object`. What you can however do is, throw an unchecked exception from the constructor, if `T` is not an instance of either of them. – Rohit Jain Nov 21 '13 at 05:06

2 Answers2

1

You could create factory methods. Here's now it might look:

public class MathValue<T extends Object> {

    public static MathValue<String> from(String s) {
        MathValue<String> mv = new MathValue<String>();

        mv.setValue(s);
        mv.setIsOperand(true);
        return mv;
    }

     public static MathValue<Integer> from(Integer s) {
        MathValue<Integer> mv = new MathValue<Integer>();

        mv.setValue(i);
        mv.setIsOperand(false);
        return mv;
    }

    // Rest of your class below
}

If you absolutely need a constructor (eg, you don't know the type that you're creating for ahead of time), then I can't see a way around @RohitJain 's suggestion.

Ian Campbell
  • 2,678
  • 10
  • 56
  • 104
bstempi
  • 2,023
  • 1
  • 15
  • 27
  • Awesome, thanks @bstempi for the information! Might this be a common design pattern for a problem like this? – Ian Campbell Nov 21 '13 at 05:49
  • 2
    This is the [static factory pattern](http://stackoverflow.com/questions/929021/what-are-static-factory-methods-in-java). It's applicable here because you have some specific types of `T` that you're concerned with. Generally, this is a poor approach if you have more than a handful of type `T` to handle. – bstempi Nov 21 '13 at 05:51
1

You could use sub-classes. Define an abstract class to actually store the value, generically:

abstract class MathValue<T> {
        private final T value;

        MathValue(T value) {
                this.value = value;
        }

        abstract boolean isOperator();

        boolean isOperand() {
                return !isOperator();
        }
}

Then one subclass that enforces the value type to be an integer.

class OperandValue extends MathValue<Integer> {

        OperandValue(int operand) {
                super(new Integer(operand));
        }

        @Override
        public boolean isOperator() {
                return false;
        }
}

and another subtype that enforces it to be a String.

class OperatorValue extends MathValue<String> {

        OperatorValue(String operator) {
                super(operator);
        }

        @Override
        boolean isOperator() {
                return true;
        }
}

With this design, you don't actually need to store the Booleans.

(Note, for simplicity I left out the visibility keywords.)

Eamonn O'Brien-Strain
  • 3,352
  • 1
  • 23
  • 33