Difficulty understanding wildcards

public static void reverse(List<?> list) 
{ 
List<Object> tmp = new ArrayList<Object>(list);
for (int i = 0; i < list.size(); i++) 
{

list.set(i, tmp.get(list.size()-i-1)); // compile-time error , why ? 

}

}

      

I am studying generics. I know this: when is the wildcard used? is replaced by the appropriate type. When is reverse () called? will be replaced, and since every type is a subtype of Object, there should be no error. I'm looking for a crystal clear explanation. Please, help.

+3


source to share


3 answers


You can pass any of List<SomeType>

your methods reverse

as an argument List<?> list

, and the compiler only has to add items SomeType

to that List

.

For example, it could be List<String>

, a List<Ineger>

, etc.

Hence, list.set

cannot work as the compiler doesn't know the type of the element that supports the actual List<?> list

one passed to the method. It doesn't know that the elements List<Object> tmp

arose from the same List

(and therefore can be added to it safely).



The correct way to write your method is to define the generic type of the parameter:

public static <T> void reverse(List<T> list) 
{ 
    List<T> tmp = new ArrayList<> (list);
    for (int i = 0; i < list.size(); i++)  {
        list.set(i, tmp.get(list.size()-i-1));
    }
}

      

Now the compiler knows that List

they tmp

contain elements of the same type.

+1


source


Generics work this way .

Here:



public static void reverse(List<?> list) 

      

since we don't know what the element's type means list

, we cannot add objects to it.

Here:



List<Object> tmp = new ArrayList<Object>(list);
  ...
list.set(i, tmp.get(list.size()-i-1)); // compile-time error , why ? 

      

you add Object

to list

, which in the absolute may contain elements with a different type.
In your case, this is not how you write:

List<Object> tmp = new ArrayList<Object>(list);

      

But the compiler doesn't throw exceptions when trying to understand the logic of your code.
Because otherwise it has to check all the code and the compiler error messages can be very tricky.
For example, let's say you are add tmp.set(0, "aa");

in your code at a time.

0


source


List<?>

is a list of unknown type, we do not know which type is in this list, generics have been introduced to exclude runtime exceptions and ensure security. You cannot add any type to the collection with undefined because you will break it. An example, if allowed:

    List<Dog> dogs = new ArrayList<>(Arrays.asList(new Dog("dog")));

    public doSomething(List<?> notKnownType) {
        notKnownType.add(new Cat("cat"));   
// if this ok or not? <?> is not known type, all types have Object as parent let add Cat
    }

    for (Dog dog : dogs) {
        System.out.println(dog); //ClassCastException
    }

      

ClassCastExceptin because we added cat to dogs and threw every dog ​​to cat, at runtime the generators do not exist and all collections, etc. contain Object. This line will look like this after compilation:

for (Object dog : dogs) {
    System.out.println((Dog) dog); //ClassCastExceptin 
}

      

to prevent this you won't be able to add something to an unknown collection of types and get a compile-time error here list.set(i, tmp.get(list.size()-i-1)); // compile-time error , why ?

0


source







All Articles