generics in java

25
Generics in Java – Part I Venkat Subramaniam [email protected] http://www.agiledeveloper.com/download.aspx Abstract Java 5 (JDK 1.5) introduced the concept of Generics or parameterized types. In this article, I introduce the concepts of Generics and show you examples of how to use it. In Part II, we will look at how Generics are actually implemented in Java and some issues with the use of Generics. Issue of Type-Safety Java is a strongly typed language. When programming with Java, at compile time, you expect to know if you pass a wrong type of parameter to a method. For instance, if you define Dog aDog = aBookReference; // ERROR where, aBookReference is a reference of type Book, which is not related to Dog, you would get a compilation error. Unfortunately though, when Java was introduced, this was not carried through fully into the Collections library. So, for instance, you can write Vector vec = new Vector(); vec.add("hello"); vec.add(new Dog()); There is no control on what type of object you place into the Vector. Consider the following example: package com.agiledeveloper; import java.util.ArrayList; import java.util.Iterator; public class Test { public static void main(String[] args) { ArrayList list = new ArrayList(); populateNumbers(list); int total = 0; Iterator iter = list.iterator(); while(iter.hasNext()) { total += ((Integer) (iter.next())).intValue(); }

Upload: vinhitbk

Post on 23-Nov-2015

84 views

Category:

Documents


3 download

DESCRIPTION

Generics in Java

