GENERICS AND ARRAY-BASED LISTS

TOPICS


  • (Java) Interface: Cloneable
  • (Java) Interface: Comparable
  • Primitive Data Types and Wrapper Classes
  • Generics
  • ADT: Abstract Data Type
  • Array-Based Lists
  • ADT: Unordered List (Array-Based Implementation)
  • ADT: Ordered List (Array-Based Implementation)
  • (Java) Class: ArrayList

  • OUTLINE


    1.  Interface: Cloneable
     
  • Reminder: inheritance, interfaces, class Object.
  • The method clone of the class Object is a protected method inherited by every class in Java. The method clone cannot be invoked by an object outside the definition of its class.
  • It provides a bit-by-bit copy of the objectís data in storage (shallow copy of object's data) --> In order to make a deep copy of an objectís data, its class must override the clone method from class Object.
  • NOTE: The interface Cloneable  has no method headings that need to be implemented --> Classes that implement this interface must only redefine the clone() method.
  • Example:
  • public class Time implements Cloneable {...}
  • When writing the clone method, follow these steps:
  • NOTE: The method clone of the class Object throws CloneNotSupportedException
  • Example: Same method clone() in class Time, Date, Person
  • public Object clone() {
        try  {
            return super.clone();
        }
        catch (CloneNotSupportedException e) {
            return null;
        }
    }
  • NOTE: The method clone returns a reference of the type Object or the value null --> must typecast the returned object to a reference of the same type as the class you work with.
  • Example:
  • Time t1 = new Time(11, 12, 13);
    Time t2 = (Time) t1.clone();
  • If the class uses composition (has instance variables of type class), then the clone method has to change the values of those instance variables.
  • Example: method clone() in class PersonalInfo
  • public Object clone() {
        try {
            PersonalInfo copy = (PersonalInfo) super.clone();
            copy.birthDate = (Date) birthDate.clone();
            copy.fullName = (Person) fullName.clone();
            return copy;
        }
        catch (CloneNotSupportedException e) {
            return null;
        }
    }

    2.  Interface: Comparable
     
  • The interface Comparable has only one method heading, which is compareTo(not a member of Object) --> Used to force a class to provide an appropriate definition of the method compareTo. It provides a means of fully ordering objects.
  • Values of two objects of the same class can be properly compared (equals compares only for equality) Many types have the notion of a natural ordering that describes whether one value of that type is "less than" or "greater than" another:  numeric values, strings (lexical/alphabetical order), times, dates, etc. Not all types have a natural ordering (ex: Point)
  • NOTE: The interface Comparable  has no method headings that need to be implemented --> Classes that implement this interface must only redefine the compareTo() method.
  • The method compareTo compares this object with the object passed for order. Returns a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the object passed.
  • Example:
  • NOTE: If a class implements multiple interfaces, separate all interfaces names using commas.
  • Example:
  • Example: method compareTo() in class Time:
  • public int compareTo(Object othertime) {
        Time temp = (Time) othertime;
        int hrDiff = hrs - temp.hrs;
        if (hrDiff != 0)
            return hrDiff;
        int minDiff = mins - temp.mins;
        if (minDiff != 0)
            return minDiff;
        return secs - temp.secs;
    }
  • Example: method compareTo() in class Date:
  •  public int compareTo(Object otherDate) {
        Date temp = (Date) otherDate;
        int yearDiff = year - temp.year;
        if (yearDiff != 0)
            return yearDiff;
        int monthDiff = month - temp.month;
        if (monthDiff != 0)
            return monthDiff;
        return day - temp.day;
    }
  • Example: method compareTo() in class Person:
  • public int compareTo(Object otherPerson) {
        Person temp = (Person) otherPerson;
        int compare = lastName.compareTo(temp.lastName);
        if(compare == 0)
            compare = firstName.compareTo(temp.firstName);
        return compare;
    }
  • Example: method compareTo() in class PersonalInfo:
  • public int compareTo(Object other) {
        PersonalInfo temp = (PersonalInfo) other;
        int compare = personID - temp.personID;
        if(compare == 0)
            compare = fullName.compareTo(temp.fullName);
        if(compare == 0)
            compare = birthDate.compareTo(temp.birthDate);
        return compare;
    }
  • Check here the (complete) new definition for class Time. (including clone() and compareTo())
  • Check here the (complete) new definition for class Date. (including clone() and compareTo())
  • Check here the (complete) new definition for class Person. (including clone() and compareTo())
  • Check here the (complete) new definition for class PersonalInfo. (including clone() and compareTo())


  • 3. Primitive Data Types and Wrapper Classes
     
  • Definition: Wrapper Classes = Classes Integer, Double, Character, Long, Float, Boolean, etc, provided so that values of primitive data types can be treated as objects.
  • A wrapper is an object whose sole purpose is to hold a primitive value.

  •  
    Primitive type Wrapper class
    int Integer
    double Double
    float Float
    char Character
    boolean Boolean
    long Long
  • The Integer class wraps a value of the primitive type int in an object. An object of type Integer contains a single instance variable whose type is int. The class provides several methods for converting an int to a String and a String to an int, as well as other methods useful when dealing with an int. Take a look at some methods in class Integer
  • Definition: The  conversion between the primitive type and the wrapper class = boxing and unboxing.
  • As of Java Standard Edition 5.0, Java automatically converts values back and forth between the primitive type and the corresponding wrapper class = auto-boxing and auto-unboxing.
  • How auto-boxing works:  if an int is passed in a place where an Integer is required, the compiler will make a (behind the scenes) call to the wrapper class constructor (Integer).
  • How auto-unboxing works:  if an Integer is passed in a place where an int is required, the compiler will make a (behind the scenes) call to the intValue method.
  • Similar behavior takes place for the 7 other primitive types/wrapper classes pairs.
  • Example:

  •  
    Auto-boxing (of the int type) Auto-unboxing (of the Integer type)
    int x; 
    Integer num; 

    num = 10; //auto boxing

    Equivalent to the statement:
    num = new Integer(10);

    int x; 
    Integer num; 

    x = num; //auto-unboxing

    Equivalent to the statement:
    x = num.intValue(); 

  • When to use auto-boxing and auto-unboxing? Only when there is a mismatch between reference types and primitives (for example, when you have to put numerical values into a collection - to be discussed later). It is not appropriate to use them for scientific computing, or other performance-sensitive numerical code. An Integer is not a perfect substitute for an int; auto-boxing and unboxing blur the distinction between primitive types and reference types, but they do not eliminate it (for example, the wrapper classes have limitations - cannot change the value stored in an object). Although boxing and unboxing can be quite convenient, this feature can generate confusion and should be used with care.


  • 4. Generics
     
  • One important goal of OOP is to provide the ability to write reusable, generalized code. One important mechanism that supports this goal = generics. The idea: if the implementation is identical except for the basic type of the object --> use a generic implementation to describe the basic functionality. Example: When sorting an array, the logic in bubble sort is independent of the types of objects being sorted. Same for searching, inserting, deleting, etc.
  • Java 5 supports generic methods and generic classes. Writing generic classes requires more work - the constructs of the language are quite complex (and sometimes tricky!)
  • Definition: Generics =  powerful means of writing generalized code that can be used by any class in any hierarchy represented by the type parameter. Class definitions that include a type parameter are called generic types.
  • Definition: Type parameter = identifier that specifies a generic type name. Any non-keyword identifier can be used for the type parameter, but by convention, the parameter starts with an uppercase letter. The type parameter can be used like other types used in the definition of a class, but cannot represent primitive types (see the note below). Also known as type variables - used to:
  • Declare the return type of the method
  • Declare formal parameters
  • Declare local variables
  • NOTE:  A type parameter cannot be used everywhere a type name can be used. In particular, the type parameter cannot be used in simple expressions using new to create a new object. Example: T someObject = new T();

  • T[] someArray = new T[SIZE]; --> Compiling ERROR!
  • NOTE:  A primitive type cannot be plugged in for a type parameter --> the type plugged in for a type parameter must always be a reference type. However, Java has automatic boxing so, for wrapper classes this is not a big restriction (use wrapper classes instead of primitive data types!). Also, reference types can include arrays.
  • Definition: Generic method = Method defined using type parameter. The type list precedes the return type. The type is used as the return type, OR the type is used in more than one parameter, OR the type is used to declare a local variable. The general syntax for a generic method is:  modifier(s) <T> methodType methodName(formal parameters) {...}
  • Examples:
  • //generic method to print array
    public static <T> void print(T[] list) {
        for(int i = 0; i < list.length; i++)
            System.out.print(list[i] + " ");
        System.out.println();
    }
    Calls:

    Integer[] intList = {1, 2, 3, 4, 5};
    Double[] doubleList = {1.1, 2.2, 3.3};
    String[] strList = {"List", "Stack", "Queue", "Tree"};

    print(intList);
    print(numList);
    print(strList);

    //generic method for linear search
    public static <T> int seqSearch(T[] list, T searchItem) {
        for(int i = 0; i < list.length; i++)
            if(list[i].equals(searchItem))
                return i;
        return -1;
    }

  • Generic methods and bounded type parameters --> There are situations when the type parameter <T> must be restricted. For example: we plan to design a method to find the larger value of two objects. The method should work with built-in as well as user-defined classes. Objects are compared using the method compareTo --> the method should work only with classes that provide a definition of the method compareTo.
  • Examples:
  • NOTE:  Always use the keyword extends regardless of whether the type parameter extends a class or an interface
  • NOTE:  If a type parameter is bounded by more than one class (or interface) class names are separated using the symbol &
  • Definition: Generic class (parameterized or parametric class) = a class that is defined with a parameter for a type. The type parameter is included in angular brackets < > after the class name in the class definition heading. The general syntax for a generic class is:  modifier(s) className <T> modifier(s) {...}
  • Generic classes are used to write a single definition for a set of related classes.
  • NOTE:  A class definition with a type parameter is stored in a file and compiled just like any other class. Once a parameterized class is compiled, it can be used like any other class. However, the class type plugged in for the type parameter must be specified before it can be used in a program.
  • NOTE:  An instantiation of a generic class cannot be an array base type --> use an ArrayList instead.
  • NOTE: A generic class definition can have any number of type parameters --> multiple type parameters are listed in angular brackets just as in the single type parameter case, but are separated by commas.


  • 5.  Abstract Data Types (ADT)
     
  • Definition: Abstract Data Type (ADT) = A data type that specifies the logical properties without the implementation details (information hiding --> the ADT hides the implementation details from the programs using the ADT --> the users can use the operations of an ADT without knowing how they have been implemented).
  • ADT = a collection of data and a set of operations on that data (while a data structure is a construct within a programming language that stores a collection of data). Describes what the collection does, not how it does it.
  • Data abstraction  is the result of ADT operations between data structures and the program that accesses the data within these data structures. The goal of data abstraction is to separate the operations on data from the implementation of these operations. A program should not depend on the details of an ADT implementation because ADT's properties (domain and operations) are specified independently of any particular implementation.
  • An ADT separates the logical properties of a data type (what are the possible values, what operations will be needed) from its implementation. In other words, data abstraction separates the qualities of an object from the details of how it works.
  • ADT properties:
  • Set of values (domain)
  • Allowable operations on those values.

  • 9. The (Java) ArrayList Class
     
  • Although arrays are conceptually important as a data structure, they are not used as much in Java as they are in most other languages.  The reason is that the java.util package includes a class called ArrayList that provides the standard array behavior along with other useful operations.
  • ArrayList is a class in the standard Java libraries that can hold any type of object --> In general, an ArrayList serves the same purpose as an array, except that an ArrayList can change length while the program is running (unlike arrays, which have a fixed length once they have been created).
  • The type you specify when creating an ArrayList must be an object type; it cannot be a primitive type --> must use wrapper classes!
  • Each object of the ArrayList class has a capacity. The capacity is the size of the array used to store the elements in the list (it is always at least as large as the list size.) As elements are added to an ArrayList, its capacity grows automatically.
  • The main difference between Java arrays and ArrayList --> ArrayList is a Java class rather than a special data type in the language.  As a result, all operations on ArrayLists are indicated using method calls.  For example, the most obvious differences include:
  • Why use an array instead of an ArrayList:
  • Why use an ArrayList instead of an array?
  • NOTE: In order to make use of the ArrayList class, must add:  import java.util.ArrayList;
  • NOTE: The Java standard libraries have a class named Vector that behaves almost exactly the same as the class ArrayList. In most situations, either class could be used, however the ArrayList class is newer (Java 5), and is becoming the preferred class.
  • Some methods in the ArrayList class:
  • Much more in detail (source:http://java.sun.com/javase/6/docs/api/ ) a more extensive list of methods for the ArrayList class (using generics):

     boolean add(E e) -->  Appends the specified element to the end of this list.
     void add(int index, E element) -->  Inserts the specified element at the specified position in this list.
     boolean addAll(Collection<? extends E> c) --> Appends all of the elements in the specified collection to the end of this list, in the order that they are returned by the specified collection's Iterator.
     boolean addAll(int index, Collection<? extends E> c) -->  Inserts all of the elements in the specified collection into this list, starting at the specified position.
     void clear() -->  Removes all of the elements from this list.
     Object clone() -->  Returns a shallow copy of this ArrayList instance.
     boolean contains(Object o) -->  Returns true if this list contains the specified element.
     void ensureCapacity(int minCapacity) -->  Increases the capacity of this ArrayList instance, if necessary, to ensure that it can hold at least the number of elements specified by the minimum capacity argument.
     E get(int index) -->   Returns the element at the specified position in this list.
     int indexOf(Object o) -->  Returns the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element.
     boolean isEmpty() -->   Returns true if this list contains no elements.
     int lastIndexOf(Object o) -->   Returns the index of the last occurrence of the specified element in this list, or -1 if this list does not contain the element.
     E remove(int index) --> Removes the element at the specified position in this list.
     boolean remove(Object o) --> Removes the first occurrence of the specified element from this list, if it is present.
     protected  void removeRange(int fromIndex, int toIndex) --> Removes from this list all of the elements whose index is between fromIndex, inclusive, and toIndex, exclusive.
     E set(int index, E element) --> Replaces the element at the specified position in this list with the specified element.
     int size() --> Returns the number of elements in this list.
     Object[] toArray() --> Returns an array containing all of the elements in this list in proper sequence (from first to last element).
     <T> T[] toArray(T[] a) --> Returns an array containing all of the elements in this list in proper sequence (from first to last element); the runtime type of the returned array is that of the specified array.
     void trimToSize() --> Trims the capacity of this ArrayList
     

  • NOTE: When looking at the methods available in the ArrayList class, there appears to be some inconsistency - in some cases, when a parameter is naturally an object of the base type, the parameter type is the base type; however, in other cases, it is the type Object. Why? Because the ArrayList class implements a number of interfaces, and inherits methods from various superclasses - they specify that certain parameters have type Object.
  • NOTE: The ArrayList class is an example of a collection class (to be discussed later). Starting with version 5.0, Java has added a new kind of for loop called a for-each or enhanced for loop. This kind of loop has been designed to cycle through all the elements in a collection (like an ArrayList).
  • Example #1:

  • import java.util.ArrayList;
    public class ExampleForEach implements Cloneable{
        public static void main(String[ ] args){
            ArrayList<Integer> list = new ArrayList<Integer>();
            ArrayList<Integer> copyList = new ArrayList<Integer>();
            list.add(1);
            list.add(2);
            list.add(3);
            System.out.println("The original list:");
            //Reads like: "for each Integer i, in list"
            for(Integer i:list)
                System.out.print(i + " ");
            //copyList = (ArrayList)list.clone();
            for(int i = 0; i < copyList.size( ); i++)
                copyList.set(i, new Integer(list.get(i)));
            System.out.println("\nThe clone list:");
            //Reads like: "for each Integer i, in copyList"
            for(Integer i:copyList)
                System.out.print(i + " ");
            System.out.println();
        }
    }
    Output:
    The original list:
    1 2 3
    The clone list:
    1 2 3
  • Example #2:

  • import java.util.ArrayList;
    public class ExampleArrayList implements Cloneable{
        public static void main( String[ ] args) {
            ArrayList<Integer> list = new ArrayList<Integer>(); //initial size = 0
            ArrayList<Integer> copyList = new ArrayList<Integer>(); //initial size = 0
            System.out.println("Size of original(empty) list = " + list.size());
            for (int i = 1; i <= 10; i++)
                list.add(2 * i);
            System.out.println("Added 10 values to list. The new size is: " + list.size());
            System.out.println("The list is " + list);
            list.set(0, 59);
            System.out.println("Changed the element with index 0 to 59.");
            System.out.println("The new list is " + list);
            list.add(3, 37);
            System.out.println("Added 37 at index 3. Increased capacity! The new size is: " + list.size());
            System.out.println("The new list is " + list);
            list.remove(6);
            System.out.println("Removed element with index 6. The new size is: " + list.size());
            System.out.println("The new list is " + list);
            list.remove((Integer)(6));
            System.out.println("Removed the value 6. The new size is: " + list.size());
            System.out.println("The new list is " + list);
            System.out.println("Printing the list using .get/for loop. The list is: ");
            for (int i = 0; i < list.size(); i++)
                System.out.print(list.get(i) + " " );
            if (list.contains(10))
                System.out.println("\nLooked for value 10 / list. 10 found at index " + list.indexOf(10));
            else
                System.out.println("Looked for 10/list. 10 NOT found.");
            copyList = (ArrayList)list.clone(); //shallow copy
            //make a deep copy
            for(int i = 0; i < copyList.size( ); i++)
                copyList.set(i, new Integer(list.get(i)));
            System.out.println("\nThe clone list (using for each):");
            for(Integer i:copyList)
                System.out.print(i + " ");
            System.out.println("\nThe clone list (using toString: \n" + copyList);
            System.out.println();
        }
    }
    OUTPUT:
    Size of original(empty) list = 0
    Added 10 values to list. The new size is: 10
    The list is [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
    Changed the element with index 0 to 59.
    The new list is [59, 4, 6, 8, 10, 12, 14, 16, 18, 20]
    Added 37 at index 3. Increased capacity! The new size is: 11
    The new list is [59, 4, 6, 37, 8, 10, 12, 14, 16, 18, 20]
    Removed element with index 6. The new size is: 10
    The new list is [59, 4, 6, 37, 8, 10, 14, 16, 18, 20]
    Removed the value 6. The new size is: 9
    The new list is [59, 4, 37, 8, 10, 14, 16, 18, 20]
    Printing the list using .get/for loop. The list is:
    59 4 37 8 10 14 16 18 20
    Looked for value 10 / list. 10 found at index 4

    The clone list (using for each):
    59 4 37 8 10 14 16 18 20
    The clone list (using toString:
    [59, 4, 37, 8, 10, 14, 16, 18, 20]


    Key Terms
    ADT: A data type that specifies the logical properties without the implementation details (information hiding)
    Clone method: A method that provides a bit-by-bit copy of the objectís data in storage (shallow copy of objectís data).
    Cloneable: An interface that forces a class to provide an appropriate definition of the method clone.
    Comparable: An interface used to force a class to provide an appropriate definition of the method compareTo so that the values of two objects of that class can be properly compared.
    Generic classes (parametric classes): Classes that are used to write a single definition for a set of related classes. Defined using type parameter.
    Generic method: Method defined using type parameter.
    List: A linear collection of elements of the same type.
    (List) Length: The number of elements in the list.
    Ordered list: An ordered collection of elements of the same type. A list in which data items are placed in ascending (or descending) order.
    Set: A list with distinct elements.
    Type parameters (type variables):  Identifiers that specify generic type names.
    Unordered list: An unordered collection of elements of the same type. A list in which data items are placed in no particular order.
    Wrapper class:  A class that allows a primitive data type to be instantiated as an object.

    Additional Resources:
    1. (Sun) API specification for version 6 of the Javaô Platform, Standard Edition (Cloneable, Comparable, ArrayList): http://java.sun.com/javase/6/docs/api/
    2. (Sun) The Java Tutorials, Generics in the Java Programming Language (Gilad Bracha): http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf
    3. (Sun) The Java Tutorials, Generics: http://java.sun.com/docs/books/tutorial/java/generics/index.html
    4. Java Generics FAQs: http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html

    References:
    [1] Java Programming: From Problem Analysis to Program Design, by D.S. Malik, Thomson Course Technology, 2008
    [2] Building Java Programs: A Back to Basics Approach, by Stuart Reges, and Marty Stepp, Addison Wesley, 2008.