Generics
Navigate Classes and Objects topic: ) |
Java is a strongly typed language, so a field in a class may be typed like this:
![]() | Code listing 4.34: Repository.javapublicclassRepository{publicIntegeritem;publicIntegergetItem(){returnitem;}publicvoidsetItem(IntegernewItem){item=newItem;}} |
This ensures that, only Integer
objects can be put in the field and a ClassCastException
can't occur at runtime, only compile-time error can occur. Unfortunately, it can be used only with Integer
objects. If you want to use the same class in another context with String
s, you have to generalize the type like this:
![]() | Code listing 4.35: Repository.javapublicclassRepository{publicObjectitem;publicObjectgetItem(){returnitem;}publicvoidsetItem(IntegernewItem){item=newItem;}publicvoidsetItem(StringnewItem){item=newItem;}} |
But you will have ClassCastException
at runtime again and you can't easily use your field. The solution is to use Generics.
Generic class
[edit | edit source]A generic class does not hard code the type of a field, a return value or a parameter. The class only indicates that a generic type should be the same, for a given object instance. The generic type is not specified in the class definition. It is specified during object instantiation. This allows the generic type to be different from an instance to another. So we should write our class this way:
![]() | Code listing 4.36: Repository.javapublicclassRepository<T>{publicTitem;publicTgetItem(){returnitem;}publicvoidsetItem(TnewItem){item=newItem;}} |
Here, the generic type is defined after the name of the class. Any new identifier can be chosen. Here, we have chosen T, which is the most common choice. The actual type is defined at the object instantiation:
![]() | Code section 4.35: Instantiation.Repository<Integer>arithmeticRepository=newRepository<Integer>();arithmeticRepository.setItem(newInteger(1));Integernumber=arithmeticRepository.getItem();Repository<String>textualRepository=newRepository<String>();textualRepository.setItem("Hello!");Stringmessage=textualRepository.getItem(); |
Although each object instance has its own type, each object instance is still strongly typed:
![]() | Code section 4.36: Compile error.Repository<Integer>arithmeticRepository=newRepository<Integer>();arithmeticRepository.setItem("Hello!"); |
A class can define as many generic types as you like. Choose a different identifier for each generic type and separate them by a comma:
![]() | Code listing 4.37: Repository.javapublicclassRepository<T,U>{publicTitem;publicUanotherItem;publicTgetItem(){returnitem;}publicvoidsetItem(TnewItem){item=newItem;}publicUgetAnotherItem(){returnanotherItem;}publicvoidsetAnotherItem(UnewItem){anotherItem=newItem;}} |
When a type that is defined with generic (for example, Collection<T>
) is not used with generics (for example, Collection
) is called a raw type.
Generic method
[edit | edit source]A generic type can be defined for just a method:
![]() | Code section 4.37: Generic method.public<D>Dassign(Collection<D>generic,Dobj){generic.add(obj);returnobj;} |
Here a new identifier (D) has been chosen at the beginning of the method declaration. The type is specific to a method call and different types can be used for the same object instance:
![]() | Code section 4.38: Generic method call.Collection<Integer>numbers=newArrayList<Integer>();Integernumber=assign(numbers,newInteger(1));Collection<String>texts=newArrayList<String>();Stringtext=assign(texts,"Store it."); |
The actual type will be defined by the type of the method parameter. Hence, the generic type can't be defined only for the return value as it wouldn't be resolved. See the Class<T> section for a solution.
Question 4.8: Consider the following class.
![]() | Question 4.8: Question8.javapublicclassQuestion8<T>{publicTitem;publicTgetItem(){returnitem;}publicvoidsetItem(TnewItem){item=newItem;}publicstaticvoidmain(String[]args){Question8<String>aQuestion=newQuestion8<String>();aQuestion.setItem("Open your mind.");aQuestion.display(aQuestion.getItem());}publicvoiddisplay(Stringparameter){System.out.println("Here is the text: "+parameter);}publicvoiddisplay(Integerparameter){System.out.println("Here is the number: "+parameter);}publicvoiddisplay(Objectparameter){System.out.println("Here is the object: "+parameter);}} |
What will be displayed on the console?
![]() | Console for Answer 4.8Here is the text: Open your mind. |
aQuestion.getItem()
is typed as a string.
Wildcard Types
[edit | edit source]As we have seen above, generics give the impression that a new container type is created with each different type parameter. We have also seen that in addition to the normal type checking, the type parameter has to match as well when we assign generics variables. In some cases this is too restrictive. What if we would like to relax this additional checking? What if we would like to define a collection variable that can hold any generic collection, regardless of the parameter type it holds? The wildcard type is represented by the character <?>, and pronounced Unknown, or Any-Type. Any-Type can be expressed also by <? extends Object>
. Any-Type includes Interfaces, not only Classes. So now we can define a collection whose element type matches anything. See below:
![]() | Code section 4.39: Wildcard type.Collection<?>collUnknown; |
Upper bounded wildcards
[edit | edit source]You can specify a restriction on the types of classes that may be used. For example, <? extends ClassName>
only allows objects of class ClassName
or a subclass. For example, to create a collection that may only contain "Serializable" objects, specify:
![]() | Code section 4.40: Collection of serializable subobjects.Collection<String>textColl=newArrayList<String>();Collection<?extendsSerializable>serColl=textColl; |
The above code is valid because the String
class is serializable. Use of a class that is not serializable would cause a compilation error. The added items can be retrieved as Serializable
object. You can call methods of the Serializable
interface or cast it to String
. The following collection can only contain objects that extend the class Animal
.
![]() | Code listing 4.38: Dog.javaclassDogextendsAnimal{} |
![]() | Code section 4.41: Example of subclass.// Create "Animal Collection" variableCollection<?extendsAnimal>animalColl=newArrayList<Dog>(); |
Lower bounded wildcards
[edit | edit source]<? super ClassName>
specifies a restriction on the types of classes that may be used. For example, to declare a Comparator that can compare Dogs, you use:
![]() | Code section 4.42: Superclass.Comparator<?superDog>myComparator; |
Now suppose you define a comparator that can compare Animals:
![]() | Code section 4.43: Comparator.classAnimalComparatorimplementsComparator<Animal>{intcompare(Animala,Animalb){//...}} |
Since Dogs
are Animals
, you can use this comparator to compare Dogs also. Comparators for any superclass of Dog can also compare Dog; but comparators for any strict subclass cannot.
![]() | Code section 4.44: Generic comparator.Comparator<Animal>myAnimalComparator=newAnimalComparator();staticintcompareTwoDogs(Comparator<?superDog>comp,Dogdog1,Dogdog2){returncomp.compare(dog1,dog2);} |
The above code is valid because the Animal
class is a supertype of the Dog
class. Use of a class that is not a supertype would cause a compilation error.
Unbounded wildcard
[edit | edit source]The advantage of the unbounded wildcard (i.e. <?>
) compared to a raw type (i.e. without generic) is to explicitly say that the parameterized type is unknown, not any type. That way, all the operations that implies to know the type are forbidden to avoid unsafe operation. Consider the following code:
![]() | Code section 4.45: Unsafe operation.publicvoidaddAtBottom(CollectionanyCollection){anyCollection.add(newInteger(1));} |
This code will compile but this code may corrupt the collection if the collection only contains strings:
![]() | Code section 4.46: Corruption of list.List<String>col=newArrayList<String>();addAtBottom(col);col.get(0).endsWith("."); |
![]() | Console for Code section 4.46Exception in thread "main" java.lang.ClassCastException: java.lang.Integer incompatible with java.lang.String at Example.main(Example.java:17) |
This situation could have been avoided if the addAtBottom(Collection)
method was defined with an unbounded wildcard: addAtBottom(Collection<?>)
. With this signature, it is impossible to compile a code that is dependent of the parameterized type. Only independent methods of a collection (clear()
, isEmpty()
, iterator()
, remove(Object o)
, size()
, ...) can be called. For instance, addAtBottom(Collection<?>)
could contain the following code:
![]() | Code section 4.47: Safe operation.publicvoidaddAtBottom(Collection<?>anyCollection){Iterator<?>iterator=anyCollection.iterator();while(iterator.hasNext()){System.out.print(iterator.next());}} |
Class<T>
[edit | edit source]Since Java 1.5, the class java.lang.Class
is generic. It is an interesting example of using generics for something other than a container class. For example, the type of String.class is Class<String>
, and the type of Serializable.class is Class<Serializable>
. This can be used to improve the type safety of your reflection code. In particular, since the newInstance()
method in Class now returns T, you can get more precise types when creating objects reflectively. Now we can use the newInstance()
method to return a new object with exact type, without casting. An example with generics:
![]() | Code section 4.48: Automatic cast.Customercust=Utility.createAnyObject(Customer.class);// No casting...publicstatic<T>TcreateAnyObject(Class<T>cls){Tret=null;try{ret=cls.newInstance();}catch(Exceptione){// Exception Handling}returnret;} |
The same code without generics:
![]() | Code section 4.49: Former version.Customercust=(Customer)Utility.createAnyObject(Customer.class);// Casting is needed...publicstaticObjectcreateAnyObject(Classcls){Objectret=null;try{ret=cls.newInstance();}catch(Exceptione){// Exception Handling}returnret;} |
Motivation
[edit | edit source]Java was long criticized for the need to explicitly type-cast an element when it was taken out of a "container/collection" class. There was no way to enforce that a "collection" class contains only one type of object (e.g., to forbid at compile time that an Integer
object is added to a Collection
that should only contain String
s). This is possible since Java 1.5. In the first couple of years of Java evolution, Java did not have a real competitor. This has changed by the appearance of Microsoft C#. With Generics Java is better suited to compete against C#. Similar constructs to Java Generics exist in other languages, see Generic programming for more information. Generics were added to the Java language syntax in version 1.5. This means that code using Generics will not compile with Java 1.4 and less. Use of generics is optional. For backwards compatibility with pre-Generics code, it is okay to use generic classes without the generics type specification (<T>
). In such a case, when you retrieve an object reference from a generic object, you will have to manually cast it from type Object to the correct type.
Note for C++ programmers
[edit | edit source]Java Generics are similar to C++ Templates in that both were added for the same reason. The syntax of Java Generic and C++ Template are also similar. There are some differences however. The C++ template can be seen as a kind of macro, in that a new copy of the code is generated for each generic type referenced. All extra code for templates is generated at compiler time. In contrast, Java Generics are built into the language. The same code is used for each generic type. For example:
![]() | Code section 4.50: Java generics.Collection<String>collString=newArrayList<String>();Collection<Integer>collInteger=newArrayList<Integer>(); |
Both these objects appear as the same type at runtime (both ArrayList
's). The generic type information is erased during compilation (type erasure). For example:
![]() | Code section 4.51: Type erasure.public<T>voidmethod(Targument){Tvariable;…} |
is transformed by erasure into:
![]() | Code section 4.52: Transformation.publicvoidmethod(Objectargument){Objectvariable;…} |
Question 4.9: Consider the following class.
![]() | Question 4.9: Question9.javaimportjava.util.ArrayList;importjava.util.Collection;publicclassQuestion9{publicstaticvoidmain(String[]args){Collection<String>collection1=newArrayList<String>();Collection<?extendsObject>collection2=newArrayList<String>();Collection<?extendsString>collection3=newArrayList<String>();Collection<?extendsString>collection4=newArrayList<Object>();Collection<?superObject>collection5=newArrayList<String>();Collection<?superObject>collection6=newArrayList<Object>();Collection<?>collection7=newArrayList<String>();Collection<?extendsObject>collection8=newArrayList<?>();Collection<?extendsObject>collection9=newArrayList<Object>();Collection<?extendsInteger>collection10=newArrayList<String>();Collection<String>collection11=newArrayList<?extendsString>();Collectioncollection12=newArrayList<String>();}} |
Which lines will generate a compile error?
![]() | Answer 4.9: Answer9.javaimportjava.util.ArrayList;importjava.util.Collection;publicclassAnswer9{publicstaticvoidmain(String[]args){Collection<String>collection1=newArrayList<String>();Collection<?extendsObject>collection2=newArrayList<String>();Collection<?extendsString>collection3=newArrayList<String>();Collection<?extendsString>collection4=newArrayList<Object>();Collection<?superObject>collection5=newArrayList<String>();Collection<?superObject>collection6=newArrayList<Object>();Collection<?>collection7=newArrayList<String>();Collection<?extendsObject>collection8=newArrayList<?>();Collection<?extendsObject>collection9=newArrayList<Object>();Collection<?extendsInteger>collection10=newArrayList<String>();Collection<String>collection11=newArrayList<?extendsString>();Collectioncollection12=newArrayList<String>();}} |
- Line 9:
Object
does not extendString
. - Line 10:
String
is not a superclass ofObject
. - Line 13:
ArrayList<?>
can't be instantiated. - Line 15:
Integer
does not extendString
. - Line 16:
ArrayList<? extends String>
can't be instantiated.