Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typing a generic type but not its own type in Java Generics

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);
                       }
                  }  
 }
like image 921
Whimusical Avatar asked Dec 29 '15 13:12

Whimusical


People also ask

How do you declare a generic type in Java?

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.

How do I restrict a generic type in Java?

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.

How does a generic method differ from a generic type in Java?

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.

Are generics type differ based on their type arguments?

Generic Types Differ Based on Their Type Arguments: Consider the following Java code.


1 Answers

What I suggest you do instead

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);

Why you can't just do what you're asking

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 Integers, another only Booleans, and a third only Strings. 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.

Why that's ok

You can still get complete type safety inside your sum() method using my suggested method, above. You make sure that the incoming Lists 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.

like image 60
Erick G. Hagstrom Avatar answered Oct 20 '22 15:10

Erick G. Hagstrom