I need to type method signature so it accepts 2 equally typed parameters of different particular concrete subtypes.
Is it possible to code something like this with generics? How would you solve it? (The case is absolutely an example)
public <T extends List<?>> T<String> sum(T<Integer> sublistOfInts, T<Boolean> sublistOfBooleans){
/*fusion both lists*/
return sublistOfStrings;
}
EDIT: In the end, what I am looking for is a way for the compiler to pass:
ArrayList<String> myList = sum(new ArrayList<Integer>(), new ArrayList<Boolean>());
but not:
ArrayList<String> myList = sum(new ArrayList<Double>(), new ArrayList<Boolean>());
nor
ArrayList<String> myList = sum(new LinkedList<Integer>(), new ArrayList<Boolean>());
(...)
EDIT 2: I found a better example. Imagine an interface Tuple, with child classes Duple, Triple>..., it would be perfectly nice to have something like
<T extends Tuple<?>> T<String> reset( T<String> input, T<Boolean> listToNull){
T copy = input.copy();
for (int i=0; i<input.size();i++){
if (listToNull.get(i)){
copy.set(i,null);
}
}
}
To update the Box class to use generics, you create a generic type declaration by changing the code "public class Box" to "public class Box<T>". This introduces the type variable, T, that can be used anywhere inside the class.
Whenever you want to restrict the type parameter to subtypes of a particular class you can use the bounded type parameter. If you just specify a type (class) as bounded parameter, only sub types of that particular class are accepted by the current generic class.
Generic methods are methods that introduce their own type parameters. This is similar to declaring a generic type, but the type parameter's scope is limited to the method where it is declared. Static and non-static generic methods are allowed, as well as generic class constructors.
Generic Types Differ Based on Their Type Arguments: Consider the following Java code.
First, get rid of the method argument generics. There's no reason to force a caller to provide ArrayList<Integer>
and ArrayList<Boolean>
when you want to return an ArrayList<String>
. Just accept any List<Integer>
and List<Boolean>
, and leave it to your method to turn them into the appropriate return List
.
Since you know that you want to return some sort of List
of String
you can write your parameter as <T extends List<String>>
and your return type as simply T
.
That leaves us with the hard part: getting your method to instantiate an object of unknown type. That's hard. You can't just do new T();
. You need to invoke something that will produce a T
on your behalf. Luckily, Java 8 provides a Functional Interface for Supplier<T>
. You just need to invoke the get()
method to get your ArrayList<String>
or whatever else you might want. The part that's painful is that your invoker needs to provide their own Supplier
. But I think that's as good as it gets in Java 8.
Here's the code:
public <T extends List<String>> T sum(
List<Integer> sublistOfInts,
List<Boolean> sublistOfBooleans,
Supplier<T> listMaker) {
T sublistOfStrings = listMaker.get();
/*fusion of both lists*/
return sublistOfStrings;
}
At least this compiles:
ArrayList<String> myNewList = thing.<ArrayList<String>>sum(intList, boolList, ArrayList::new);
And this does not:
ArrayList<String> myNewList = thing.<ArrayList<String>>sum(intList, boolList, LinkedListList::new);
You can even leave off the type parameter on the invocation. This compiles:
ArrayList<String> myNewList = thing.sum(intList, boolList, ArrayList::new);
And this does not:
ArrayList<String> myNewList = thing.sum(intList, boolList, LinkedListList::new);
In brief, it's because type arguments can't themselves be parameterized. And that's because we don't know how many type arguments they themselves would take, nor the restrictions that might be placed on them.
Take the relatively obscure class RoleList
. It extends ArrayList<Object>
, so it fits List<?>
. But it doesn't take a type argument at all. So if someone invoked your sum()
method with RoleList
, that would require in your example:
RoleList<Integer> intList = // something
RoleList<Boolean> boolList = // something
RoleList<String> myNewList = thing.sum(intList, boolList);
That clearly can't work since it requires an unparameterized type to take type arguments. And if you took off the type arguments like so:
RoleList intList = // something
RoleList boolList = // something
RoleList myNewList = thing.sum(intList, boolList);
Then your method needs to be able to accept two List<Object>
arguments and return a value of List<Object>
. And that violates your basic premise, that you be able to control such things.
In reality, RoleList
should not be allowed here at all, because you can't ever guarantee that one instance will contain only Integer
s, another only Boolean
s, and a third only String
s. A compiler that allowed RoleList
here would necessarily have weaker type checking than we have now.
So the bottom line is that you just can't do what you're asking because Java just isn't built that way.
You can still get complete type safety inside your sum()
method using my suggested method, above. You make sure that the incoming List
s contain only Integer
or Boolean
values, respectively. You make sure that the caller can rely on the return of a specific subtype of List
containing only String
values. All of the guarantees that make a difference are there.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With