Jump to page content Jump to navigation

College Board

AP Central

AP Online Score Reporting
Be an AP Exam Reader
Siemens Awards for Advanced Placement
Click here to visit the SpringBoard Microsite
Print Page
Home > AP Courses and Exams > Course Home Pages > Teaching with Tiger: Using Java 5.0 Features in AP Computer Science Courses

Teaching with Tiger: Using Java 5.0 Features in AP Computer Science Courses

by Cay Horstmann
professor of computer science
San Jose State University
San Jose, California

This article reviews the features that were added to the Java language in the 5.0 (aka 1.5, aka Tiger) release and gives suggestions on how to use them in an AP Computer Science course. It also discusses how these new features are being used in the AP CS Exam.

Executive Summary
Starting with the 2007 AP Exam, your students will need to know the following new language features:
  • How to use generic collections such as ArrayList<E>
  • The "for each" loop
  • The Queue, Stack, and PriorityQueue types from the java.util package (The AP interfaces are no longer required.)
In addition, your students will probably enjoy autoboxing. You may also want to take advantage of other features such as enumerated types, the Scanner class, or static imports. However, these are not required for the exam.

Generic Collections
Probably the most useful new feature in Java 5.0 is the ability to specify element types of collections. You can now form an

ArrayList<City>cityList;

or a

Map<Location, City> cityLocator;

The benefit is clear: There is no more guesswork about what kind of Object is in a given collection.

Generic collections are easy to use. All collection classes and interfaces in the standard Java library support type parameters. Simply specify the element type for collections or the key and value types for maps, enclosed in angle brackets.

There are two important benefits:
  • There is no more guesswork about what kind of Object is in a given collection. Had cityList been declared as a plain ArrayList, it would not have been clear whether it contains City objects or String objects.
  • You can use ArrayList earlier in your course, without having to introduce casting or the Object class. For example,

    City firstCity = cityList.get(0); // no (City) cast needed

    The cast is not required because the compiler keeps track of the element types. It knows that an ArrayList<City> contains City objects.
Generic collections cannot hold primitive types. For example, there is no ArrayList<int>. The remedy is to use wrapper types such as ArrayList<Integer>. The new autoboxing feature, discussed later in this article, makes it easy convert between primitive types and wrapper classes.

You are not forced to use generic collections -- if you omit the type parameters, you simply get "raw" collections that hold elements of type Object. You can even mix generic and raw collections in the same program, but then you may get unsightly compiler warnings when the compiler loses track of type information.

Of course, C++ veterans will recognize generic types as the equivalent of templates such as vector<City>. As with C++ templates, Java generic types are easy to use if you stick to the basics and just use classes that other programmers have defined, such as collections. Defining your own generic types is considerably more complex, as you will see later in this article.

Starting in 2007, the AP CS Exam will use generic collections. That is, your students will see types such as ArrayList<City> or Map<Location, City> on the exam.

The "For Each" Loop
The "for each" loop (aka "enhanced for loop") is a new looping construct that iterates over all elements in an array or a collection. For example:

City[] cities = . . .;

for (City c : cities)
    System.out.println(c.getName());
The loop iterates over the elements of cities. In each iteration, the variable c is set to the next element and the loop body is executed.

The benefit is simpler code. The "for each" loop is much easier to read than a traditional loop:

for (int i = 0; i < cities.length; i++)
{
    City c = cities[i];
    System.out.println(c.getName());
}
There is also less room for indexing errors.

You can use the same loop to visit each element in a collection:

ArrayList<City> cityList = . . .;
for (City c : cityList)
    System.out.println(c.getName());
Technically, this loop is a shortcut for the following traditional loop:
for (Iterator<City> iter = cityList.iterator(); iter.hasNext(); )
{
    City c = iter.next();
    System.out.println(c.getName());
}
In fact, you can use the "for each" loop to iterate through objects of any class that implements the Iterable interface. That interface has a single method, iterator. In Java 5.0, all collection classes implement the Iterable interface. Therefore, you can use the "for each" loop to iterate through linked lists and sets, not just array lists.
Set<City> citySet = . . .;

for (City c : citySet)
    System.out.println(c.getName());
To visit all entries in a map, you iterate through the key set, like this:
Map<Location, City> cityLocator = . . .;
for (Location loc : cityLocator.keySet())
{
    City c = cityLocator.get(loc);
    . . .
}
Of course, there are many loops that cannot be expressed as a "for each" loop. For example, if you want to skip the first or last element, or if you need the index variable in the loop body, or if you want to add or remove elements during the iteration, then you still need a traditional loop.

