When to use generic methods and when to use wild-card?

前端 未结 9 2191
猫巷女王i
猫巷女王i 2020-11-22 10:32

I am reading about generic methods from OracleDocGenericMethod. I am pretty confused about the comparison when it says when to use wild-card and when to use generic methods.

相关标签:
9条回答
  • 2020-11-22 10:42

    As far as I understand, there is only one use case when wildcard is strictly needed (i.e. can express something that you can not express using explicit type parameters). This is when you need to specify a lower bound.

    Apart from that however wildcards serve to write more concise code, as described by the following statements in the document you mention:

    Generic methods allow type parameters to be used to express dependencies among the types of one or more arguments to a method and/or its return type. If there isn't such a dependency, a generic method should not be used.

    [...]

    Using wildcards is clearer and more concise than declaring explicit type parameters, and should therefore be preferred whenever possible.

    [...]

    Wildcards also have the advantage that they can be used outside of method signatures, as the types of fields, local variables and arrays.

    0 讨论(0)
  • 2020-11-22 10:44

    In your first question: It means that if there is a relation between the parameter's type and the method's return type then use a generic.

    For example:

    public <T> T giveMeMaximum(Collection<T> items);
    public <T> Collection<T> applyFilter(Collection<T> items);
    

    Here you are extracting some of the T following a certain criteria. If T is Long your methods will return Long and Collection<Long>; the actual return type is dependent on the parameter type, thus it is useful, and advised, to use generic types.

    When this is not the case you can use wild card types:

    public int count(Collection<?> items);
    public boolean containsDuplicate(Collection<?> items);
    

    In this two example whatever the type of the items in the collections the return types will be int and boolean.

    In your examples:

    interface Collection<E> {
        public boolean containsAll(Collection<?> c);
        public boolean addAll(Collection<? extends E> c);
    }
    

    those two functions will return a boolean whatever is the types of the items in the collections. In the second case it is limited to instances of a subclass of E.

    Second question:

    class Collections {
        public static <T> void copy(List<T> dest, List<? extends T> src) {
        ...
    }
    

    This first code allow you to pass an heterogeneous List<? extends T> src as a parameter. This list can contain multiple elements of different classes as long as they all extends the base class T.

    if you had:

    interface Fruit{}
    

    and

    class Apple implements Fruit{}
    class Pear implements Fruit{}
    class Tomato implements Fruit{}
    

    you could do

    List<? extends Fruit> basket = new ArrayList<? extends Fruit>();
    basket.add(new Apple());
    basket.add(new Pear());
    basket.add(new Tomato());
    List<Fruit> fridge = new ArrayList<Fruit>(); 
    
    Collections.copy(fridge, basket);// works 
    

    On the other hand

    class Collections {
        public static <T, S extends T> void copy(List<T> dest, List<S> src) {
        ...
    }
    

    constrain List<S> src to be of one particular class S that is a subclass of T. The list can only contain elements of one class (in this instance S) and no other class, even if they implement T too. You wouldn't be able to use my previous example but you could do:

    List<Apple> basket = new ArrayList<Apple>();
    basket.add(new Apple());
    basket.add(new Apple());
    basket.add(new Apple());
    List<Fruit> fridge = new ArrayList<Fruit>();
    
    Collections.copy(fridge, basket); /* works since the basket is defined as a List of apples and not a list of some fruits. */
    
    0 讨论(0)
  • 2020-11-22 10:47

    One other difference which is not listed here.

    static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
        for (T o : a) {
            c.add(o); // correct
        }
    }
    

    But the following will result in compile time error.

    static <T> void fromArrayToCollection(T[] a, Collection<?> c) {
        for (T o : a) {
            c.add(o); // compile time error
        }
    }
    
    0 讨论(0)
  • 2020-11-22 10:48

    Mainly -> Wildcards enforce generics at the parameter/argument level of a Non-Generic method. Note. It can also be performed in genericMethod by default, but here instead of ? we can use T itself.

    package generics;

    public class DemoWildCard {
    
    
        public static void main(String[] args) {
            DemoWildCard obj = new DemoWildCard();
    
            obj.display(new Person<Integer>());
            obj.display(new Person<String>());
    
        }
    
        void display(Person<?> person) {
            //allows person of Integer,String or anything
            //This cannnot be done if we use T, because in that case we have to make this method itself generic
            System.out.println(person);
        }
    
    }
    
    class Person<T>{
    
    }
    

    SO wildcard has its specific usecases like this.

    0 讨论(0)
  • 2020-11-22 10:49

    ? means unknown

    The general rule applies: You can read from it, but not write

    given simple pojo Car

    class Car {
        void display(){
    
        }
    }
    

    This will compile

    private static <T extends Car> void addExtractedAgain1(List<T> cars) {
        T t = cars.get(1);
        t.display();
        cars.add(t);
    }
    

    This method won't compile

    private static void addExtractedAgain2(List<? extends Car> cars) {
        Car car = cars.get(1);
        car.display();
        cars.add(car); // will not compile
    }
    

    Another example

    List<?> hi = Arrays.asList("Hi", new Exception(), 0);
    
    hi.forEach(o -> {
       o.toString() // it's ok to call Object methods and methods that don't need the contained type
    });
    
    hi.add(...) // nothing can be add here won't compile, we need to tell compiler what the data type is but we do not know
    
    0 讨论(0)
  • 2020-11-22 10:55

    There are certain places, where wildcards, and type parameters do the same thing. But there are also certain places, where you have to use type parameters.

    1. If you want to enforce some relationship on the different types of method arguments, you can't do that with wildcards, you have to use type parameters.

    Taking your method as example, suppose you want to ensure that the src and dest list passed to copy() method should be of same parameterized type, you can do it with type parameters like so:

    public static <T extends Number> void copy(List<T> dest, List<T> src)
    

    Here, you are ensured that both dest and src have same parameterized type for List. So, it's safe to copy elements from src to dest.

    But, if you go on to change the method to use wildcard:

    public static void copy(List<? extends Number> dest, List<? extends Number> src)
    

    it won't work as expected. In 2nd case, you can pass List<Integer> and List<Float> as dest and src. So, moving elements from src to dest wouldn't be type safe anymore. If you don't need such kind of relation, then you are free not to use type parameters at all.

    Some other difference between using wildcards and type parameters are:

    • If you have only one parameterized type argument, then you can use wildcard, although type parameter will also work.
    • Type parameters support multiple bounds, wildcards don't.
    • Wildcards support both upper and lower bounds, type parameters just support upper bounds. So, if you want to define a method that takes a List of type Integer or it's super class, you can do:

      public void print(List<? super Integer> list)  // OK
      

      but you can't use type parameter:

       public <T super Integer> void print(List<T> list)  // Won't compile
      

    References:

    • Angelika Langer's Java Generics FAQs
    0 讨论(0)
提交回复
热议问题