Below are important Question & Answers taken from the Q&A discussions in this section.
Q1. Am still not clear on the difference between Store<T> and Store<?>. If you check the video at 1:15, that slide shows class Store<T> where T is defined as "unknown type". Am still not able to understand the benefit we get by defining Store<?> as even "?" means "any type/unknown type".
In both cases, we can send the actual arguments as Store<Integer> or Store<Double> in the implementation code. I watched this entire video on unbounded wildcard but am not able to get the use of it as generics notation should be able to achieve this anyways...
Can you please explain this in detail? Thanks in advance
Answer: Store<T> generifies the class itself. Store<?> is generifying the parameterized type, not the class. So, you can have class Store<T>, but not class Store<?>. Let's consider the following different versions of the go() method. Cases (i), (ii) and (iv) are using parameterized types List<String>, List<Object>, and List<?> while (iii) used raw type. In cases (i) & (ii), you can invoke go as go(new ArrayList<String>()) or go(new ArrayList<Object>()) respectively. For instance, you cannot invoke (ii) with go(new ArrayList<Integer>()) due to invariance property and it helps with type safety. Problem with case (iii) was discussed in the lecture on Raw Types. Unlike cases (i) & (ii), you can invoke it with go(new ArrayList<Integer>()) or go(new ArrayList<String>()). However, you can run into type safety at run time as one may insert whatever they wish in the go() method. So, it is same as not using generics even though List interface is a generic type (interface List<E>). With (iv), like in the case of (iii), you can pass anything (ArrayList<Integer> or ArrayList<String>), however it protects you from not running into type safety at runtime as it prevents you from accessing certain risky methods in List (like add), which have class-level type parameter E as method parameter type. For e.g., add method is defined as boolean add(E e). Had compiler allowed you to invoke add() in the go() method of case (iv), then there wouldn't be any difference from case (iii) where raw type was used, i.e., we can have runtime type-safety issue. However, (iv) would still allow use to use all methods of List that do not have E as a method parameter type, i.e., methods like contains(), clear(), etc that are totally harmless (i.e., nothing to do with type safety).
(i) go(List<String> list)
(ii) go(List<Object> list)
(iii) go(List list)
(iv) go(List<?> list)
So, to summarize, your main doubt was about case (iv). It addresses the drawbacks in cases (i), (ii), (iii). It addresses the limitation of (i) & (ii) which are inflexible in that you can only pass something like ArrayList<String> or ArrayList<Object>. It addresses case (iii) as with List<?>, we do not end up with type safety problem at runtime (doesn't allow us to invoke those risky methods in List, which a raw type would allow).
Q2. In the below class, why did we not define the method public void set(T a) as public <T> void set(T a)?
class Store<T> implements Container<T> {
private T a;
public void set(T a) {
this.a = a;
}
public T get() {
return a;
}
}Answer: By defining it as public void set(T a), Generic Type Store<T> is saying that whatever you specify as type argument in your parameterized type, only that can be passed to set method. For e.g. if we have Store<String> s = new Store<>(), then you can only pass a String to set() method ~ s.set("java"), but cannot do s.set(1.0);. But, had we used public <T> void set(T a), then for that set method, the T defined in class declaration is no longer valid and you could do s.set(1.0) or s.set("java") or s.set(new Student()) too.
Below is another example. As you can see, StoreTest has both variations with foo() & bar(). In main method, foo(1.0) gives compiler error while bar(1.0) does not as bar is a generic method while foo is not a generic method (it is using a type parameter of the class).
public class Store {
public static void main(String[] args) {
StoreTest<String> st = new StoreTest<>();
st.foo("java");
st.foo(1.0); // compiler error
st.bar(1.0); // ok
}
}
class StoreTest<T> {
void foo(T t) { }
<T> void bar(T t) {}
}Q3. What is the Difference between wildcards(<?>) and Generics(<T>)? Could you actually give an elaborate example which shows an instance where an objective could only have been achieved using wildcards (unbounded or bounded) and couldn't have been possible using generics (<T>). And similarly vice-versa.
Answer: 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. 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. For instance, the copy() method below ensures that the src and dest list passed should be of same parameterized type.
public static <T extends Number> void copy(List<T> dest, List<T> src)
But, if you go on to change the method to use wildcard as in:
public static void copy(List<? extends Number> dest, List<? extends Number> src)
In this 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.
Source: https://stackoverflow.com/questions/18176594/when-to-use-generic-methods-and-when-to-use-wild-card
Q4. Can you please explain complier's type safety restriction concept, again in the lecture titled 'Unbounded Wildcard + Demo'. Also I did not able to understand what is class-level type parameter?
Class-level type parameter is simply the type parameter specified in the class definition (e.g., class ABC<T>{}). Later, we'll look at generic methods, which can have their own type parameters. Now, regarding the restriction, let's just consider the code in the following slide, which is reproduced below. Here due to the type safety restriction that was mentioned, compiler does not allow us to invoke add() method on list2. If it allows, then we may add different types of values like int, double and so on to list2, which could have been a List<Double> as passed by the caller code. So, in the invoking code, the caller may retrieve a value and try to assign to a double type and it would give run-time error if the value retrieved is int, i.e., type safety is broken, which is what we have seen in the raw types lecture earlier. The next question is how does compiler know that it should raise an error when add is invoked. The signature of add method is add(E e) where E is class-level type parameter and the type safety restriction says compiler will not allow us to invoke any such methods that use class-level type parameters. This is not the case with contains method, which is also used below as contains uses Object as method parameter type and not E. So, we can invoke any method that does not use class-level type parameter (e.g., indexOf(Object), iterator(), etc). Hope it is clear now. Thanks.
int getCommonElementsCount(List<?> list1, List<?> list2) {
int count = 0;
for (Object element : list1) {
if (list2.contains(element))
count++;
}
list2.add(25);
return count;
}