
Understanding Java Generics for Efficient Code
Delve into the world of Java Generics and discover the benefits they offer in simplifying code, reducing errors, and enhancing type safety. Learn why Generics were introduced in Java, how they allow for cleaner code, and their role in creating flexible, type-safe containers. Explore the differences between ArrayList and ArrayList<E>, along with examples and illustrations to deepen your understanding.
Download Presentation

Please find below an Image/Link to download the presentation.
The content on the website is provided AS IS for your information and personal use only. It may not be sold, licensed, or shared on other websites without obtaining consent from the author. If you encounter any issues during the download, it is possible that the publisher has removed the file from their server.
You are allowed to download the files provided on this website for personal or commercial use, subject to the condition that they are used lawfully. All files are the property of their respective owners.
The content on the website is provided AS IS for your information and personal use only. It may not be sold, licensed, or shared on other websites without obtaining consent from the author.
E N D
Presentation Transcript
Generics We have already seen Generics, let s examine why we use them In using an ArrayList, we specify <Type> to indicate the type of Object that the ArrayList will store this is known as a Generic Type, or Generics <Type> is known as the type parameter the <> is sometimes called the diamond notation Generics were first introduced in Java with version 1.5 They are roughly based on a C++ concept called templates We do not necessarily need Generics as instead we could always declare a variable to be of type Object, but then we would have to perform casting or downcasting of the object, Generics is cleaner
ArrayList We have seen that the ArrayList can be used to store any type of Object We can either declare an ArrayList without a specific type (this would be using the ArrayList pre Java 1.5) or we can declare the ArrayList using Generics with Generics, it cuts down on the need to perform casting ArrayList names=new ArrayList( ); String temp=(String) names.get(i); Without the cast, we get syntax errors and even with the cast we may get a compiler warning (requiring that we use @Suppresswarnings) By creating a Generic type, we avoid having to cast the result ArrayList<String> s=new ArrayList<String>( ); s.add( Susan ); String s2 = s.get(0);
Declaring a Generic Type We allow the user program to specify the type to be used for a container class so that, when the container class itself is written, there is no commitment to the type being stored (as long as the type is an object of some kind) ArrayList<Integer> myints=new ArrayList<Integer>(); ArrayList<Tank> mytanks=new ArrayList<tank>(); Generics permit stronger type checking to reduce the number of possible run-time errors that might arise when the user is dealing with Objects We can also write our own Generic classes We will also be examining several Java container classes (in the next couple of chapters) that were defined using Generics to give us greater flexibility in their usage
Comparing ArrayList and ArrayList<E> java.util.ArrayList +ArrayList() +add(o: Object) : void +add(index: int, o: Object) : void +clear(): void +contains(o: Object): boolean +get(index: int) : Object +indexOf(o: Object) : int +isEmpty(): boolean +lastIndexOf(o: Object) : int +remove(o: Object): boolean +size(): int +remove(index: int) : boolean +set(index: int, o: Object) : Object java.util.ArrayList<E> +ArrayList() +add(o: E) : void +add(index: int, o: E) : void +clear(): void +contains(o: Object): boolean +get(index: int) : E +indexOf(o: Object) : int +isEmpty(): boolean +lastIndexOf(o: Object) : int +remove(o: Object): boolean +size(): int +remove(index: int) : boolean +set(index: int, o: E) : E (b) ArrayList in JDK 1.5 (a) ArrayList before JDK 1.5
Defining a Generic Class To indicate that a class we are writing is generic, add <placeholder> to the class header after the class name The placeholder is usually a single letter, using the following naming convention E used with Java container types like ArrayList K, V used for Key and Value (when you have a Key-Value pair) N used if the type is to be restricted to a numeric type of Object T generic Type Example: public class MyGenericClass<T> { } In your class, use T as the type to declare whatever instance datum is necessary private T foo; To create a variable of a Generic class, you must the type in the declaration as in MyGenericClass<Integer> myInt;
Simple Example: Boxing Recall that for all primitive types, we have equivalent Classes that box the type so that we can treat a datum of a primitive type as an Object Let s create a generic Box class to box any Object We might do this so that the programmer who wishes to utilize different types of objects will not have to use casts What should a Boxing class have? It needs to store the object itself We need an accessor (get) and mutator (set) We might also implement a toString
public class Box<T> { Our box class is very basic private T item; The type of Object is recorded as T (filled in when you declare a variable of type Box) public Box(T item) { this.item=item; } public T get() { return item; } T is used to specify the type for item when declared as an instance datum, or passed as a parameter to a method or returned from a method public void set(T item) { this.item=item; } public String toString() { if(item!=null) return ""+item; else return "not set"; } } Note that T needs to have a toString implemented or this returns the address of item
public class BoxUsers { public static void main( String[] args) { Box<String> a; Box<Integer> b; Box<Double> c; Box<Object> d=null; a=new Box<>("hi there"); b=new Box<>(100); c=new Box<>(100.1); System.out.println(a.get()); a.set("bye bye"); c.set(c.get()+1); System.out.println(a); System.out.println(c); System.out.println(d); } } What if we want to do c.set(b.get( )+1); This yields an error because b.get( ) returns an Integer and c.set expects a Double, so instead use c.set(new Double(b.get( )+1)); Output: hi there bye bye 101.1 null
import java.util.*; public class GenericStack<E> { private ArrayList<E> stack; public GenericStack(){ stack=new ArrayList<E>(); } public int getSize() { return stack.size(); } public E peek() { return stack.get(stack.size()-1); } public E pop() { E returnItem=stack.get(stack.size()-1); stack.remove(stack.size()-1); return returnItem; } public void push(E newItem) { stack.add(newItem); } public boolean isEmpty() { return stack.isEmpty(); } } A Generic Stack Class GenericStack<String> stack= new GenericStack<>(); stack.push( string1 ); stack.push( string2 );
Bounded Generic Classes We can restrict the types acceptable in a Generic class by bounding the generic class using public class Name<placeholder extends Type> Where Type is the type we want to restrict our Generic classes to, such as Number We might want a Box-like generic which is limited to Numeric Objects we would employ <T extends Number> public class NumericBox<T extends Number> { T item; public NumericBox(T item){this.item=item;} public boolean bigger(T item2){ if(item.doubleValue()>item2.doubleValue()) return true; else return false; } }
Example: Unique Pair Class Let s expand on the Box example by implementing a Generic class which stores 2 items of the same type of Object In this case, we want to make sure that the two items (instance data) are unique Arbitrarily, we will decide that if the two items are equal (as determined by Comparable), the second of the two will be set to null To test for uniqueness, we need to have Comparable implemented on the class of the Objects How can we ensure that the class being utilized implements Comparable? We need to state that the type, T, implements Comparable by specifying <T extends Comparable<T>>
public class UniquePair<T extends Comparable<T>> { private T item1, item2; public UniquePair(T i1, T i2){ item1=i1; item2=i2; if(item1.compareTo(item2)==0) item2=null; } public T get1() { return item1; } public T get2() { return item2; } public void set1(T i1) { item1=i1; if(item1.compareTo(item2)==0) item2=null; } public void set2(T i2){ item2=i2; if(item1.compareTo(item2)==0) item2=null; } } UniquePair<Integer> a=new UniquePair<>(new Integer(10), new Integer(20)); a.set1(new Integer(20)); results in object a storing (20, null)
Generic Classes with More than 1 Type UniquePair had 2 instance data of the same type What about a Generic class of multiple types? The common example is a Key-Value pair, sometimes called a tuple The idea is that we are storing an attribute s name and the value of that attribute a person can be described using multiple key-value pairs such as Name: Frank Zappa , Occupation: Musician , Height: 70 inches , etc Let s implement a Key-Value pair which has methods to determine if another Key-Value pair has the same Key and Value Through Comparable, we compare both Keys and both Values
public class KeyValuePair<K extends Comparable<K>,V extends Comparable<V>> { private K key; private V value; public KeyValuePair(K k2, V v2) {key=k2;value=v2;} public K getKey(){return key;} public V getValue(){return value;} public void setKey(K k2){key=k2;} public void setValue(V v2) {value=v2;} @SuppressWarnings("unchecked") public boolean equal(KeyValuePair kvp){ if(key.compareTo((K)kvp.getKey())==0&& value.compareTo((V)kvp.getValue())==0) return true; else return false; } } NOTE: The Java compiler does not like the compareTo operations and so we have to suppress a warning to ensure proper compilation
@SuppressWarnings("unchecked") public static void main(String[] args){ KeyValuePair<String,Integer> k1=new KeyValuePair("Height", 71); KeyValuePair<String,Integer> k2=new KeyValuePair("Height", 69); System.out.println(k1.equal(k2)); } Here, the Java compiler does not like the equal operation and so we have to suppress a warning to ensure proper compilation Let s take a closer look at the class header Aside from <K, V> we say that both extend Comparable and add Comparable<K> and Comparable<V> so that compareTo expects a KeyValuePair<K,V> public class KeyValuePair<K extends Comparable<K>,V extends Comparable<V>>
Raw Types In some cases, you can omit the <Type> when declaring a variable of a Generic type this is known as a raw type ClassName variable=new Classname(); This is available for Generic classes which were originally defined in Java prior to the inclusion of Generics in Java (pre Java 1.5), such as ArrayList or classes that implement a Generic interface like Comparable This allows us to maintain backward compatibility Raw types are unsafe and result in unchecked warnings generated by the Java compiler resulting in the program not compiling You can use a different compiler setting to avoid unchecked warnings We can also avoid the unchecked warnings by suppressing them by placing the following compiler directive before any method that might cause this warning @Suppresswarnings( unchecked )
Implementing Comparable Interfaces themselves can be generic when written If we want our Generic class to implement an interface, then we have to make sure we implement the proper method(s) for the interface Let s implement Comparable for our Box class If we have two Box objects, compare them using compareTo The problem is that we can t just implement our own compareTo method that somehow tests the internal portions of our Object because we may not have access to those Objects instance data But as we saw, we can ensure that the Objects are Comparable by using <T extends Comparable<T>> Since T is Comparable, we can then implement our own compareTo method which calls T s compareTo method
Implementing a Comparable Box The class header is now more complicated as not only does T extend Comparable, but this class implements Comparable<T> public class ComparableBox<T extends Comparable<T>> implements Comparable<T> We have to implement compareTo recall compareTo expects to receive an Object but this will be utilized in a setting like this: ComparableBox<T> foo.compareTo(ComparableBox<T> bar) we have to strip out the T object from bar before we can compare it to foo s T object and so we wind up writing two compareTo methods
public class ComparableBox<T extends Comparable<T>> implements Comparable<T> { T item; // ... as before public int compareTo(ComparableBox<T> item2) { return compareTo(item2.get()); } } @Override public int compareTo(T item2) { return item.compareTo(item2); } Invoked by code like this: ComparableBox<String> a=new ComparableBox<>("hi there"); ComparableBox<String> b=new ComparableBox<>("bye bye"); System.out.println(a.compareTo(b));
A Generic Matrix Class The book covers a Generic Matrix Class A Matrix is a 2-D array The type of value to be stored will be Generic We often perform arithmetic operations on matrices so we will restrict the Generic type to extend Number The author implements GenericMatrix as an abstract class requiring that subclasses implement the abstract methods Why? Some operations will have to be implemented differently based on type So for instance, an Integer version might implement an add method one way while a RationalNumber version would use a different approach But at the same time, other operations like copyMatrix and printMatrix can be implemented strictly with the Generic type see details on pages 785-789
Generic Methods We can write static methods which receive a Generic type of parameter rather than a specifically typed parameter We could also receive an Object, but that may require casting and if we need to use an object of the same type in the method, we wouldn t know which type to use or to cast Structure of a Generic method header: public static <type> returntype name(params){ } To invoke a Generic method, much like creating a variable of a Generic class, you need to specify the type in the method call as in SomeClass.<type>methodName(params); As with Generic classes, you can bound the type using <Type extends Class> and use multiple types as in <String,Integer>
Generic Selection Sort This selection sort can be called with any array of some Comparable Objects whether that is String, Character, Integer, Double, etc public static <E extends Comparable<E>> void sort(E[] list) { E min; int minIndex; for(int i=0;i<list.length-1;i++) { min=list[i]; minIndex=i; for(int j=i+1;j<list.length;j++) { if(min.compareTo(list[i]>0) { min=list[j] minIndex=j; } } list[minIndex]=list[i]; list[i]=min; } } Assume this is placed in a class called GenericSortClass and we have an Object of this type called foo Call the function with foo.<Integer>sort(intArray); Or from within GenericSortClass as sort(intArray);
Wildcard Types Recall the NumericBox bounded our Box s Object to be a subclass of Number, but we don t have to resort to bounding the class itself Instead, we can place a bound on the parameter type allowed for a generic method Let s see how to create a greaterThan method for two Boxes where we expect the Box types to be Numeric (e.g., Box<Number>) We employ another Generic mechanism called a wildcard wildcards are only allowed in static methods As a static Generic method will only operate on parameters and not non-static instance data, we will pass both objects to be operated on to this method
Enhancing Box with a Wildcard First, we go back to our Box class without having <T extends Number> So this is just an ordinary Box class in which any Object type can be used to declare an object such as Box<String> b1; Box<Integer> b2; Box<Student> b3; // assumes we have a Student class But if we want to implement a greaterThan static method, we need to make sure that the type (String, Integer, Student) is Comparable in our case we will restrict it even further to be a Number type So our greaterThan static method will bound the type and not the Box class itself
Bounding with a Wildcard The notation for our static method is similar to our previous example of a static method (although in this case, we are receiving 2 parameters, not one) Instead of the two Box parameters being <T>, we use <? extends Number> to indicate any class/subclass of Number public static <T> boolean greater(Box<? extends Number> item1, Box<? extends Number> item2) if(item1.get().doubleValue()>item2.get().doubleValue()) return true; else return false; } (any numeric type can be converted to a double) { we use double since it is the widest numeric type Box<String> a=new Box<>("Hi there"); Box<Integer> b=new Box<>(12); Box<Double> c=new Box<>(11.5); System.out.println(Box.greater(a,c)); System.out.println(Box.greater(b,c)); Causes a syntax error because object a (Box<String>) is not a subtype of Number
Another Example Returning to our previously written GenericStack, we create a multiply method We can only multiply Numbers We will multiply pairwise values of two GenericStacks, adding each product together and return the result as a double since double is the widest type of Number public static <E> double multiply(GenericStack<? extends Number> s1, GenericStack<? extends Number> s2) { double temp=0; int smaller=s1.getSize(); if(s2.getSize()<smaller) smaller=s2.getSize(); for(int i=0;i<smaller;i++) temp=s1.pop().doubleValue()*s2.pop().doubleValue(); return temp; }
Continued Now let s write a method to multiply all pairs of the Stack, creating a new Stack (e.g., top element of the new Stack is the top of s1 * top of s2, etc, the method will return a Stack Our method header would look like this: public static <E> GenericStack<sometype> multiplyStacks (GenericStack<? extends Number> s1, GenericStack<? extends Number> s2) But what type of GenericStack should the method return? What Java does not allow is a return type of ObjectType<?> - that is, we cannot use the wildcard character in the return type anywhere, we must specify an exact type
Continued For our return type, let s re-examine the previous multiply method We used double as the return type, why? Because it is the widest numeric type (aside from BigInteger and BigDecimal) No matter what numeric type is used for s1 and s2, we can coerce them into doubles So we will use GenericStack<Double> as our return type public static <E> GenericStack<Double> multiplyStacks (GenericStack<? extends Number> s1, GenericStack<? extends Number> s2) { GenericStack<Double> temp=new GenericStack<>(); int smaller=s1.getSize(); if(s2.getSize()<smaller) smaller=s2.getSize(); for(int i=0;i<smaller;i++) temp.push(s1.pop().doubleValue()*s2.pop().doubleValue()); return temp; }
More on Wildcards If you want to reference any type of object, use <?> known as an unbounded wildcard If you want to reference any type of object at or lower than a given class, use <? extends class> known as an upper bound note that <?> is equal to <? extends Object> If you want to reference any type of object at or higher than a given class, use <? super class> known as a lower bound you would use a lower bound if there were specific subclasses that you wanted to prohibit from being usable as parameters Note that while parameters of a generic method can use wildcards, if the method is to return a generic type, it must be specified (e.g., as T) and not use a wildcard
Type Erasure Generic types are filled in by the compiler as the compiler converts your Java code Thus, at run time, all Generic types are actually converted into raw types This is because you must specify the type to be used in a Generic class when you declare your variable Box<Integer> b1 = causes a Box type to be implemented with Integer as the type (for all occurrences in Box of T) Box<String> b2 = causes a Box type to be implemented with String Once compilation is over, the generic aspect of the class is erased, now the object contains a specific type Thus, you cannot apply a generic at run time, only at compile time This idea is known as type erasure
Restrictions on Generics You cannot create an instance of a variable using a Generic type using new as in E foo=new E( ); You will only be able to assign foo to a parameter of type E as in public MyClass(E bar) {E foo=bar;} You cannot create an array of a Generic type as in E[ ] foo=new E[100]; There are ways to get around this, for instance creating an ArrayList of type E as in ArrayList<E> foo=new ArrayList<>(); or by creating an array of objects and casting as in E[] foo=(E[])new Object[100]; although this solution causes an unchecked warning
Restrictions Continued You cannot create a Generic using a primitive type as in Box<int> b1= When using a generic method, since it has to be static, it cannot access non-static members of your class Instead, think of the method as only operating on parameters Similarly, you cannot declare a static variable to be of a Generic type You cannot use casts or instanceof on a parameterized type You cannot create parameterized Exceptions you can use a parameterized type in a throws clause You cannot overload methods with parameterized types