Reputation: 6649
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);
}
}
}
Upvotes: 8
Views: 478
Reputation: 4945
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.
Upvotes: 4
Reputation: 11913
I know what you have is just an example but if you only want to return a single list that contains the String value of all the contents in a group of other lists you could just specify a method that takes a varargs of unbounded lists.
public List<String> sum(List<?>... lists) {
List<String> sublistOfStrings = new ArrayList<String>();
for(List<?> list : lists) {
for(Object obj : list) {
sublistOfStrings.add(obj.toString());
}
}
return sublistOfStrings;
}
Upvotes: 0
Reputation: 2105
There are two things that strike me about the above. How are you instantiating sublistOfStrings
, and what advantages do you expect to get above using plain old inheritance?
There are a couple of ways of instantiating T<String>
. You could have a factory check the class of your arguments, and instantiate it based on that. Or you could do something like:
(List<String>)sublistOfInts.getClass().newInstance()
But you can't just go new T<String>()
. So you're basing the implementation of your return type off of the type of one of your arguments anyway (unless there's a way I haven't thought of).
By specifying both arguments are of type 'T' doesn't mean they're exactly of the same concrete type 'T' either. For instance
sum((int)1, (long)2L); // valid
sum((int)2, (double)2.0D); // valid ... etc
public <T extends Number> T sum(T a, T b) {
return a;
}
So you aren't enforcing that sublistOfInts
and sublistOfBooleans
are both of type say ArrayList
, and therefore you can return an ArrayList
. You still need to write code to check what type of List<?>
you'll want to return based on the arguments.
I think you're better off not using generics, and using something like this:
public List<String> sum(List<Integer> sublistOfInts, List<Boolean> sublistOfBooleans) {
// Determine what subclass of list you want to instantiate based on `sublistOfInts` and `sublistOfBools`
// Call factory method or newInstance to instantiate it.
// Sum, and return.
}
You can still call it with subtypes of List<?>
. I don't beleive there's any advantage you could get from generics even if Java did let you do it (which is doesn't, because it can't parameterize T
like that).
Upvotes: 2