Java Generic methods and generic classes enable programmers to specify, with a single method declaration, a set of related methods or, with a single class declaration, a set of related types, respectively.
Generics also provide compile-time type safety that allows programmers to catch invalid types at compile time.
Using Java Generic concept, we might write a generic method for sorting an array of objects, then invoke the generic method with Integer arrays, Double arrays, String arrays and so on, to sort the array elements.
Generics and Inheritance
We know that Java inheritance allows us to assign a variable A to another variable B if A is subclass of B. So we might think that any generic type of A can be assigned to generic type of B, but it’s not the case. Lets see this with a simple program.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| package com.journaldev.generics;public class GenericsInheritance { public static void main(String[] args) { String str = "abc"; Object obj = new Object(); obj=str; // works because String is-a Object, inheritance in java MyClass<String> myClass1 = new MyClass<String>(); MyClass<Object> myClass2 = new MyClass<Object>(); //myClass2=myClass1; // compilation error since MyClass<String> is not a MyClass<Object> obj = myClass1; // MyClass<T> parent is Object } public static class MyClass<T>{}} |
We are not allowed to assign MyClass<String> variable to MyClass<Object> variable because they are not related, in fact MyClass<T> parent is Object.
Generic Classes and Subtyping
We can subtype a generic class or interface by extending or implementing it. The relationship between the type parameters of one class or interface and the type parameters of another are determined by the extends and implements clauses.
For example, ArrayList<E> implements List<E> that extends Collection<E>, so ArrayList<String> is a subtype of List<String> and List<String> is subtype of Collection<String>.
The subtyping relationship is preserved as long as we don’t change the type argument, below shows an example of multiple type parameters.
1
2
| interface MyList<E,T> extends List<E>{} |
The subtypes of List<String> can be MyList<String,Object>, MyList<String,Integer> and so on.
Generics Wildcards
Question mark (?) is the wildcard in generics and represent an unknown type. The wildcard can be used as the type of a parameter, field, or local variable and sometimes as a return type. We can’t use wildcards while invoking a generic method or instantiating a generic class. In following sections, we will learn about upper bounded wildcards, lower bounded wildcards, and wildcard capture.
Generics Upper Bounded Wildcard
Upper bounded wildcards are used to relax the restriction on the type of variable in a method. Suppose we want to write a method that will return the sum of numbers in the list, so our implementation will be something like this.
1
2
3
4
5
6
7
| public static double sum(List<Number> list){ double sum = 0; for(Number n : list){ sum += n.doubleValue(); } return sum; } |
Now the problem with above implementation is that it won’t work with List of Integers or Doubles because we know that List<Integer> and List<Double> are not related, this is when upper bounded wildcard is helpful. We use generics wildcard with extends keyword and the upper bound class or interface that will allow us to pass argument of upper bound or it’s subclasses types.
The above implementation can be modified like below program.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| package com.journaldev.generics;import java.util.ArrayList;import java.util.List;public class GenericsWildcards { public static void main(String[] args) { List<Integer> ints = new ArrayList<>(); ints.add(3); ints.add(5); ints.add(10); double sum = sum(ints); System.out.println("Sum of ints="+sum); } public static double sum(List<? extends Number> list){ double sum = 0; for(Number n : list){ sum += n.doubleValue(); } return sum; }} |
It’s similar like writing our code in terms of interface, in above method we can use all the methods of upper bound class Number. Note that with upper bounded list, we are not allowed to add any object to the list except null. If we will try to add an element to the list inside the sum method, the program won’t compile.
Generics Unbounded Wildcard
Sometimes we have a situation where we want our generic method to be working with all types, in this case unbounded wildcard can be used. Its same as using <? extends Object>.
1
2
3
4
5
| public static void printData(List<?> list){ for(Object obj : list){ System.out.print(obj + "::"); } } |
We can provide List<String> or List<Integer> or any other type of Object list argument to the printDatamethod. Similar to upper bound list, we are not allowed to add anything to the list.
Generics Lower bounded Wildcard
Suppose we want to add Integers to a list of integers in a method, we can keep the argument type as List<Integer> but it will be tied up with Integers whereas List<Number> and List<Object> can also hold integers, so we can use lower bound wildcard to achieve this. We use generics wildcard (?) with superkeyword and lower bound class to achieve this.
We can pass lower bound or any super type of lower bound as an argument in this case, java compiler allows to add lower bound object types to the list.
1
2
3
| public static void addIntegers(List<? super Integer> list){ list.add(new Integer(50)); } |
Subtyping using Generics Wildcard
1
2
| List<? extends Integer> intList = new ArrayList<>();List<? extends Number> numList = intList; // OK. List<? extends Integer> is a subtype of List<? extends Number> |
Type Erasure
Generics was added to provide type-checking at compile time and it has no use at run time, so java compiler uses type erasure feature to remove all the generics type checking code in byte code and insert type-casting if necessary. Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.
For example if we have a generic class like below;
1
2
3
4
5
6
7
8
9
10
11
12
| public class Test<T extends Comparable<T>> { private T data; private Test<T> next; public Test(T d, Test<T> n) { this.data = d; this.next = n; } public T getData() { return this.data; }} |
The Java compiler replaces the bounded type parameter T with the first bound interface, Comparable, as below code:
1
2
3
4
5
6
7
8
9
10
11
12
| public class Test { private Comparable data; private Test next; public Node(Comparable d, Test n) { this.data = d; this.next = n; } public Comparable getData() { return data; }} |
No comments:
Post a Comment