You may wonder how useful the "for each" loop is -- after all, any "for each" loop could be rewritten as a traditional for loop. However, most programmers find the "for each" loop seductive. It is very liberating to be able to write "Do something for all elements in a collection" without having to worry about indexes or iterators.

Starting in 2007, your students will see the "for each" loop in exams. There won't be any "trick questions" -- the "for each" loop will simply be used in situations in which students shouldn't waste time with indexes or iterators.

Additional Collection Classes and Interfaces
JDK 5.0 introduces a Queue interface, implemented by
  • the LinkedList class
  • the PriorityQueue class
  • several classes that are useful for multithreading and are not of interest in introductory CS courses
With the advent of these interfaces and classes, the Queue, PriorityQueue and Stack interfaces are being retired after the 2006 exam. Starting with the 2007 exam, only the interfaces and classes of the standard Java library will be required.

There are minor changes in method names:

Old name New name
peekTop, peekFront, peekMin peek
enqueue add
dequeue, removeMin remove


If you need a queue, simply use the Queue interface and instantiate it with a LinkedList:

Queue<Customer> waitingCustomers = new
LinkedList<Customer>();


For a priority queue or stack, the Java API provides classes (not interfaces) PriorityQueue and Stack. If an AP Exam question discusses alternative implementations of an interface for priority queues or stacks, the question will define interfaces and classes such as StackInt and StackImpl, as required for the particular question.

CAUTION: Queue, Stack, and PriorityQueue inherit methods that are incompatible with the idealized queue, stack, and priority queue data structures. For example, it is possible to inspect and remove arbitrary elements by using inappropriate methods in the Java API.
  • AP CS Exam questions about the behavior of queues, stacks, and priority queues always refer to the idealized data structures, not to the Queue, Stack, and PriorityQueue types in the java.util package. For example, a question asking about the efficiency of accessing the bottom of a stack would assume that all other elements need to be popped off first, even though the bottom of a java.util.Stack can be accessed more efficiently.
  • If students carry out operations in a free-response question that are incompatible with the nature of a data structure, they may not receive full credit.
Autoboxing
Autoboxing refers to the automatic conversion between primitive types and their corresponding wrapper types. (Autowrapping would have been a better term, but the Java designers took this feature, including the name, from C#.) For example:

Integer integerObj = 1729; // automatically calls the constructor new Integer(1729)
The converse (sometimes called auto-unboxing) is also automatic:
int n = integerObj; // automatically calls integerObject.intValue()
Automatic boxing and unboxing also happens in arithmetic expressions that involve wrapper objects:
integerObj--; // same as integerObj = new Integer(integerObj.intValue() - 1);
Autoboxing is useful when primitive type values are stored in collections. For example:
ArrayList<Integer> luckyNumbers = new ArrayList<Integer>();
luckyNumbers.add(1729); // same as luckyNumbers.add(new Integer(1729));
int n = luckyNumbers.get(0); 
For professional programming, the opportunity for autoboxing does not occur very often. However, when teaching introductory computer science courses, collections that hold numbers are commonly used as examples, and autoboxing is convenient.

Note that it is not a good idea to replace all primitive types with wrappers. Even though the resulting code will compile and run in most cases, the code is significantly less efficient and does not resemble "real world" code.

The downside of autoboxing is that the exact rules are rather complex. For example, consider the comparison:
integerObject == n
Does this code unbox integerObject and compare two integer values, or does it box n and compare two object references? As it turns out, it does the former. But you probably don't want to spend valuable class time discussing syntax trivia. If you decide to cover autoboxing in your class, it would seem best to stick to simple situations; in particular, getting and setting elements in wrapper collections.

Currently, there are no plans for using autoboxing in the AP CS Exam. In the few cases in which the exam contains collections of Integer or Double types, the exam will contain explicit calls to constructors and intValue/doubleValue. That way, students won't need to study the subtleties of the conversion rules. However, students are certainly free to use autoboxing in the free response portion of the exam.

Console I/O with Scanner and printf
Before Java 5.0, the Java library mechanisms for console input or formatted output were cumbersome and hard to use. While input and formatted output are not included in the AP CS Java subset, they are required for teaching a CS course. Some textbooks provided custom classes for console input, but it was always a bit of a hassle to teach beginning students how to add these libraries to their development environments. (Of course, if you use BlueJ, you can supply all input by calling methods in the object workbench, and the entire problem goes away.)

Java 5.0 introduces a Scanner class that can be used to read from any input stream, in particular, from System.in. It has a set of methods to read numbers and strings, none of which throw checked exceptions. The class was specifically designed for introductory programming courses, but it is robust enough for professional usage. Here is how you read an integer from the console:
Scanner in = new Scanner(System.in);
System.out.print("How old are you?");
int age = in.nextInt();
If the user doesn't enter an integer, an unchecked InputMismatchException is thrown. For better error handling, you can check whether the next input token is an integer:
if (in.hasNextInt())
    age = in.nextInt();
else
    System.out.println("Hey, knucklehead, read the instructions!");
You can also use a Scanner to read from a file:
String fileName = . . .;
Scanner file = new Scanner(new File(fileName));
In other words, anything that you can do with BufferedReader, you can do better with a Scanner.

Another useful addition in Java 5.0 is formatted output, using a C style printf method. For example, to print a monetary value with dollars and cents, you simply call
double amount = 1.0 / 3.0;
System.out.printf("amount=%8.2f", amount); // prints amount= 0.33
    // 8 characters total, 2 digits after the decimal point
This is obviously useful to suppress unsightly round off errors, and to line up columns in tables. In older versions of Java, you had to use the clunky NumberFormat class for the same purpose.

Neither the Scanner class nor the printf method is tested on the AP CS Exam, but you will probably find both of them very useful for your course.

Methods with Variable Arguments
Starting with Java 5.0, you can define methods that accept a variable number of arguments. One example is the printf method. For example, you can print multiple values like this:

System.out.printf("%8.2f %8.2f %8.2f", amount, tax, amount * tax);

The printf method is declared like this:

public PrintStream printf(String format, Object... values)

The Object... parameter indicates that it is legal to pass any number of arguments, each of which must be of type Object. (In our example, autoboxing turns numbers into wrapper objects.) All parameters are placed inside an Object[] array named values.

Here is another example of a method with variable arguments. This method computes the average of any sequence of double values:
public static double average(double... values)
{
    assert values.length > 0;
    double sum = 0;
    for (double v : values) sum += v;
    return sum / values.length;
}
This capability is occasionally used in advanced parts of the standard library, but it probably should not be covered in an introductory course. It is not a part of the AP CS subset.

Static Imports
A static import lets you refer to static fields and methods without a class name prefix. For example:
import static java.lang.Math.*;
. . .
double x = sqrt(PI); // instead of Math.sqrt(Math.PI)
Code that uses many mathematical functions is more pleasant to read when you use static imports. However, in a beginning course, you probably want students to know that sqrt and PI are in the Math class.

Static imports are not a part of the AP CS Java subset.

Enumerated Types
Prior to JDK 5.0, Java had no facility for defining enumerated types, that is, types with only a finite number of values. The naive workaround is to define a sequence of integer constants instead:
public class Font
{
    public static int PLAIN = 0;
    public static int BOLD = 1;
    public static int ITALIC = 2;
    . . .
}
. . .
public int style = Font.BOLD;
However, this technique is not type safe. It is possible to assign another integer value to the variable style that does not represent a valid font style. In fact, a common error is to call the Font constructor
public Font(String name, int style, int size)
with the wrong parameters:
Font f = new Font("Serif", 12, Font.BOLD); 
    // oops, makes a 1 point font with a style of "12"
A typesafe enumeration solves this problem.
public enum Style { PLAIN, BOLD, ITALIC };
Now Style is a separate type that cannot be confused with int. In fact, enumerated types are classes with a private constructor and a finite number of instances. Omitting some technical details, the sample enum declaration is a shortcut for:
public class Style
{
    private Style() {} 
    public static Style PLAIN = new Style();
    public static Style BOLD = new Style();
    public static Style ITALIC = new Style();
}
It is even possible to add methods and instance fields to enum classes.

You may want to cover basic enumerated types in a beginning course, but it would seem wise to stay away from enumerations with added methods or fields. Enumerated types are not a part of the AP CS Java subset.

Implementing Generic Types
AP CS students learn to implement linked lists, hash tables, and binary trees. It seems reasonable to expect students to implement generic classes that exactly mimic the behavior of the standard collection classes.

In simple cases, this is indeed easy in Java 5.0. For example, a generic list node class can be defined like this:
public class ListNode<E> // not the AP ListNode class
{
    public ListNode(E value, ListNode next) { . . . }
    public E getValue() { . . . }
    public ListNode getNext() { . . . }
    private E value;
    private ListNode next;
}
A ListNode<E> holds a value of type E. A linked list with element type E is composed of ListNode<E> objects:
public class LinkedListImpl<E>
{
    . . .
    private ListNode<E> link;
}
However, with other data structures, the situation is more complex. Consider a binary search tree. The tree node values should implement the Comparable interface so that we can compare them. The syntax for expressing this constraint is:
public class TreeNode<E extends Comparable<E>>
{
    . . .
}
Note that Comparable is a generic interface in Java 5.0, defined like this:
public interface Comparable<T> // the type parameter is not used in the AP Exam
{
    int compareTo(T other);
}
The type parameter specifies the type of the other parameter of the compareTo method. This is an improvement over the "raw" Comparable interface that required a cast in implementations of compareTo. For example:
public class Person implements Comparable<Person>
{
    public int compareTo(Person other) 
    { 
         return id - other.id; 
             // no need to cast other
    }
    . . .
    private int id;
}
Because Person implements the Comparable interface, we can form a TreeNode<Person>. But if we tried to form a TreeNode<City>, then the compiler would complain that City does not implement Comparable<City>. That is good.

But now something very unpleasant happens. Suppose we want to form a subclass of Person, say Student:

public class Student extends Person { . . . }

Can we form a TreeNode<Student>? No -- Student doesn't implement Comparable<Student>, only Comparable<Person>. This is an unreasonable restriction since we can obviously compare two Student objects. To overcome this restriction, you have to relax the constraint on the generic type, like this:
public class TreeNode<E extends Comparable<? super E>>
{
    . . ..
}
This means "E is a type that implements Comparable, where ? is an anonymous type that is a supertype of E." It may be possible to explain this to a beginning student, but it is far removed from the material that we want to study, namely the implementation of binary search trees.

There are other pitfalls. Suppose we want to implement a dynamic array:
public class ArrayListImpl<E>
{
    public ArrayListImpl(int capacity) 
    {
         elements = new E[capacity]; // ERROR 
    } 

    private E[] elements;
}
Unfortunately, it is not legal to construct an array of a generic type. This restriction is due to the implementation of generics through "type erasure", something that you probably don't want to explain to your students. There are workarounds (after all, the Java library designers managed to implement ArrayList<E>), but they are not student-friendly.

In the AP CS Exam, the implementation of data structures is covered in the traditional way, using collections of type Object and the "raw" Comparable type without a type parameter. This requires some casting, but it minimizes surprises. In particular:
  • The ListNode and TreeNode classes that are often used in exam questions are not generic
  • The exam uses the "raw" Comparable interface with the familiar method int compareTo(Object other)
  • Students are not expected to implement generic methods in the exam
Annotations
Graham Hamilton, Sun Fellow in the Java platform team at Sun Microsystems, hails annotations as the most important feature of the Java 5.0 release. Programmers can annotate methods, fields, classes, and other parts of their Java programs with custom tags. Here is an example of a @TestCase annotation:
public class LinkedListImpl
{
    . . .
    @TestCase
    public void testInsert() 
    {
         . . .
    }
}
Annotations have no effect on the generated code. They are simply added to the class files, where they can be harvested by special tools, called annotation processors. An annotation processor for unit testing might invoke all methods that have the @TestCase annotation. Another annotation processor might edit the class files, removing the bytecodes for the test methods after testing is complete.

Programmers who use the J2EE (Java 2 Enterprise Edition) platform are excited about annotations because they promise to remove much of the boilerplate code that programmers currently have to supply. The next version of J2EE will provide annotation processors that generate the boilerplate code automatically, provided, of course, that programmers have inserted the proper annotations into the source code.

One annotation with potential benefits for beginning students is @Override. You use it to tag methods that are intended to override a superclass method. For example:
public class Person
{
   @Override
   public boolean equals(Person other) // oops...
   {
   }
}
The annotation causes the compiler to complain. The superclass Object has no method boolean equals(Person other), so it is an error to override it. Of course, the student meant to override the method boolean equals(Object other).

Annotations are not tested in the AP CS Exam.


Cay Horstmann is a professor of computer science at San Jose State University. Previously, he was vice president and chief technology officer of Preview Systems Inc. and a consultant for major corporations, universities, and organizations on C++, Java, and internet programming. He is the author of many successful professional and academic books.


  ABOUT MY AP CENTRAL
    Course and Email Newsletter Preferences
  AP COURSES AND EXAMS
    Course Home Pages
    Course Descriptions
    The Course Audit
    Teachers' Resources
    Exam Calendar and Fees
    Exam Information
    FAQs
  PRE-AP
    SpringBoard® Pre-AP Program
    Workshops
    Teachers' Corner
  AP COMMUNITY
    About Electronic Discussion Groups
    Become an AP Exam Reader

Back to top