Understanding generic in Java

Viewed 98

I am building some generic codes which returning a collection of any type of object. Here is my build:

public class Util{
    public static <T, R extends Collection> R a(Collection<T> collection) {
        return (R) collection;
    }

    public static <T> List<T> listWith(T... items) {
        return Arrays.asList(items);
    }

    public static <T> Set<T> setWith(T... items) {
        return new HashSet<>(Arrays.asList(items));
    }
}

My Idea is from this Util class I can create any list or set of any object type, something like:

List<String> list = a(listWith("str1", "str2"));
Set<Object> set = a(setWith(new Object(), new Object()));

I have now two questions:

  1. How can I restrict the type of the Obj in the left to be the same as the Obj type of the parameter passed to the listWith method? Because right now I can do something like : Set<String> set = a(setWith(new Object(), new Object())); without any compile error
  2. How can I restrict the return type of the a method to be only class that extends the Collection. Right now I can do something like: User user = a(listWith("test")); also without compile error but then it will throw an casting exception. I did try above with return type as R extends Collection but it did not success
2 Answers
  1. How can I restrict the type of the Obj in the left to be the same as the Obj type of the parameter passed to the listWith method?

    Don't use a raw type of the Collection and restrict it to the generic type.

    public static <T, R extends Collection<T>> R a(Collection<T> collection) { 
        return (R) collection;
    }
    

    But it can be designed better (look below).

  2. How can I restrict the return type of the a method to be only class that extends the Collection. Right now I can do something like: User user = a(listWith("test")); also without compile error but then it will throw an casting exception.

    Remember the class User might implement Collection in the future and compiler doesn't check this information at the compile time.

    Further restriction will do that. If the R is a subtype of Collection<T> and the very same collection is required as an input parameter, you can ask for R collection and return it without explicit casting.

     public static <T, R extends Collection<T>> R a(R collection) {
         return collection;
     }
    

After applying the 2nd part of the answer:

List<String> list = a(listWith("str1", "str2"));           // compiles
Set<Object> set = a(setWith(new Object(), new Object()));  // compiles

Set<String> set2 = a(setWith(new Object(), new Object())); // doesn't compile
User user = a(listWith("test"));                           // doesn't compile

This restriction is valid for this simple case but doesn't make sense at all. You can find a further explanation at @DmytroMitin's answer.

As you can see, the whole application of the #2 finally returns the input itself, which it gives me a question:

"Is such generic wrapping needed?":

List<String> list = listWith("str1", "str2");
Set<Object> set = setWith(new Object(), new Object());

I'll just add to @Nikolas's answer why with his fix #1

public static <T, R extends Collection<T>> R a(Collection<T> collection) { 
    return (R) collection;
}

User user = a(listWith("test")); still compiles i.e. why R extends Collection<T> doesn't restrict enough the return type so that it still can be User.

I guess the thing is that the type parameters T, R of the method a can be inferred like

User user = Util.<String, User & Collection<String>>a(listWith("test"));

Here Util.<String, User & Collection<String>>a... seems to be not valid syntax but if we define

class User {}
class SubUser extends User implements Collection<String>{ ... }

then

User user = Util.<String, SubUser>a(listWith("test"));

is possible.


Regarding your 2nd question

How can I restrict the return type of the a method to be only class that extends the Collection.

Generally this seems impossible because for every class User its subclass

class SubUser<T> extends User implements Collection<T> {...}

is still possible.

Related