TRANSCRIPT

  • Generics in Java Part I Venkat Subramaniam

    [email protected] http://www.agiledeveloper.com/download.aspx

    Abstract Java 5 (JDK 1.5) introduced the concept of Generics or parameterized types. In this article, I introduce the concepts of Generics and show you examples of how to use it. In Part II, we will look at how Generics are actually implemented in Java and some issues with the use of Generics.

    Issue of Type-Safety Java is a strongly typed language. When programming with Java, at compile time, you expect to know if you pass a wrong type of parameter to a method. For instance, if you define

    Dog aDog = aBookReference; // ERROR

    where, aBookReference is a reference of type Book, which is not related to Dog, you would get a compilation error.

    Unfortunately though, when Java was introduced, this was not carried through fully into the Collections library. So, for instance, you can write

    Vector vec = new Vector(); vec.add("hello"); vec.add(new Dog());

    There is no control on what type of object you place into the Vector. Consider the following example:

    package com.agiledeveloper;

    import java.util.ArrayList; import java.util.Iterator;

    public class Test { public static void main(String[] args) { ArrayList list = new ArrayList(); populateNumbers(list);

    int total = 0; Iterator iter = list.iterator(); while(iter.hasNext()) { total += ((Integer) (iter.next())).intValue(); }

  • System.out.println(total); }

    private static void populateNumbers(ArrayList list) { list.add(new Integer(1)); list.add(new Integer(2)); } }

    In the above program I create an ArrayList, populate it with some Integer values, and then total the values by extracting the Integer out of the ArrayList.

    The output from the above program is a value of 3 as you would expect.

    Now, what if I change populateNumbers() method as follows:

    private static void populateNumbers(ArrayList list) { list.add(new Integer(1)); list.add(new Integer(2)); list.add("hello"); }

    I will not get any compilation errors. However, the program will not execute correctly. We will get the following runtime error:

    Exception in thread "main" java.lang.ClassCastException: java.lang.String at com.agiledeveloper.Test.main(Test.java:17)

    We did not quite have the type-safety with collections pre Java 5.

    What are Generics? Back in the good old days when I used to program in C++, I enjoyed using a cool feature in C++ templates. Templates give you type-safety while allowing you to write code that is general, that is, it is not specific to any particular type. While C++ template is a very powerful concept, there are a few disadvantages with it. First, not all compilers support it well. Second, it is fairly complex that it takes quite an effort to get good at using it. Lastly, there are a number of idiosyncrasies in how you can use it that it starts hurting the head when you get fancy with it (this can be said generally about C++, but that is another story). When Java came out, most features in C++ that was complex, like templates and operator overloading, were avoided.

    In Java 5, finally it was decided to introduce Generics. Though generics the ability to write general or generic code which is independent of a particular type is similar to the template in C++ in concept, there are a number of differences. For one, unlike C++ where different classes are generated for each parameterized type, in Java, there is only one class for each generic type, irrespective of how many different types you instantiated it

  • with. There are of course certain problems as well in Java Generics, but that we will talk about in Part II. In this part I, we will focus on the good things.

    The work of Generics in Java originated from a project called GJ1 (Generic Java) which started out as a language extension. This idea then went though the Java Community Process (JCP) as Java Specification Request (JSR) 142.

    Generic Type-safety Lets start with the non-generic example we looked at to see how we can benefit from Generics. Lets convert the code above to use Generics. The modified code is shown below:

    package com.agiledeveloper;

    import java.util.ArrayList; import java.util.Iterator;

    public class Test { public static void main(String[] args) { ArrayList list = new ArrayList(); populateNumbers(list);

    int total = 0; for(Integer val : list) { total = total + val; }

    System.out.println(total); }

    private static void populateNumbers(ArrayList list) { list.add(new Integer(1)); list.add(new Integer(2)); list.add("hello"); } }

    I am using ArrayList instead of the ArrayList. Now, if I compile the code, I get a compilation error:

    Test.java:26: cannot find symbol symbol : method add(java.lang.String) location: class java.util.ArrayList list.add("hello"); ^

    1 error

    The parameterized type of ArrayList provides the type-safety. Making Java easier to type and easier to type, was the slogan of the generics contributors in Java.

  • Naming Conventions In order to avoid confusion between the generic parameters and real types in your code, you must follow a good naming convention. If you are following good Java convention and software development practices, you would probably not be naming your classes with single letters. You would also be using mixed case with class names starting with upper case. Here are some conventions to use for generics:

    Use the letter E for collection elements, like in the definition public class PriorityQueue {}

    Use letters T, U, S, etc. for general types

    Writing Generic Classes The syntax for writing a generic class is pretty simple. Here is an example of a generic class:

    package com.agiledeveloper;

    public class Pair { private E obj1; private E obj2;

    public Pair(E element1, E element2) { obj1 = element1; obj2 = element2; }

    public E getFirstObject() { return obj1; } public E getSecondObject() { return obj2; } }

    This class represents a pair of values of some generic type E. Lets look at some examples of usage of this class:

    // Good usage Pair aPair = new Pair(new Double(1), new Double(2.2));

    If we try to create an object with types that mismatch we will get a compilation error. For instance, consider the following example:

    // Wrong usage Pair anotherPair = new Pair(new Integer(1), new Double(2.2));

    Here, I am trying to send an instance of Integer and an instance of Double to the instance of Pair. However, this will result in a compilation error. Generics and Substitutability

  • Generics honor the Liskovs Substitutability Principle4. Let me explain that with an example. Say I have a Basket of Fruits. To it I can add Oranges, Bananas, Grapes, etc. Now, lets create a Basket of Banana. To this, I should only be able to add Bananas. It should disallow adding other types of fruits. Banana is a Fruit, i.e., Banana inherits from Fruit. Should Basket of Banana inherit from Basket for Fruits as shown in Figure below?

    If Basket of Banana were to inherit from Basket of Fruit, then you may get a reference of type Basket of Fruit to refer to an instance of Basket of Banana. Then, using this reference, you may add a Banana to the basket, but you may also add an Orange. While adding a Banana to a Basket of Banana is OK, adding an Orange is not. At best, this will result in a runtime exception. However, the code that uses Basket of Fruits may not know how to handle this. The Basket of Banana is not substitutable where a Basket of Fruits is used.

    Generics honor this principle. Lets look at this example:

    Pair objectPair = new Pair(new Integer(1), new Integer(2));

    This code will produce a compile time error:

    Error: line (9) incompatible types found : com.agiledeveloper.Pair required: com.agiledeveloper.Pair

    Now, what if you want to treat different type of Pair commonly as one type? We will look at this later in the Wildcard section.

    Before we leave this topic, lets look at one weird behavior though. While

    Pair objectPair = new Pair(new Integer(1), new Integer(2));

    is not allowed, the following is allowed, however:

  • Pair objectPair = new Pair(new Integer(1), new Integer(2));

    The Pair without any parameterized type is the non-generic form of the Pair class. Each generic class also has a non-generic form so it can be accessed from a non-generic code. This allows for backward compatibility with existing code or code that has not been ported to use generics. While this compatibility has a certain advantage, this feature can lead to some confusion and also type-safety issues.

    Generic Methods In addition to classes, methods may also be parameterized.

    Consider the following example:

    public static void filter(Collection in, Collection out) { boolean flag = true; for(T obj : in) { if(flag) { out.add(obj); } flag = !flag; } }

    The filter() method copies alternate elements from the in Collection to the out Collection. The in front of the void indicates that the method is a generic method with being the parameterized type. Lets look at a usage of this generic method:

    ArrayList lst1 = new ArrayList(); lst1.add(1); lst1.add(2); lst1.add(3);

    ArrayList lst2 = new ArrayList(); filter(lst1, lst2); System.out.println(lst2.size());

    We populate an ArrayList lst1 with three values and then filter (copy) its contents into another ArrayList lst2. The size of the lst2 after the call to filter() method is 2.

    Now, lets look at a slightly different call:

    ArrayList dblLst = new ArrayList(); filter(lst1, dblLst);

    Here I get a compilation error:

    Error:

  • line (34) filter(java.util.Collection,java.util.Collection) in com.agiledeveloper.Test cannot be applied to (java.util.ArrayList,

    java.util.ArrayList)

    The error says that it cant send ArrayList of different types to this method. This is good. However, lets try the following:

    ArrayList lst3 = new ArrayList(); ArrayList lst = new ArrayList(); lst.add("hello"); filter(lst, lst3); System.out.println(lst3.size());

    Like it or not, this code compiles with no error and the call to lst3.size() returns a 1. First, why did this compile and whats going on here? The compiler bends over its back to accommodate calls to generic methods, if possible. In this case, by treating lst3 as a simple ArrayList, without any parameterized type that is (refer to the last paragraph in the Generics and Substitutability section above), it is able to call the filter method.

    Now, this can lead to some problems. Lets add another statement to the example above. As I start typing, the IDE (I am using IntelliJ IDEA) is helping me with code prompt as shown below:

    It says that the call to the get() method takes an index and returns an Integer. Here is the completed code:

    ArrayList lst3 = new ArrayList(); ArrayList lst = new ArrayList(); lst.add("hello"); filter(lst, lst3); System.out.println(lst3.size()); System.out.println(lst3.get(0));

    So, what do you think should happen when you run this code? May be runtime exception? Well, surprise! We get the following output for this code segment:

    1 hello

    Why is that? The answer is in what actually gets compiled (we will discuss more about this in Part II of this article). The short answer for now is, even though code completion

  • suggested that an Integer is being returned, in reality the return type is Object. So, the String "hello" managed to get through without any error.

    Now, what happens if we add the following code:

    for(Integer val: lst3) { System.out.println(val); }

    Here, clearly, I am asking for an Integer from the collection. This code will raise a ClassCastException. While Generics are supposed to make our code type-safe, this example shows how we can easily, with intent or by mistake, bypass that, and at best, end up with runtime exception, or at worst, have the code silently misbehave. Enough of those issues for now. We will look at some of these gotchas further in Part II. Lets progress further on what works well for now in this Part I.

    Upper bounds Lets say we want to write a simple generic method to determine the max of two parameters. The method prototype would look like this:

    public static T max(T obj1, T obj2)

    I would use it as shown below:

    System.out.println(max(new Integer(1), new Integer(2)));

    Now, the question is how do I complete the implementation of the max() method? Lets take a stab at this:

    public static T max(T obj1, T obj2) { if (obj1 > obj2) // ERROR { return obj1; } return obj2; }

    This will not work. The > operator is not defined on references. Hum, how can I then compare the two objects? The Comparable interface comes to mind. So, why not use the comparable interface to get our work done:

    public static T max(T obj1, T obj2) { // Not elegant code Comparable c1 = (Comparable) obj1; Comparable c2 = (Comparable) obj2;

    if (c1.compareTo(c2) > 0)

  • { return obj1; } return obj2; }

    While this code may work, there are two problems. First, it is ugly. Second, we have to consider the case where the cast to Comparable fails. Since we are so heavily dependent on the type implementing this interface, why not ask the compiler to enforce this. That is exactly what upper bounds do for us. Here is the code:

    public static T max(T obj1, T obj2) { if (obj1.compareTo(obj2) > 0) { return obj1; } return obj2; }

    The compiler will check to make sure that the parameterized type given when calling this method implements the Comparable interface. If you try to call max() with instances of some type that does not implement the Comparable interface, you will get a stern compilation error.

    Wildcard We are progressing well so far and you are probably eager to dive into a few more interesting concepts with Generics. Lets consider this example:

    public abstract class Animal { public void playWith(Collection playGroup) {

    } }

    public class Dog extends Animal { public void playWith(Collection playGroup) { } }

    The Animal class has a playWith() method that accepts a Collection of Animals. The Dog, which extends Animal, overrides this method. Lets try to use the Dog class in an example:

    Collection dogs = new ArrayList();

    Dog aDog = new Dog(); aDog.playWith(dogs); //ERROR

  • Here I create an instance of Dog and send a Collection of Dog to its playWith() method. We get a compilation error:

    Error: line (29) cannot find symbol method playWith(java.util.Collection)

    This is because a Collection of Dogs cant be treated as a Collection of Animals which the playWith() method expects (see the section Generics and Substitutability above). However, it would make sense to be able to send a Collection of Dogs to this method, isnt it? How can we do that? This is where the wildcard or unknown type comes in.

    We modify both the playMethod() methods (in Animal and Dog) as follows:

    public void playWith(Collection playGroup)

    The Collection is not of type Animal. Instead it is of unknown type (?). Unknown type is not Object, it is just unknown or unspecified.

    Now, the code aDog.playWith(dogs); compiles with no error.

    There is a problem however. We can also write:

    ArrayList numbers = new ArrayList(); aDog.playWith(numbers);

    The change I made to allow a Collection of Dogs to be sent to the playWith() method now permits a Collection of Integers to be sent as well. If we allow that, that will become one weird dog. How can we say that the compiler should allow Collections of Animal or Collections of any type that extends Animal, but not any Collections of other types? This is made possible by the use of upper bounds as shown below:

    public void playWith(Collection

  • ArrayList dogList1 = new ArrayList(); ArrayList dogList2 = new ArrayList(); // copy(dogList1, dogList2);

    In this code we are copying Dogs from one Dog ArrayList to another.

    Since Dogs are Animals, a Dog may be in both a Dogs ArrayList and an Animals ArrayList, isnt it? So, here is the code to copy from a Dogs ArrayList to an Animals ArrayList.

    ArrayList animalList = new ArrayList(); copy(dogList1, animalList);

    This code, however, fails compilation with error:

    Error: line (36) copy(java.util.Collection,java.util.Collection) in com.agiledeveloper.Test cannot be applied to (java.util.ArrayList,

    java.util.ArrayList)

    How can we make this work? This is where the lower bounds come in. Our intent for the second argument of Copy is for it to be of either type T or any type that is a base type of T. Here is the code:

    public static void copy(Collection from, Collection

  • 1. GJ http://homepages.inf.ed.ac.uk/wadler/gj 2. JSR 14 http://jcp.org/en/jsr/detail?id=14 3. http://java.sun.com/j2se/1.5.0/download.jsp 4. http://c2.com/cgi/wiki?OoDesignPrinciples

  • Generics in Java Part II Venkat Subramaniam

    [email protected] http://www.agiledeveloper.com/download.aspx

    Abstract In Part-I we showed the benefits and usage of Generics in Java 5. In this part (Part-II), we discuss how it is implemented in Java, and we delve into a number of issues with it. In Part-III we will discuss the problems with mixing generic and non-generic code, and the issues with converting a non-generic legacy code to generics. Unchecked Warning The Java compiler will warn you if it cant verify type-safety. You would see this if you mix generic and non-generic code (which is not a good idea). Developing applications, while leaving these kinds of warnings unattended is a risk. It is better to treat warnings as errors. Consider the following example: public class Test { public static void foo1(Collection c) { } public static void foo2(Collection c) { } public static void main(String[] args) { Collection coll = new ArrayList(); foo1(coll); ArrayList lst = new ArrayList(); foo2(lst); } }

    You have a method foo1 which accepts a traditional Collection as parameter. Method foo2, on the other hand, accepts a generics version of the Collection. You are sending an object of traditional ArrayList to method foo2. Since the ArrayList may contain objects of different types, within the foo2 method, the compiler is not able to guarantee that the Collection will contain only instances of Integer. The compiler in this case issues a warning as shown below: Warning: line (22) [unchecked] unchecked conversion found : java.util.ArrayList

    required: java.util.Collection

  • While getting this warning is certainly better than not being alerted about the potential problem, it would have been better if it had been an error instead of a warning. Use the compilation flag Xlint to make sure you do not overlook this warning. There is another problem. In the main method, you are sending generics Collection of Integer to the method foo1. Even though the compiler does not complain about this, this is dangerous. What if within the foo1 method you add objects of types other than Integer to the collection? This will break the type-safety. You may be wondering how in the first place the compiler even allowed you to treat a generic type as traditional type. Simply put, the reason is, there is no concept of generics at the byte code level. I will delve into the details of this in the Generics Implementation section. Restrictions There are a number of restrictions when it comes to using generics. You are not allowed to create an array of generic collections. Any array of collection of wildcard is allowed, but is dangerous from the type-safety point of view. You cant create a generic of primitive type. For example, ArrayList is not allowed. You are not allowed to create parameterized static fields within a generic class, or have static methods with parameterized types as parameters. For instance, consider the following: class MyClass { private Collection myCol1; // OK private static Collection myCol2; // ERROR }

    Within generic class, you cant instantiate an object or an array of object of parameterized type. For instance, if you have a generic class MyClass, within a method of that class you cant write: new T(); or new T[10];

    You may throw an exception of generic type, however, in the catch block, you have to use a specific type instead of the generic. You may inherit your class from another generic class; however, you cant inherit from a parametric type. For instance, while class MyClass2 extends MyClass { }

  • is OK, class MyClass2 extends T { }

    is not. You are not allowed to inherit from two instantiations of the same generic type. For example, while class MyList implements MyCollection { //... }

    is OK, class MyList implements MyCollection, MyCollection { //... }

    is not. What is the reason for these restrictions? These restrictions largely arise from the way generics are implemented. By understanding the mechanism used to implement generics in Java, you can see where these restrictions come from and why they exist. Generics Implementation Generics is a Java language level feature. One of the design goals of generics was to keep binary compatibility at the byte code level. By requiring no change to JVM, and maintaining the same format of the class files (byte code), you can easily mix generics code and non-generics code. However, this comes at a price. You may end up loosing what generics are intended to provide in the first place type-safety. Does it matter that generics are at the language level and not really at the byte code level? There are two reasons to be concerned. One, if this is only a language level feature, what would happen if and when other languages are expected to run on the JVM? If the other languages to run on JVM are dynamic languages (Groovy, Ruby, Python, ), then it may not be a big deal. However, if you attempt to run a strongly typed language on JVM, this may be an issue. Second, if this is simply a language level features (one heck of a macro essentially), then it would be possible to pass in correct types at runtime, using reflection, for instance. Unfortunately, generics in Java does not provide adequate type-safety. It does not fully serve what it was created for.

  • Erasure So, if generics is a language level feature, what happens when you compile your generics code? Your code is striped out of all parametric types and each reference to parametric type is replaced with a class (typically Object or something more specific). This process is given a fancy name type erasure. According to the documentation The main advantage of this approach is that it provides total interoperability between generic code and legacy code that uses non-parameterized types (which are technically known as raw types). The main disadvantages are that parameter type information is not available at run time, and that automatically generated casts may fail when interoperating with ill-behaved legacy code. There is, however, a way to achieve guaranteed run-time type safety for generic collections even when interoperating with ill-behaved legacy code. While this provides interoperability with generic and non-generic code, it unfortunately compromises type-safety. Lets look at the effect of erasure on your code. Consider the example code: class MyList { public T ref; }

    By running javap c, you can look at whats in the byte code as shown below: javap -c MyList Compiled from "Test.java" class com.agiledeveloper.MyList extends java.lang.Object{ public java.lang.Object ref; com.agiledeveloper.MyList(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return The type T of the ref member of the class has been erased to (replaced by) type Object. Not all types are always erased to or replaced by Object. Take a look at this example: class MyList { public T ref; }

    In this case, the type T is replace by Vehicle as shown below: javap -c MyList Compiled from "Test.java"

  • class com.agiledeveloper.MyList extends java.lang.Object{ public com.agiledeveloper.Vehicle ref; com.agiledeveloper.MyList(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return

    Now consider the example: class MyList { public T ref; }

    Here the type T is replace by Comparable interface. Finally, if you use the multi-bound constraint, as in: class MyList { public T ref; }

    then the type T is replaced by Vehicle. The first type in the multi-bound constraint is used as the type in erasure. Effect of Erasure Lets look at the effect of erasure on a code that uses a generic type. Consider the example: ArrayList lst = new ArrayList(); lst.add(new Integer(1)); Integer val = lst.get(0);

    This is translated into: ArrayList lst = new ArrayList(); lst.add(new Integer(1)); Integer val = (Integer) lst.get(0);

    When you assign lst.get(0) to val, type casting is performed in the translated code. If you were to write the code without using generics, you would have done the same. Generics in Java, in this regards, simply acts as a syntax sugar. Where are we? We have discussed how Generics are treated in Java. We looked at the extent to which type-safety is provided. We will discuss some more issues related to generics in the next Part (Part III).

  • Conclusion Generics in Java were created to provide type-safety. They are implemented only at the language level. The concept is not carried down to the byte code level. It was designed to provide compatibility with legacy code. As a result, generics lack what they were intended for type-safety. References

    1. Generics in Java, Part-I at http://www.agiledeveloper.com/download.aspx (look at references in Part-I)

  • Generics in Java Part III Venkat Subramaniam

    [email protected] http://www.agiledeveloper.com/download.aspx

    Abstract In Part-I and II we discussed the benefits and usage of Java Generics, and how it is implemented under the hood. In this Part-III, we conclude with discussions on issues with mixing generic and non-generic (raw-type) code, and the issues of converting a non-generic legacy code to generics. Mixing Generic and non-generic code Lets consider the following example: import java.util.ArrayList; public class Test { public static void addElements(Collection list) { list.add(3); //list.add(1.2); } public static void main(String[] args) { ArrayList lst = new ArrayList(); addElements(lst); //lst.add(3.2); int total = 0; for(int val : lst) { total += val; } System.out.println("Total is : " + total); } }

    In the above example, lst refers to an instance of generic ArrayList. I am passing that instance to the addElements() method. Within that method, I add 3 to the ArrayList. Back in the main() main, I iterate though the ArrayList extracting one integer value at a time from it, and total it. The output from the above program is: Total is : 3

  • Now, in main(), if I uncomment the statement, lst.add(3.2);, I get a compilation error as shown below: Error: line (18) cannot find symbol method add(double) On the other hand, if I leave that statement commented, but uncomment the statement list.add(1.2); in the method addElements(), I dont get any compilation errors. When I run the program, however, I get a runtime exception as shown below: Exception in thread "main" java.lang.ClassCastException: java.lang.Double at Test.main(Test.java:21) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:585) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:78)

    What went wrong? In main() I am assuming that the ArrayList contains integer values. At runtime though, that assumption is proved wrong by the addition of the value 1.2 in the addElements() method. You may agree that getting a compile time error is better than getting a runtime error. However, Generics dont fully provide the type-safety they were intended to provide. If we are going to get runtime exception, it would be better to get that within the addElements() method, where we are adding the value 1.2 to the ArrayList, instead of in the main() when we are trying to fetch the elements out of the list. This can be realized by using Collections classs checkedList() method as shown below: //addElements(lst); addElements(Collections.checkedList(lst, Integer.class));

    The checkedList() method wraps the given ArrayList in an object that will ascertain that the elements added through it are of the specified type, in this case, Integer type. When I execute this program, I get the following runtime exception: Exception in thread "main" java.lang.ClassCastException: Attempt to insert class java.lang.Double element into collection with element type class java.lang.Integer at java.util.Collections$CheckedCollection.typeCheck(Collections.java:2206)

  • at java.util.Collections$CheckedCollection.add(Collections.java:2240) at Test.addElements(Test.java:11) at Test.main(Test.java:19) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:585) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:78)

    Compare this exception message with the previous one. The exception is reported in this case in line number 11 within the addElements() method instead of the previously reported line number 21 within the main() method. If you have to pass generic types to methods that accept non-generic types, consider wrapping the objects as shown in the above example. Converting non-generic code to generics In Part II we discussed the type erasure technique and saw how the parameterized types are converted to Object type or one of the types specified in the bound. If we have to convert from non-generic type to generic type, is it simply the question of adding the parameterized type E or replacing Object with E? Unfortunately, lifes not that simple. Consider the following example: import java.util.ArrayList; import java.util.Collection; public class MyList { private ArrayList list = new ArrayList(); public void add(Object anObject) { list.add(anObject); } public boolean contains(Object anObject) { if (list.contains(anObject)) return true; return false; } public boolean containsAny(Collection objects) { for(Object anObject : objects) {

  • if (contains(anObject)) return true; } return false; } public void addMany(Collection objects) { for(Object anObject : objects) { add(anObject); } } public void copyTo(MyList destination) { for(Object anObject : list) { destination.list.add(anObject); } } }

    MyList is a class that represents my own collection. Lets not get too technical about whether the addMany() method or the containsAny() method should actually belong to the class MyList. From the design point of view, if you think these should not belong here, they may belong elsewhere in a faade and the problems we will discuss will then extend to that class. Now, lets look at a sample code that uses this class: class Animal {} class Dog extends Animal { } class Cat extends Animal { } public class Test { public static void main(String[] args) { MyList lst = new MyList(); Dog snow = new Dog(); lst.add(snow); System.out.println("Does list contain my snow? "

    + lst.contains(snow)); Cat tom = new Cat(); lst.add(tom); System.out.println("Does list contain tom? "

    + lst.contains(tom)); } }

  • The above program produces the desired result as shown below: Does list contain my snow? true Does list contain tom? true

    Now, lets set out the change the MyList to use generics. The simplest solution modify Object with parameterized type E. Here is the result of that code change: import java.util.ArrayList; import java.util.Collection; public class MyList { private ArrayList list = new ArrayList(); public void add(E anObject) { list.add(anObject); } public boolean contains(E anObject) { if (list.contains(anObject)) return true; return false; } public boolean containsAny(Collection objects) { for(E anObject : objects) { if (contains(anObject)) return true; } return false; } public void addMany(Collection objects) { for(E anObject : objects) { add(anObject); } } public void copyTo(MyList destination) { for(E anObject : list) { destination.list.add(anObject); } } }

  • We modify the main() method to use the generic type (actually no change is needed to main() if you will continue to use the non-generic style). The only statement modified is shown below: MyList lst = new MyList();

    The program compiles with no error and produces the same result as before. So, the conversion from raw-type to generics went very well right? Lets ship it? Well, this hits right on the head with the issue of testing the code. Without good test, we would end up shipping this code, only to get calls from clients who write code like the following: Dog rover = new Dog(); ArrayList dogs = new ArrayList(); dogs.add(snow); dogs.add(rover); System.out.println(

    "Does list contain snow or rover? " + lst.containsAny(dogs));

    We get a compilation error: Error: line (29) containsAny(java.util.Collection) in MyList cannot be applied to java.util.ArrayList)

    Whats the fix? We need to tweak the containsAny() method a little to accommodate this reasonable call. The change is shown below: public boolean containsAny(Collection

  • myDogs.add(new Dog()); myDogs.copyTo(new MyList());

    In the above code, we are copying Dogs from one MyList to another MyList. Seems reasonable? Yep and it works. It is also legitimate to copy Dogs from a MyList to a MyList isnt it? After all, a list of Animals can hold Dogs. So, lets give that a shot: MyList myDogs = new MyList(); myDogs.add(new Dog()); myDogs.copyTo(new MyList()); This code, however, results in a compilation error as shown below: Error: line (36) copyTo(MyList) in MyList cannot be applied to (MyList)

    In this case, however, we do want a collection of base to be sent to this method. We have to tweak again, this time the copyTo() method. We want this method to accept a MyList of Dogs or MyList of Dogs base class. In general terms, we want it to accept MyList of the parameterized type or MyList of the parameterized types base type. So, here is the code for that: public void copyTo(MyList