2

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
3
  • 2
    do not use raw types, change generic types definition to <T, R extends Collection<T>> and you will get compile time error. Commented Sep 16, 2020 at 12:48
  • The problem with a is that the type is chosen at the call site. For example, you could do both List<String> list = a(something); Set<String> set = a(something); - and at least one of those will likely fail, because something is likely not both a Set and a List. In that case, it is better just to do an explicit cast where you would otherwise invoke a. Commented Sep 16, 2020 at 13:08
  • <T, R extends Collection<T>> R a(Collection<T> collection) but why not returning it directly in listWith and setWith methods? Also why wrapping asList with a different name? asSet would be a good enhancement. Commented Sep 16, 2020 at 13:16

2 Answers 2

1
  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());
Sign up to request clarification or add additional context in comments.

6 Comments

I guess also a question can be why with the fix #1 User user = a(listWith("test")); compiles.
Also in your fix #2 a can be made trivial (returning the same collection). Suppose a does some work: public static <T, R extends Collection<Tuple<T, T>>> R a(Collection<T> collection) { Collection<Tuple<T, T>> res = ...; for (T t: collection) { res.add(new Tuple<>(t, t)); } return (R) res; }. In such case fix #2 seems impossible. But User user = a(listWith("test")); compiles. So this doesn't answer OP's second question how to restrict the return type to be only class that extends the Collection.
Your code is not compliant with my 2nd fix. What I suggest is a(R collection) while you stick back with a(Collection<T> collection). Thus the method actually doing something public static <T, R extends Collection<T>> R a(R collection) { Collection<Tuple<T, T>> res = new ArrayList<>(); for (T t: collection) { res.add(new Tuple<>(t, t)); } return (R) res; } doesn't let User user = a(listWith("test")); compile.
Yeap but this signature is a lie (possible because of casting). Actually you accept a subtype of Collection<T> and return a subtype of Collection<Tuple<T, T>>. With correct signature public static <T, R extends Collection<T>, R1 extends Collection<Tuple<T, T>>> R1 a(R collection) User user = a(listWith("test")); still compiles.
I finally see where do you aim at. The real purpose of my answer is the process of getting into the last step: omitting the whole wrapper. I don't think it's possible to really restrict the return type in such case.
|
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.