I'm making a generic abstract class that will work with float and integer values and will use different algorithms to generate a new value. It relies heavily on picking a random value within range, so I wanted to make a generic RandomRange kind of function.
This is the example of my code and what I'm trying to achieve.
public abstract class SelectionAlgorythm < T > {
protected static Random random = new Random();
BiFunction < T, T, T > picker;
RangePair pair;
public abstract T value();
protected SelectionAlgorythm(RangePair < T > pair) {
this.pair = pair
if (pair.minRange instanceof Integer) {
picker = SelectionAlgorythm::pickRandomInt;
} else if (pair.minRange instanceof Float) {
picker = SelectionAlgorythm::pickRandomFloat;
}
}
private static Integer pickRandomInt(Integer val1, Integer val2) {
return random.nextInt((int) val2 - (int) val1) + (int) val1;
}
private static Float pickRandomFloat(Float val1, Float val2) {
return random.nextFloat() * (val2 - val1) + val1;
}
public T pickValue() {
return picker(pair.minRange, pair.maxRange);
}
}
However I run into problems here and I can't figure out how to fix them. If I leave the code as it is, then the picker initialization lines complain that "Incompatible types, T is not convertible to Integer"
But I also can't just make the functions themselves generic, because random.nextInt() and random.nextFloat() expect particular values, not generics.
How do I do that? I admit, I have not worked with generics much and kind of moved away from Java recently, so I may be missing something obvious.
You can’t do it in constructor code. There is no way to change the meaning of T at runtime using code. Generic types are enforced by the compiler, not at runtime.
If this weren’t an abstract class, I would suggest that you make the constructor private, and create public static factory methods as a way to let allow code construct instances.
Since it is an abstract class, you can just make the picker function a parameter:
protected SelectionAlgorythm(RangePair<T> pair,
BinaryOperator<T> picker)
{
this.pair = pair;
this.picker = picker;
}
(Note that BinaryOperator is the same as a BiFunction where all types are the same, but is easier to read.)
If you want to restrict which picker functions are allowed, create your own inner pseudo-enum class:
protected static final class Picker<V> {
private final BinaryOperator<V> function;
private Picker(BinaryOperator<V> function) {
this.function = function;
}
BinaryOperation<V> function() {
return function;
}
public static final Picker<Integer> INT =
new Picker<>(SelectionAlgorythm::pickRandomInt);
public static final Picker<Float> FLOAT =
new Picker<>(SelectionAlgorythm::pickRandomFloat);
}
This will be typesafe since no class other than SelectionAlgorythm is capable of creating Picker instances:
protected SelectionAlgorythm(RangePair<T> pair,
Picker<T> picker)
{
this.pair = pair;
this.picker = picker.function();
}