10. Collections and Generics

About this Tutorial –

Objectives –

This course is aimed at object-oriented developers (e.g. C++ or C#) who need to transition into Java. It is also aimed at those learning to program for the first time; the course covers the Java programming constructs and APIs quickly, focussing on the differences between Java and other OO languages.

Audience

This training course is aimed at OO developers who need to transition into Java.

Prerequisites

No previous experience in Java programming is required. But any experience you do have in programming will help. Also no experience in eclipse is required. But again any experience you do have with programming development environments will be a valuable.

Contents

The Java course cover these topics and more:

  • Flow Control: Decision making: if and if-else; The switch statement; Looping: for loops; while loops; do-while loops; for-each style loops; Assertionsv
  • Concurrency: Overview of multithreading; Creating new threads; Object locking; Using wait, notify, and notifyAll
  • Collections: Overview of Java SE collection classes; Generics; Using List-based collection classes; Using Set-based collection classes; Using Map-based collection classes; Collection techniques

Exam Preparation

The Java course will help you prepare for these certifications:

  • Oracle Certified Java Associate – Exam 1Z0-803
  • Oracle Certified Java Professional – Exam 1Z0-804

Download Solutions

HTML tutorial


Overview

Estimated Time – 2 Hours

Not what you are looking? Try the next tutorial – Exceptions and Assertions

  1. Collections vs. Arrays
    • A collection is an object that holds a group of other objects
      • Some collection types (e.g. ArrayList) actually use arrays internally to store the data
    • Differences between arrays and collections:
      • Arrays are a Java language feature – Collections are standard Java classes, in the java.util package
      • Arrays are pretty basic – Collections provide lots of useful methods to manipulate contents
      • Arrays are fixed size – Collections are resizable
      • Arrays can store primitive types as well as objects – Collections store only objects
      • Arrays are processed using indexes – Collections are usually processed without using indexes
  2. Simple Code Examples
    • This code uses arrays:
      public static void demoUsingArrays() {
       String[] countries = new String[3];
       countries[0] = "Norway";
       countries[1] = "Sweden";
       countries[2] = "Denmark";
       for (int i = 0; i < countries.length; i++))
        System.out.println(countries[i]);
      }
    • View code file.
    • Equivalent code, using collections – See next section for discussion of <> generics syntax
      import java.util.*;
      ...
      public static void demoUsingCollections() {
       ArrayList<String> countries = new ArrayList<String>();
       countries.add("Norway");
       countries.add("Sweden");
       countries.add("Denmark");
       for (String country: countries)
        System.out.println(country);
      }
    • View code file.
  3. Common Collection Classes / Interfaces
  4. T10P2

  5. Understanding the Classes / Interfaces
    • Collection
    • Set
      • Set-based collections guarantee element uniqueness
      • E.g. HashSet uses hash codes to ensure element uniqueness
    • List
      • List-based collections store elements sequentially, by position
      • E.g. ArrayList stores elements internally as an array
      • E.g. LinkedList stores elements internally as a linked list
    • Map
      • Map-based collections store key/value pairs
      • E.g. HashMap arranges items by using hash codes
      • E.g. TreeMap arranges items in a tree

Lab 1: The need for generics

