Search code examples
javagenericsfunction-reference

Making a reference to set type functions in Java


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.


Solution

  • 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();
    }