Home > Software engineering >  How to write generic Java List algorithms?
How to write generic Java List algorithms?

Time:11-07

I want to write a function that will add a constant to each list element. The list can be Doubles or Integers or something similar. I write:

static <T> List<T> forEachIndexChange(List<T> list,
        Function<Integer, T> cb) {
    for (int i = 0; i < list.size();   i) {
        list.set(i, cb.apply(i));
    }
    return list;
}

static <T extends Number> List divide(List<T> list, int val) {
    return forEachIndexChange(list, i -> list.get(i) / val);
}

And then the compiler spills out that I can't call / on class Number:

error: bad operand types for binary operator '/'
        return forEachIndexChange(list, i -> list.get(i) / val);
                                                         ^
  first type:  T
  second type: int
  where T is a type-variable:
    T extends Number declared in method <T>divide(List<T>,int,T)

Great, then let me overload depending on type:

static <T extends Number> List divide(List<T> list, int val, T type) {
    if (type instanceof Double) {
        return forEachIndexChange(list, i -> list.get(i).doubleValue() / val);
    }
    return null;
}

static <T extends Number> List divide(List<T> list, int val) {
    if (list.size() == 0) return list;
    return divide(list, val, list.get(0));
}

But this spills out an error message that I do not understand:

error: incompatible types: inference variable T#1 has incompatible bounds
            return forEachIndexChange(list, i -> list.get(i).doubleValue() / val);
                                     ^
    equality constraints: T#2
    lower bounds: Double
  where T#1,T#2 are type-variables:
    T#1 extends Object declared in method <T#1>forEachIndexChange(List<T#1>,Function<Integer,T#1>)
    T#2 extends Number declared in method <T#2>divide(List<T#2>,int,T#2)

Overloading on multiple T separately also doesn't work, because, as I understand, List erases the information about T when overloading a function. It looks like I am basically forced to write a separate function name for each type.

How do I write a generic algorithm to permute each list element in Java? Is there a better way to approach such problems in Java?

CodePudding user response:

First of all, why would you write custom function for altering the collection, when there already is map() function on stream?

Second thing I am not sure, is why would you want to work with List of unknown number types. It allows you to have different types in the list and you still want to divide by integer, which could be problem when dealing with bytes for example..

Nevertheless, I would do something like this:

    private static <T extends Number> List<T> divide(List<T> orig, int divisor) {
        return orig.stream()
                .map(it -> divide(it, divisor))
                .toList();
    }

    private static <T extends Number> T divide(T num, int divisor) {
        if (num instanceof Double) return (T) Double.valueOf(num.doubleValue() / divisor);
        if (num instanceof Float) return (T) Float.valueOf(num.floatValue() / divisor);
        if (num instanceof Long) return (T) Long.valueOf(num.longValue() / divisor);
        if (num instanceof Integer) return (T) Integer.valueOf(num.intValue() / divisor);
        if (num instanceof Byte) return (T) Byte.valueOf((byte) (num.byteValue() / divisor)); // this might be problem if divisor is greater than 127
        throw new IllegalStateException("Cannot divide class of "   num.getClass());
    }

Note that the original collection is not affected. If changing the original collection is desired (altough i dont think it is good practice), you could do it like this:

    private static <T extends Number> void divide(List<T> orig, int divisor) {
        for (var i = 0; i < orig.size(); i  ) {
            orig.set(i, divide(orig.get(i), divisor));
        }
    }

CodePudding user response:

If you are happy with checking types manually, and assuming that you only pass lists that contain a single type of number (lists with different types of numbers are pretty rare anyway), you can do:

static <T extends Number> List<T> divide(List<T> list, int val) {
    if (list.isEmpty()) return list;

    if (list.get(0) instanceof Double) {
        return forEachIndexChange(list, i -> (T)(Object)(list.get(i).doubleValue() / val));
    }

    if (list.get(0) instanceof Integer) {
        return forEachIndexChange(list, i -> (T)(Object)(list.get(i).intValue() / val));
    }

    // other types you want to support...

    throw new UnsupportedOperationException();
}

Note that this produces unchecked cast warnings. This just means that Java cannot check that the cast to T is safe at runtime. But this is fine, because we have checked ourselves using the if statement just the line before. For example, in the first if statement we have checked that the list indeed contains Double, (Object)(list.get(i).doubleValue() / val) produces a Double, so the cast is definitely safe.

CodePudding user response:

Your forEachIndexChange() is generic enough and should be used as is.

public static void main(String[] args) {
    List<Integer> integers = Arrays.asList(9, 6, 2, 3);
    System.out.println(forEachIndexChange(integers, i -> integers.get(i) / 2));
    List<Double> doubles = Arrays.asList(9.0, 6.0, 2.0, 3.0);
    System.out.println(forEachIndexChange(doubles, i -> doubles.get(i) / 2));
}

output:

[4, 3, 1, 1]
[4.5, 3.0, 1.0, 1.5]
  • Related