Java Generics part 4
In previous posts, we saw the basic introduction to generics and we also understood how generics is implemented internally and got a glimpse about bounded-types and their syntaxes. In this post we’ll try to understand about generic methods and wildcard character(?).
Generic Methods and wildcard character(?)
- **
m1(ArrayList
l)</code>:** - We can call this method by passing
ArrayList
typeString
only. - Within the body above method, we can only add
String
type of objects to theArrayList
. If we are trying to add any other type by mistake, then we will get compile-time error.
- We can call this method by passing
m1(ArrayList<String> l){
l.add("Ottoman"); // Valid
l.add(null); // valid
l.add(10); // Compile-time error
}
m1(ArrayList<?> l)
:- We can call this method by passing
ArrayList
of any type. - Within the body of above method, we cannot add anything to the list except
null
because we do not know the type exactly. null
is allowed because it is valid value for any type.- These type of methods are best suitable for read-only operations.
- We can call this method by passing
m1(ArrayList<?> l){
l.add(10.5); // Invalid
l.add("A"); // Invalid
l.add(10); // Invalid
l.add(null); // VALID
}
m1(ArrayList<? extends X> l)
:X
can be either class or interface.- If
X
is a class, then we can call this method by passingArrayList
of eitherX
type or its child classes. - If
X
is aninterface
, then we can call this method by passingArrayList
of eitherX
type or its implementation classes. - Within the body of above method, we cannot add anything to the list except
null
because we do not know the type ofX
or its child classes exactly. - These type of methods are also best suitable for read-only operations.
m1(ArrayList<? super X> l)
:X
can be either class or interface.- If
X
is a class, then we can call this method by passingArrayList
of eitherX
type or its super classes. - If
X
is an interface, then we can call this method by passingArrayList
of eitherX
type or super class of implementation class ofX
. - Within the body of this method, we can add
X
type of objects andnull
to the list.
Some common implementations:
ArrayList<String> l = new ArrayList<String>(); // VALID
ArrayList<?> l = new ArrayList<String>(); // VALID
ArrayList<?> l = new ArrayList<Integer>(); // VALID
ArrayList<? extends Number> l = new ArrayList<Integer>(); // VALID
ArrayList<? extends Number> l = new ArrayList<String>(); //INVALID
ArrayList<? super String> l = new ArrayList<Object>(); // VALID
ArrayList<?> l = new ArrayList<?>(); // CE: unexpected type required class or interface **without bounds.**
ArrayList<?> l = new ArrayList<? extends Number>(); // CE: unexpected type required class or interface **without bounds.**
Declaring Type-parameter at Class level
class Test<T>{
// We can use `T` within this class based on our requirement.
}
Declaring Type-parameter at method level
We have to declare type-parameter just before return type.
class Test{
public <T> void m1(T ob){
// We can use T anywhere within this method based on our requirement.
}
}
We can define bounded types even at method level also.
public <T> void m1(T ob);
public <T extends Runnable> void m1(T ob);
public <T extends Number> void m1(T ob);
public <T super String> void m1(T ob);
public <T extends Runnable & Comparable> void m1(T ob);
public <T extends Number & Runnable> void m1(T ob);
Communication with Non-generic code
If we send generic object to non-generic area then, it starts behaving like non-generic object. Similarly, if we send non-generic object to generic area then, it starts behaving like generic object i.e. the location in which object is present behavior will be defined accordingly. e.g.
class Test{
public static void main(String[] args){
// generic area
ArrayList<String> al = new ArrayList<String>();
al.add("Bose");
al.add("Jim");
al.add(new Integer(10)); // CE
m1(al);
System.out.println(al); // [Bose, Jim, 10.5, 10, true]
al.add(10.5); // Compile-time error
}
// Non-generic area
public static void m1(ArrayList al){
al.add(10.5);
al.add(new Integer(10));
al.add(true);
}
}
Conclusions
- The main purpose of generics is to provide type-safety and to resolve type-casting problems.
- Type-safety and type-casting both are applicable at compile-time, hence generics concept is also applicable only at compile-time but not at runtime.
- At the time of compilation, as the last step generics syntax will be removed and hence for the JVM generic syntax will not be available. Hence, the following declarations are equal.
ArrayList al = new ArrayList<String>(); ArrayList al = new ArrayList<Integer>(); ArrayList al = new ArrayList<Double>(); ArrayList al = new ArrayList();
e.g.
ArrayList al = new ArrayList<String>(); al.add(10); al.add(10.5); al.add(true); System.out.println(al); // [10, 10.5, true]
- The following declarations are equal. For these
ArrayList
objects, we can add onlyString
type.ArrayList<String> al = new ArrayList<String>(); ArrayList<String> al = new ArrayList<>();
- At compile-time, generally the code is compiled considering the generic syntax and then the compiler removes the generic syntax and after removing it compiles the code again. This is the reason the below code generates compile time error.
class Test{ public void m1(ArrayList<Integer> al) // ==> converts to m1(ArrayList al) { // body } public void m1(ArrayList<String> al) // ==> converts to m1(ArrayList al) { // body } // Compile-time error: name clash: Both methods having same erasure. }