Lab 1: The need for generics
  1. What are Generics?
    • Generics are type-safe classes or methods
      • Parameterized types (must be reference types, not primitives)
      • More efficient and type-safe than non-generic types
      • Enables collections (typically) to know the types of their objects
    • Examples of non-generic (“raw”) types
      • LinkedList
      • HashMap
    • Examples of generic types
      • LinkedList<String>
      • HashMap<Integer, String>
    • Note:
      • Generics were introduced in Java SE 1.5
      • Definitely the preferred approach nowadays
  2. Example of Using Raw Types
    • Consider the following example, which uses the raw types
      import java.util.*;
      ...
      public static void demoRawTypes() {
        System.out.println("\nDemonstrate the use of raw types.");
        Map players = new HashMap();
        players.put(6, "Ferrie Bodde");
        ...
        Set items = players.entrySet();
        Iterator iter = items.iterator();
        while (iter.hasNext()) {
          Map.Entry entry = (Map.Entry)iter.next();
          int number = (Integer)entry.getKey();
          ...
        }
      }
    • View code file.
  3. Problems with Using Raw Types
    • The map doesn’t remember the types of the keys/values that it contains
      • The map just stores Object references internally
        Map players = new HashMap();
    • This means the compiler cannot detect if client code inserts incorrect types of objects
      • This will probably cause run-time errors later:
        players.put("Twenty three", "Guillem Bauza");
    • Client code has to cast objects on retrieval
      • Because the retrieval methods just return Object
      • The client code has to remember the real types
        int number = (Integer)entry.getKey();
        String name = (String)entry.getValue();
  4. Example of Using Generic Types
    • Consider the following example, which uses generic types
      import java.util.*;
      ...
      public static void demoGenericTypes() {
        System.out.println("\nDemonstrate the use of generic types.");
        Map<Integer, String> players = new HashMap<Integer, String>();
        players.put(6, "Ferrie Bodde");
        players.put(8, "Darren Pratley");
        players.put(22, "Angel Rangel");
        Set<Map.Entry<Integer, String>>> items = players.entrySet();
        Iterator<Map.Entry<Integer, String>>> iter = items.iterator();
        while (iter.hasNext()) {
          Map.Entry<Integer, String> entry = iter.next();
          int number = entry.getKey();
          String name = entry.getValue();
          System.out.println(number + "\t: " + name);
        }
      }
    • View code file.
  5. Advantages of Using Generic Types
    • The generic map remembers the types of the keys/values that it contains
      • The map stores type-safe references internally
        Map players = new HashMap();
    • This means the compiler can detect if client code inserts incorrect types of objects
      • No possibilities of run-time errors later:
        players.put("Twenty three", "Guillem Bauza");
        // This causes a compile error
    • Client code doesn’t cast objects on retrieval
      • Because the retrieval methods return type-safe types
      • The client code doesn’t have to remember the real types
        int number = entry.getKey();
        String name = entry.getValue();
  6. Type Inference in Java SE 7
    • Java SE 7 allows you to omit type information when you invoke a constructor
      • Just use empty <> instead
        Map<String, Integer> asiaDialCodes = new HashMap<>();
      • Example:
        • See DemoGenericsTypeInference.java
Lab
  1. Using a simple list
    • In manageFootballTeams(), add code where indicated by the TODO comments, to manipulate a list of Strings
    • You can use either an ArrayList or a LinkedList in this exercise, because both classes implement the necessary list-related behaviour
        List<String> teamList = new ArrayList<>();
    • Cases:
      • Case 1 –
              team = Helper.getString("Enter team: ");
              boolean added = teamList.add(team);
              if (added) {
               System.out.printf("Successfully appended %s.\n", team);
              } else {
               System.out.printf("Could not append %s.\n", team);
              }
              break;
      • View code file.
      • Case 2 –
              team = Helper.getString("Enter team: ");
              index = Helper.getInt("Enter index: ");
              if (index <= teamList.size()) {
               teamList.add(index, team);
               System.out.printf("Successfully added %s at index %d.\n", team, index);
              } else {
               System.out.printf("Sorry, max index is %d.\n", teamList.size());
              }
              break;
      • View code file.
      • Case 3 –
              team = Helper.getString("Enter team: ");
              index = Helper.getInt("Enter index: ");
              if (index < teamList.size()) {
               String origTeam = teamList.set(index, team);
               System.out.printf("Successfully set %s at index %d, replacing %s.\n", team, index, origTeam);
              } else {
               System.out.printf("Sorry, max index is %d.\n", teamList.size()-1);
              }
      • View code file.
      • Case 4 –
              index = Helper.getInt("Enter index: ");
              if (index < teamList.size()) {
               String removedTeam = teamList.remove(index);
               System.out.printf("Successfully removed %s at index %d.\n", removedTeam, index);
              } else {
               System.out.printf("Sorry, max index is %d.\n", teamList.size()-1);
              }
              break;
      • View code file.
      • Case 5 –
              team = Helper.getString("Enter team: ");
              boolean removed = teamList.remove(team);
              if (removed) {
               System.out.printf("Successfully removed %s.\n", team);
              } else {
               System.out.printf("Could not remove %s, not found.\n", team);
              }
              break;
      • View code file.
      • Case 6 –
              Helper.displayCollection(teamList);
              break;
      • View code file.
    • Run the application, and make sure all the options work as expected

