Friday, March 14, 2008

Generics 101

­          -- With generics, we can create type-safe collections where more problems are caught at compile-time instead of runtime

­          -- With generics, we are prevented at compile time from putting the wrong type of object into a container

­          -- Without generics, the compiler would let us put any type of object into a collection

­          -- There are three important places where generics are used

o        Creating instances of generified classes

new ArrayList<String>()

o        Declaring and assigning variables of generic types

List<String> names = new ArrayList<String>()

o        Declaring and invoking methods that take generic types

void foo(List<String> list) {

      //method code

}

x.foo(names)

­          -- The java documentation defines the ArrayList as

public class ArrayList<E> extends AbstractList<E>

implements List<E> {

            public boolean add(E o) {

                  //method code

            }

            //other ArrayList methods

}

­          -- In the above definition "E" represents the type used to create an instance of ArrayList

­          -- "E" is just a stand-in for the type of element we want this collection to hold and return

­          -- For instance, if we declare the ArrayList as

ArrayList<String> thisList = new ArrayList<String>();

­          -- The compiler interprets it as

public class ArrarList<String> extends AbstractList<String>

                                    implements List<String> {

      public Boolean add(String o) {

      }

}

­          -- In other words, the "E" is replaced by the real type that is used when the ArrayList is created

­          -- A generic class means that the class declaration includes a type parameter

­          -- A generic method means that the method declaration uses a type parameter in its signature

­          -- When you declare a type parameter for the class, you can simply use that type any place that you'd use a real class or interface type

public class MyClass<E> extends MySuperClass<E> {

      //in the following method declaration "E" simply

//represents the type parameter defined in the

//class declaration above

      public void myMethod(E o) {

}

}

­          -- We can also define a type parameter as part of method definition, even if the type parameter was defined in the class declaration

public <T extends Animal> void takeThing(ArrayList<T> list) {

}

­          -- In the above example, "T" used as the type for argument ArrayList is the type defined in the method signature

­          -- When we say "extends" in the above code, it really means "extends or implements"

­          -- In generics, the keyword "extends" really means is-a and works for both classes and interfaces

­          -- The type specified to the right of "extends" can either be a class or an interface

­          -- If the class itself doesn't use a type parameter, we can still specify one for a method, by declaring it before the return type

public void takeThing(ArrayList<Animal> list) {

}

­          -- The above code is really different from the earlier one

­          -- In the first case, the type can be "Animal" or any subclass of "Animal"

­          -- In the second case, the type has to be strictly "Animal", using a subclass of "Animal" will cause a compiler error

­          -- If the compiler does allow an ArrayList of subclass – say Cat, of Animal to be passed in the second case, then it will cause problems if we try to add a Dog object – another subclass of Animal, to the ArrayList passed as parameter

­          -- If the parameter were an array of "Animal" objects, then we can very well pass an array of "Cat" objects to the method

­          -- This is because Array types are checked again at runtime, but collection type checks happen only at compile time

­          -- Hence if we try to add a Dog object to a Cat Array passed as parameter, the compiler will throw a runtime error

­           

­          -- If we do need to make the method argument accept a subclass of the given type, we can use a wildcard

public void takeThing(ArrayList<? extends Animal> animals) {

}

­          -- Now we can very well pass an ArrayList of Cat to the method

­          -- But when we use a wildcard in the method argument, the compiler will not let us add any element to the list

­          -- We can call other methods on the argument but we can't add a new element to the ArrayList

public void takeThing(ArrayList<? extends Animal> animals) {

      for(Animal a : animals)

            a.eat();          à this works

      a.add(new Cat());       à this won't work

}

­          -- Using the wildcard is the same as using the following

public <T extends Animal> void takeThing(ArrayList<T> list) {

}

No comments: