Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Java, can I specify any amount of generic type parameters?

Tags:

java

generics

I am looking to create a particular type of interface in Java (although this is just as applicable to regular classes). This interface would need to contain some method, say, invoke; it would be called with a varying amount of parameters depending on the generic type arguments supplied.

As an example:

public interface Foo<T...> {
    public void invoke(T... args);
}

// In some other class
public static Foo<Float, String, Integer> bar = new Foo<Float, String, Integer>() {
    @Override
    public void invoke(Float arg1, String arg2, Integer arg3) {
        // Do whatever
    }
};

To explain, briefly, how this could be used (and provide some context), consider a class Delegator: the class takes a varying number of generic types, and has a single method - invoke, with these parameter types. The method passes on its parameters to an object in a list: an instance of IDelegate, which takes the same generic types. This allows Delegator to choose between several delegate methods (defined inside IDelegate) without having to create a new class for each specific list of parameter types.

Is anything like this available? I have read about variadic templates in C++, but cannot find anything similar in Java. Is any such thing available? If not, what would be the cleanest way to emulate the same data model?

like image 310
Sam McCreery Avatar asked Nov 06 '15 14:11

Sam McCreery


3 Answers

Is anything like this available? I have read about variadic templates in C++, but cannot find anything similar in Java. Is any such thing available?

No, this feature is not available in Java.

like image 150
Puce Avatar answered Jan 03 '23 13:01

Puce


No, there is nothing like that directly available. However if you use a library with Tuple classes you can simulate it by just making the interface

interface Foo<T> {
    void invoke(T t);
}

(This interface is essentially the same as Consumer<T>.)

Then you could do for example

Foo<Tuple<String, Integer, Date, Long>> foo = new Foo<>() {
    ...
}

You would need a separate Tuple type for each number of parameters. If you have a Tuple class for 4 parameters, but not one for 5, you could squeeze an extra parameter in by using a Pair class.

Foo<Tuple<String, Integer, Date, Pair<Long, BigDecimal>>> foo = ...

By nesting tuple types in this way you get an unlimited number of parameters. However, these workarounds are really ugly, and I would not use them.

like image 45
Paul Boddington Avatar answered Jan 03 '23 13:01

Paul Boddington


Given the context you provided I would recommend using a List as a parameter. If these parameters have something in common, you can restrain your list to <T extends CommonParrent> instead of using List<Object>. If not, you may still want to use marker interface.

Here is an example.

public class Main {

    public static void main(String[] args) {
        delegate(asList(new ChildOne(1), new ChildTwo(5), new ChildOne(15)));
    }

    private static <T extends Parent> void delegate(List<T> list) {
        list.forEach(item -> {
            switch (item.type) {
                case ONE: delegateOne((ChildOne) item); break;
                case TWO: delegateTwo((ChildTwo) item); break;
                default: throw new UnsupportedOperationException("Type not supported: " + item.type);
            }
        });
    }

    private static void delegateOne(ChildOne childOne) {
        System.out.println("child one: x=" + childOne.x);
    }

    private static void delegateTwo(ChildTwo childTwo) {
        System.out.println("child two: abc=" + childTwo.abc);
    }

}

public class Parent {
    public final Type type;

    public Parent(Type type) {
        this.type = type;
    }
}

public enum Type {
    ONE, TWO
}

public class ChildOne extends Parent {
    public final int x;

    public ChildOne(int x) {
        super(Type.ONE);
        this.x = x;
    }
}

public class ChildTwo extends Parent {
    public final int abc;

    public ChildTwo(int abc) {
        super(Type.TWO);
        this.abc = abc;
    }
}

The biggest flaw of this solution is that children have to specify their type via enum which should correspond to the casts in the switch statement, so whenever you change one of these two places, you will have to remember to change the other, because compiler will not tell you this. You will only find such mistake by running the code and executing specific branch so test driven development recommended.

like image 43
Jaroslaw Pawlak Avatar answered Jan 03 '23 12:01

Jaroslaw Pawlak