Lab 2: Using collections

Lab 2: Using collections
  1. Using ArrayList
    • Constructors:
      • Default capacity
      • Specific capacity
      • From another Collection
    • Some useful methods:
      • add(), addAll(), set()
      • size(), get(), indexOf(), contains(), isEmpty()
      • remove(), removeAll(), clear(), etc…
    • Example:
      • See UsingCollections.java, demoArrayList()
  2. Using LinkedList
    • Constructors:
      • Empty list
      • From another Collection
    • Some useful methods, in addition to those of ArrayList:
      • addFirst(), addLast()
      • push(), pop()
      • getFirst(), getLast(), lastIndexOf(), etc…
    • Example:
      • See UsingCollections.java, demoLinkedList()
  3. Using HashSet
    • Constructors:
      • Default capacity
      • Specific capacity, and optionally a specific load factor
      • From another Collection
    • Some useful methods (not many):
      • add(), addAll()
      • size(), contains(), isEmpty()
      • remove(), removeAll(), clear()
    • Example:
      • See UsingCollections.java, demoHashSet()
  4. Using HashMap
    • Constructors:
      • Default capacity
      • Specific capacity, and optionally a specific load factor
      • From another Map
    • Some useful methods:
      • put(), putAll()
      • keySet(), values()
      • size(), containsKey(), containsValue(), isEmpty()
      • remove(), clear()
    • Example:
      • See UsingCollections.java, demoHashMap()
  5. Using TreeMap
    • TreeMap
      • Stores key / value pairs (each pair is an Entry<K,V>)
      • Is sorted
      • Offers constant-time performance for basic operations
      • Is much more powerful than HashMap
    • Take a look in the JavaDoc and try out some of its functionality
Lab
  1. Using a LinkedList
    • In manageSalaries(), add code where indicated by the TODO comments, to manipulate a list of Doubles
    • You must use a LinkedList in this exercise. Also note that you must declare it as LinkedList<Double> rather than as a LinkedList<double> – why is this?
        // Declare a LinkedList to hold salaries (i.e. Doubles).
        LinkedList<Double> salaryList = new LinkedList<>();
    • Cases:
      • Case 1 –
              salary = Helper.getDouble("Enter salary: ");
              salaryList.push(salary);
              System.out.printf("Successfully pushed %.2f.\n", salary);
              break;
      • View code file.
      • Case 2 –
              System.out.printf("Popped salary is %.2f.\n", salaryList.pop());
              break;
      • View code file.
      • Case 3 –
              salary = Helper.getDouble("Enter salary: ");
              salaryList.addFirst(salary);
              System.out.printf("Successfully added %.2f at start of list.\n", salary);
              break;
      • View code file.
      • Case 4 –
              salary = Helper.getDouble("Enter salary: ");
              salaryList.addLast(salary);
              System.out.printf("Successfully added %.2f at end of list.\n", salary);
              break;
      • View code file.
      • Case 5 –
              if (salaryList.size() != 0) {
               System.out.printf("Peek first gives: %.2f.\n", salaryList.peekFirst());
               System.out.printf("Peek last gives: %.2f.\n", salaryList.peekLast());
              } else {
               System.out.printf("Sorry, there are no elements at the moment.\n");
              }
              break;
      • View code file.
      • Case 6 –
              Helper.displayCollection(salaryList);
              break;
      • View code file.
    • In main(), uncomment the call to manageSalaries(), and then run the application. Make sure all the options work as expected

Lab 3: Defining generic classes

