简体   繁体   中英

How to specify the generic type of a collection?

I want to define a function which can convert a kind of Set to another, like convert HashSet to LinkedHashSet. Here is the function declaration. The sp is sharedpreferences.

public Set<String> decodeStringSet(String key, @Nullable Set<String> defaultValue, Class<? extends Set> cls){
    Set<String> result = sp.getStringSet(key, defaultValue);
    if(result == null){
        return defaultValue;
    }
    else {
        String[] array = result.toArray(new String[0]);
        Set<String> a;
        try {
            a = cls.newInstance();
        } catch (IllegalAccessException | InstantiationException var7) {
            return defaultValue;
        }

        a.addAll(Arrays.asList(array));
        return a;
    }
}

However, the compiler remind me that "Unchecked assignment: '? extends java.util.Set' to 'java.util.Set<java.lang.String>'" on "a = cls.newInstance();". I don't know how to change cls to cls<java.lang.String>.

The warning is unavoidable. Isolate it in a helper method and toss the appropriate @SuppressWarnings at it. Or, refactor how this thing works. In general, the generics of Class<?> are weird and don't work well; if you try to write code that relies on the generics part to make it work, it's likely to result in many situations where you can't avoid these warnings, and the API is suboptimal. 1

One tricky way to do what you're trying to do here in a one-size-fits-all way is so-called Super Type Tokens. You can search the web for this concept, because for what you're specifically doing here, STTs are overkill. What you are looking for, is a supplier .

You want the caller not to pass you the type of a set. No. You want the caller to pass you a piece of code that, if executed, creates the set.

While we're at it, let's get rid of the array, you're shifting the elements through that array for absolutely no sensible reason.

public <S extends Set<String>> S decodeStringSet(String key, @Nullable Set<String> defaultValue, Supplier<S> setMaker) {
    Set<String> result = sp.getStringSet(key, defaultValue);
    if(result == null) return defaultValue;

    S a = setMaker.get();
    a.addAll(result);
    return a;
}

This code can be used as follows:

LinkedHashSet<String> mySet = decodeStringSet("myKey", null, LinkedHashSet::new);

Perhaps you're unfamiliar with this syntax. new LinkedHashSet() will, when you run that code, create a LinkedHashSet. In contrast, LinkedHashSet::new will, when you run that code, produce an object that can be asked to create a LinkedHashSet, by invoking its get() method. One does the act right this very moment. The other wraps 'do the act' into a little machine. You can hand the machine to other code, or press the button on the machine to make it do the act, and you can press the button as often as you feel like.


[1] Need some more explanations as to why relying on the generics of jlClass is awkward and not a good idea?

A class object simply cannot , itself, represent generics, whereas generics can represent generics. That is: List<List<String>> is perfectly fine. However, Class<List<String>> does not make sense. You can write it, (jlClass does not have hardcoded rules to keep sanity alive in the langspec), but it doesn't represent anything: There's just one class object that represents the type juList . This one object cannot therefore represent the generics; you can't have one class object representing List<String> and another representing List<Integer> . Less important, but still annoying - there are things class objects can represent that generics cannot. int.class is types as Class<Integer> but this isn't quite right.

Hence, in your example, the compiler consider Class<? extends Set> Class<? extends Set> as problematic; it's got a raw type inside the generics. However, it is technically correct, in that it is not possible to represent eg a Set<T> , merely 'a Set, whose generics are unknown, given that jlClass objects cannot represent them'.

Lastly, classes basically only produce (the P in PECS - which explains what the difference is between <Number> , <? extends Number> , and <? super Number> ); it is mentally difficult to fathom the difference between Class<? extends String> Class<? extends String> and Class<String> , because it's an irrelevant difference, given that jlClass only produces. And yet, often you really do need to write Class<? extends String> Class<? extends String> because if you don't, the compiler refuses to compile your code for imaginary, irrelevant reasons. That's because, again, jlClass is not hardcoded in the lang spec: The compiler does not know that there is no effective distinction between Class<T> and Class<? extends T> Class<? extends T> , and java does not have a way to mark off a given generics param as forced Produces-only or some such.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM