1. Why use generic programming
Generic programming means that code written can be reused by many different types of objects. Before adding generic classes to Java (JDK 1.5), generic programming was implemented through inheritance. For example:
public class ArrayList
{
private Object[] elementData;
public Object get(int i){......}
public void add(Object o){......}
}
There are two problems in this implementation.
I. Mandatory type conversion is required when a value is obtained
ArrayList files = new ArrayList();
files.add(0);
files.add(1);
files.add(1.0);
files.add("abc");
String filenames = (String)files.get(0);
II. Lack of error checking to add objects of any class to the array
files.add(new File("...."));
For this call, neither compilation nor execution will fail. However, in other places, such as unreasonable downtype conversion, loss of integer precision in long integer conversion, etc., will bring some errors.
Generics provide a better solution: type parameters. For example:
ArrayList< String > files = new ArrayList< String>();
The compiler can also make good use of this information. When get is called, the compiler knows that the return type is String without forcing type conversion. Instead of Object:
String filename = files.get(0);
The compiler also knows that there is a parameter of type String in the add method in ArrayList < String >. This would be safer than using Object-type parameters. This means that the compiler can check to avoid inserting objects of incorrect type.
2. Generic classes
A generic class is a class with one or more type variables. The sample code is as follows:
public class Pair< T >
{
private T first;
private T second;
public Pair(){
first=null;
second=null;
}
public Pair(T first,T second){
this.first = first;
this.second = second;
}
public void setFirst(T newValue){first = newValue;}
public T getFirst(){
return this.first;
}
public T getSecond(){
return this.second;
}
public void setSecond(T newValue){second = newValue;}
}
Generic classes can also have multiple type variables that define Pair classes, where the first and second domains use different types. For example:
public class Pair< T, U>{...}
Replacing type variables with specific types instantiates generic types: Pair < String >.
Think of the results as generic classes and methods with constructors:
Pair< String>()
Pair< String>(String,String)
String getFirst()
String getSecond()
void setFirst(String)
void setSecond(String)
In other words, generic classes can be seen as factories for common classes.
The following program is from Java Core Technology Volume 1, which shows the static minmax method traversing the array and calculating the minimum and maximum simultaneously. It returns two results with a Pair object.
public class PairTest {
public static void main(String[] args) {
String[] words = {"Mary","had","a","little","lamb"};
Pair<String> mm= ArrayAlg.minmax(words);
System.out.println("min: "+mm.getFirst());
System.out.println("max: "+mm.getSecond());
}
}
class ArrayAlg{
public static Pair<String> minmax(String[] a){
if(a==null||a.length==0)
return null;
String min = a[0];
String max = a[0];
for(int i=1;i<a.length;i++){
if(min.compareTo(a[i])>0)
min=a[i];
if(max.compareTo(a[i])<0)
max=a[i];
}
return new Pair<>(min,max);
}
}
Operation results:
3. Generic Approach
As the name implies, generic methods are simple methods with type parameters.
class ArrayAlg
{
public static <T> T getMiddle(T a){
return a[a.length/2];
}
}
Notice that the type variable is placed after the modifier (public static in this case) and before the return type. Generic methods can be defined in general classes or in generic classes. When a generic method is invoked, the parentheses before the method name are placed in the specific type:
String middle = ArrayAlg.< String>getMiddle("John","Q.","public");
Note that < String > can usually be omitted.
4. Limitation of type variables
Sometimes, classes and methods need to constrain type variables. For example:
class ArrayAlg{
public static <T> T min(T[] a){
if(a==null||a.length==0)
return null;
T smallest = a[0];
for(int i=1;i<a.length;i++)
if(smallest.compareTo(a[i])>0)
smallest = a[i];
return smallest;
}
}
The problem with this code is that there is no guarantee that every T-type object has a compareTo method. The solution is to limit the type variable T to a class that implements the Compareble interface (the standard interface of compareTo with only one method). The restrictions are as follows:
public static < T extends Compareble> T min(T[] a){...}
Now generic min methods can only be called by arrays of classes that implement the Compareble interface (such as String, Date, etc.). Because Rectangle, for example, does not implement the Compareble interface, calling min will result in a compilation error. < T extends Bounding Type > indicates that T should be a subtype of the binding type. T and BoundingType can be classes or interfaces. When multiple qualifications are used, wildcards "&" are used to separate them, such as < T extends Compareble & Serializable >. Examples are as follows:
package Generic;
import java.util.Calendar;
import java.util.GregorianCalendar;
public class PairTest {
public static void main(String[] args) {
/*String[] words = {"Mary","had","a","little","lamb"};
Pair<String> mm= ArrayAlg.minmax(words);*/
GregorianCalendar[] birthdays = {
new GregorianCalendar(1906,Calendar.DECEMBER,9),
new GregorianCalendar(1815,Calendar.DECEMBER,10),
new GregorianCalendar(1903,Calendar.DECEMBER,3),
new GregorianCalendar(1910,Calendar.DECEMBER,22),
};
Pair<GregorianCalendar> mm = ArrayAlg.minmax(birthdays);
System.out.println("min: "+mm.getFirst().getTime());
System.out.println("max: "+mm.getSecond().getTime());
}
}
class ArrayAlg{
public static <T extends Comparable> Pair<T> minmax(T[] a){
if(a==null||a.length==0)
return null;
T min = a[0];
T max = a[0];
for(int i=1;i<a.length;i++){
if(min.compareTo(a[i])>0)
min=a[i];
if(max.compareTo(a[i])<0)
max=a[i];
}
return new Pair<>(min,max);
}
}
Result:<\center>
* Note: Refer to Chapter 12 of Java Core Technology Volume I. *