Amir Afghani
Amir Afghani

Reputation: 38541

Creating a Parameters class using Generics

I have an interface that defines a method that does some computation

public interface Computable { 
    public Result compute(Foo foo);
}

I want to also pass in a set of arguments to the computation that can be peeled off. I can hack this up, but I am wondering if there's an elegant solution with generics and var args. Something like...

 public class Parameter<K,V> {

    private final K key;
    private final V value;

    public Parameter(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() {
        return this.key;
    }

    public V getValue() {
        return value;
    }    
}

But then I'm lost as to how going through each parameter from the list of parameters I would be able to simply get the key value pairs with their types inferred. Can someone help me? Has this not already been built into the JDK?

EDIT with example:

In a concrete implementation we'd have....

public Result compute(Foo foo,
                      Parameter ...parameters) {

    // Here I'd like to get each parameter, and have it know it's key type and value type
    for(Parameter p : parameters) {
        p.getKey();
        p.getValue()
        //implementers know what to do with their parameters
    }


}

Upvotes: 2

Views: 149

Answers (2)

Markus A.
Markus A.

Reputation: 12752

If you have different key types for the different parameters or different value types, you probably won't be able to solve this with a simple generic varargs function definition. Your only type-safe option would then be something like this:

public Result compute(Foo foo, Parameter<Byte, Byte> p1, Parameter<Byte, Float> p2, ...)

(here "..." denotes further parameters added in the same way, not the varargs ellipses)

.

If you need to be flexible, though, and you can determine which key and value type you are dealing with by probing some other property of the Parameter, there's a trick I sometimes use to simplify the type-casting: Change your getKey() function in the Parameter class to this:

@SuppressWarnings("unchecked")
public <KeyType> KeyType getKey() {
    return (KeyType) this.key;
}

(and similarly for the getValue() function)

That way, you can simply assign the results of getKey() to a variable of any (non-primitive) type, like this:

Integer key = parameter.getKey();

or

String key = parameter.getKey();

and you won't have to do any type casting in your compute function, and there won't be any warnings.

But: You will loose the compile-time type-check! So, you have to make sure at runtime (which you might have to anyway) that you don't assign a key to a variable with a wrong type. Or at least handle the resulting ClassCastException gracefully.

Upvotes: 1

Paul Bellora
Paul Bellora

Reputation: 55223

As AmitD mentioned in the comments, you should make Computable a parameterized type:

public interface Computable<K, V> { 
    public Result compute(Foo foo, Parameter<K, V>... parameters);
}

An implementation would then resolve K and V and the types would be known:

public class BarBazComputable implements Computable<Bar, Baz> {
    @Override
    public Result compute(Foo foo, Parameter<Bar, Baz>... parameters) {
        ...
    }
}

Edit: And yes, as AmitD mentioned again, you're not required to resolve either or all of the type parameters:

public class WithStringComputable<K> implements Computable<K, String> {
    @Override
    public Result compute(Foo foo, Parameter<K, String>... parameters) {
        ...
    }
}

public class OhJustACoupleObjectsComputable<K, V> implements Computable<K, V> {
    @Override
    public Result compute(Foo foo, Parameter<K, V>... parameters) {
        ...
    }
}

public class NumbersComputable<N1 extends Number, N2 extends Number> implements Computable<N1, N2> {
    @Override
    public Result compute(Foo foo, Parameter<N1, N2>... parameters) {
        ...
    }
}

Note that varargs don't play nice with generics, and you will get a type-safety warning at the callsite when invoking compute. Instead, consider having the method take an Iterable<Parameter<K, V>>.

Upvotes: 3

Related Questions