Lab 3: Defining generic classes
  1. Overview
  2. Defining a Generic Class
    • Here’s a sample generic class
      import java.util.*;
      ...
      class CyclicList<T> {
        // Define members. Note we can use the type parameter here.
        private ArrayList<T> elements;
        private int currentPosition;
        private int maxElements;
        // Define a constructor. 
        CyclicList(int size) {
          elements = new ArrayList<T>();
          currentPosition = 0;
          maxElements = size;
          // Pre-populate collection with nulls.
          for (int i = 0; i < maxElements; i++) {
            elements.add(null);
          }
        }
        ...
    • View code file.
  3. Implementing Methods in a Generic Class
    • Methods in a generic class can use the type parameter
      public void insert(T item) {
        elements.set(currentPosition, item);
        if (++currentPosition == maxElements) {
          currentPosition = 0;
        }
      }
      public T getItemAt(int position) throws IndexOutOfBoundsException {
        if (position >= maxElements) { ... // error
        else { ... //return position
      }
      public void display() {
        for(T item: elements) {
          if (item == null) {
            System.out.print("[Null] ");
          }
          else {
            System.out.print(item.toString() + " ");
          }
        }
      }
    • View code file.
  4. Using a Generic Class
    • When you instantiate a generic class, you must specify a type parameter
      • The type parameter must be a class/interface
      • This is called “type substitution”
    • When you use the generic class, it’s typesafe!
      • No need for ugly casts
        CyclicList<Integer> lotteryNumbers = new CyclicList<>(6);
        lotteryNumbers.insert(19);
        lotteryNumbers.insert(1);
        lotteryNumbers.insert(2);
        lotteryNumbers.insert(7);
        int lotteryNumber0 = lotteryNumbers.getItemAt(0);
        System.out.println("Lottery number 0 is: " + lotteryNumber0);
        System.out.print("Collection: ");
        lotteryNumbers.display();
      • View code file.
      • The result would be “Lottery number 0 is: 19” and “Collection: 19 1 2 7 [Null] [Null]
  5. Additional Techniques
    • A generic type can have multiple type parameters
      public class MyMap<K,V> {
       ...
      }
      // Usage:
      MyMap<String, Date> birthdays = new MyMap<>();
    • A generic type can contain a nested type
      public class MyMap<K,V> {
       static interface Entry<K,V> {...}
       ...
      }
      // Usage:
      MyMap<String, Date> birthdays = new MyMap<>();
      MyMap.Entry<String,Date> entry;
  6. Erasures
    • Note that generics are a compile-time-only mechanism
      • Provides type information for the compiler, to make sure your code is type safe
    • The compiler generates non-generic bytecode!
      • At run-time, type information is not available
      • E.g. if your code uses Map<Integer,String>, at run-time the bytecodes will use just Map
    • This is called “erasure”
      • I.e. the compiler erases type information
      • Why? For compatibility with pre-generic code
Lab
  1. Using a TreeMap
    • In this exercise, you’ll use a TreeMap to manage a collection of Employee objects
    • To get started, take a look at the Employee class in Employee.java and note the following points:
      • Each employee has an ID (string), a name, and a salary
      • The constructor initializes a new Employee object from the keyboard, for simplicity
      • The getId() method returns the employee’s ID
      • The toString() method returns a textual representation of an Employee object
      • The equals() method determines whether an Employee object “is equal to” another object. This will be useful later, when you need to ascertain whether an Employee object is already in the TreeMap
    • Now switch back to UsingCollections.java and locate the manageEmployees() method
      • Add code where indicated by the TODO comments, to manipulate a TreeMap of employees
      • In the TreeMap, the keys should be the employee IDs, and the values should be the Employee objects themselves
          // Declare a TreeMap to hold Employees (keyed by employee id).
          TreeMap<String, Employee> empMap = new TreeMap<>();
      • View code file.
      • Cases:
        • Case 1 –
                emp = Employee.createEmployee();
                Employee putEmp = empMap.put(emp.getId(), emp);
                System.out.printf("Successfully put %s.\n", putEmp);
                break;
        • View code file.
        • Case 2 –
                id = Helper.getString("Enter id: ");
                emp = empMap.remove(id);
                if (emp != null) {
                 System.out.printf("Successfully removed employee %s.\n", emp);
                } else {
                 System.out.printf("Could not remove employee with id %s, id not found.\n", id);
                }
                break;
        • View code file.
        • Case 3 –
                id = Helper.getString("Enter id: ");
                boolean keyFound = empMap.containsKey(id);
                if (keyFound) {
                 System.out.printf("id %s was found.\n", id);
                } else {
                 System.out.printf("id %s was not found.\n", id);
                }
                break;
        • View code file.
        • Case 4 –
                emp = Employee.createEmployee();
                boolean empFound = empMap.containsValue(emp);
                if (empFound) {
                 System.out.printf("Employee %s was found.\n", emp);
                } else {
                 System.out.printf("Employee %s was not found.\n", emp);
                }
                break;
        • View code file.
        • Case 5 –
                if (empMap.size() != 0) {
                 System.out.printf("First entry is: %s %s.\n", empMap.firstEntry().getKey(), empMap.firstEntry().getValue());
                 System.out.printf("Last entry is: %s %s.\n", empMap.lastEntry().getKey(), empMap.lastEntry().getValue());
                } else {
                 System.out.printf("Sorry, there are no entries at the moment.\n");
                }
                break;
        • View code file.
        • Case 6 –
                Helper.displayCollection(empMap.values());
                break;
        • View code file.
      • In main(), uncomment the call to manageEmployees(), and then run the application. Make sure all the options work as expected

Lab 4: Defining generic methods

Lab 4: Defining generic methods
  1. Overview
    • The previous section described generic types
    • It’s also possible to define generic methods
      • A generic method contains type parameter(s) that affect one particular method only
      • This means a non-generic class can contain a mixture of generic and non-generic methods
    • Syntax for generic methods:
      accessSpecifier <T> returnType methodName(methodArgs) {
       ...
      }
  2. Implementing Generic Methods
    • The following non-generic class contains two generic methods
      • For simplicity, the methods are static in this example
        class MyUtilityClass {
          public static <T> void displayType(T obj) {
            System.out.println("Object type: " + obj.getClass().getName());
          }
          public static <T> void displayArray(T[] array) {
            System.out.print("Array items: ");
            for(T item: array) {
              System.out.print(item.toString() + " ");
            }
          }
        }
  3. Invoking Generic Methods
    • Invoke generic methods in the same way as you invoke non-generic methods
      • The argument types implicitly provide the type information
        MyUtilityClass.displayType(new Date());
        Integer[] array = new Integer[] {10, 20, 30};
        MyUtilityClass.displayType(array[0]);
        MyUtilityClass.displayType(array);
        MyUtilityClass.displayArray(array);
      • T10P1

Lab
  1. Implementing a generic method
    • Take a closer look at your “display” code in each of the methods you’ve just written. You should find quite a lot of similarity in each case With this in mind, refactor your code by implementing a generic helper method in Helper.java to display the items (of a given type) in any kind of collection.
    • Suggestions and requirements:
      • Name the method displayCollection()
      • The method should be flexible enough to take any kind of collection, containing any type of items
      • In the method, display a message indicating the actual type of collection passed in (e.g. ArrayList, LinkedList, etc.)
      • Then display each item in the collection (along with its type, e.g. String, Double, etc.)
         // Generic method, displays all the items in a Collection<T>.
         public static <T> void displayCollection(Collection<T>>list) {
          System.out.printf("Elements in %s:\n", list.getClass().getName());
          for (T element : list) {
           System.out.printf(" %s value: %s.\n", element.getClass().getName(), element);
          }
         }
      • View code file.

 

Well done. You have completed the tutorial in the Java course. The next tutorial is

11. Exceptions and Assertions


Back to beginning
Copyright © 2016 TalkIT®






If you liked this post, please comment with your suggestions to help others.
If you would like to see more content like this in the future, please fill-in our quick survey.
Scroll to Top