SE450: Lecture 1 (Basics)

Contents [0/63]

Are You In The Right Room? [1/63]
Overview of Today's Class [2/63]
Admin: A Brief Introduction [3/63]
Admin: Course Objectives [4/63]
Admin: Course Overview [5/63]
Admin: Contrast with SE430 [6/63]
Admin: SE450 Approach [7/63]
Admin: Prerequisites [8/63]
Admin: Java Self-Assessment [9/63]
Admin: Expectations [10/63]
Admin: Course Homepage [11/63]
Admin: Mailing List [12/63]
Admin: How to talk about code via email [13/63]
Admin: Assessment [14/63]
Admin: Attendance [15/63]
Admin: Projects [16/63]
Admin: Contact Information [17/63]
Admin: Textbooks [18/63]
Admin: Tools [19/63]
Basics: Subclassing [20/63]
Basics: How Do We Run Programs? [21/63]
Basics: Class Initialization [22/63]
Basics: Functions [23/63]
Basics: Activation [24/63]
Basics: Objects [25/63]
Basics: Object-Oriented? [26/63]
Basics: Object Classes and Static Classes [27/63]
Basics: Constructors/Methods/Fields [28/63]
Basics: Object Diagrams [29/63]
Basics: Fields [30/63]
Basics: Passing Parameters [31/63]
Basics: Passing Parameters [32/63]
Basics: Values and References [33/63]
Basics: Values and References [34/63]
Basics: UML Notation for References [35/63]
Basics: UML Class Diagrams: Static Class [36/63]
Basics: UML Class Diagrams: Object Class [37/63]
Basics: Aggregation (Reference) [38/63]
Basics: Composition (Exclusive Reference) [39/63]
Basics: Dependency [40/63]
Basics: The Ugly Truth [41/63]
Basics: Mixed Classes [42/63]
Basics: Multiplicity [43/63]
Basics: Association [44/63]
Basics: UML Summary [45/63]
Basics: Test [46/63]
Testing: Overview [47/63]
Testing: Junit Tests [48/63]
Testing: Junit Assertions [49/63]
Testing: Junit Derived Assertions [50/63]
Testing: Examples [51/63]
Testing: Homework 1 [52/63]
Taxonomy: Why a Taxonomy? [53/63]
Taxonomy: Object Classes [54/63]
Taxonomy: Immutable Data Classes [55/63]
Taxonomy: Guidelines for Immutable Data Classes [56/63]
Taxonomy: Advantages of Immutable Objects [57/63]
Taxonomy: The Builder Pattern [58/63]
Taxonomy: A Builder in the Java APIs [59/63]
Taxonomy: Mutable Data Classes [60/63]
Taxonomy: Hashing Mutable Data is Dangerous [61/63]
Taxonomy: Collection Classes [62/63]
Taxonomy: UML representation of Collections [63/63]

Are You In The Right Room? [1/63]

Course: SE450 (Object-Oriented Software Development)

Instructor: James Riely

Diagram1_dia

Overview of Today's Class [2/63]

Admin: A Brief Introduction [3/63]

What is this course about?

Programming in the large.

This course teaches management of the producer/consumer relationship, emphasizing code structure.

Admin: Course Objectives [4/63]

By the end of this course you should:

Admin: Course Overview [5/63]

We will study object-oriented design and implementation. Among the topics of the course are:

Java and the UML will be used for source code examples, homework assignments, and the exams.

Admin: Contrast with SE430 [6/63]

SE430 teaches ``top-down'' design: requirements analysis, use cases, modeling, decomposition.

Here we work more closely with the code.

The approach is complementary to that of SE430.

If you are going to take both classes, take SE430 first.

Admin: SE450 Approach [7/63]

We advocate an ``extreme'' style:

Admin: Prerequisites [8/63]

You must have the following:

If you do not have CSC403, CSC383 or CSC393, drop now.

Useful, but optional:

Admin: Java Self-Assessment [9/63]

Do the following:

If you have:

Admin: Expectations [10/63]

The course will be conducted using Java and some of its many APIs.

I expect you to be able to work your way through the APIs without guidance from me.

The course requires that you actively engage the material on your own.

You should not only read the example code given in class, but modify and run it.

Spend at least a few hours a week playing with the examples given in class, or your own Java code.

Testimonial from a former student:

As a side note, I've spent my summer giving my final project, 'MReversi' for Spring se450, a gui, unlimited undo/redo and tcp connectivity so players can play across a network. I've also ported a version to my Java capable phone with j2me. My next step is to give the phone version tcp capability so phone players can play computer players or whatever. I could not have done this in this short time frame (at least I think it's short) if it wasn't for all the hard work we were made to do in se450. Thanks! "If it don't kill ya, it makes you stronger." :-)

Another testimonial from a former student:

Overall, I enjoyed and learned a lot in SE450. To date, it has been the most time consuming class I've taken possibly ever so you were quite accurate in your warnings early on that we students need to seriously make time.

After many years of project work and software implementations at my day job, there was one thing in particular about the final project that, whether intentional or not, very accurately represented "real world" work. That is, the ambiguity of the assignment. No software project I've been involved in has ever been in neat, clearly defined, package as many of the projects and assignments I've worked on at DePaul. Due to the complexity of what you were asking and a certain amount of open-endedness, it would have been less stressful to not have any other assignments during development of the final project, but again, real life doesn't always allow us the privilege of working on only one thing at a time.

Admin: Course Homepage [11/63]

Course homepage: http://fpl.cs.depaul.edu/jriely/450/

Admin: Mailing List [12/63]

You must subscribe to the course mailing list. Do it as soon as possible.

http://groups.google.com/group/se450fall2015

Admin: How to talk about code via email [13/63]

If you want advice about an error, send me email, doing one of the following.

If you have a problem getting a program to work and you want me to look at some of your code in more detail, send me email with the following three things.

  1. Your java file as an attachment.

  2. A description of how the output of the program is different from what you expected.

  3. The output of your program, if it runs.

Admin: Assessment [14/63]

There will be weekly assignments culminating in a final project, a midterm exam, and a final exam. The course grade will be computed as follows:

Numerical grades correspond to letter grades roughly as follows:

93-100 = A
90-92  = A-
88-89  = B+
83-87  = B
80-82  = B-        
etc...

You must hand in the homework each week in order to receive the 10 points for homework. Note, however, that I will only review homework in detail for students whose final score is borderline (say between an A- and a B+).

The midterm and final will be cumulative.

There will be no make-up exams nor extra credit assignments. If there is an extreme emergency and you must miss an exam, you must notify me in advance and provide documented evidence of the emergency.

Students in DL sections may take the course remotely. They may take the exam at times different from the in class section, usually within a few days. Exact details will be provided on COL closer to the exam date.

Homework is due by 5:30PM before class, one week after assigned (9:00PM for OL sections). No late assignments will be accepted. I will drop the lowest homework score will be dropped in the calculation of your final grade.

Homework assignments must be submitted through the online system. Email submissions will not be accepted.

Program submissions will be assessed on whether they achieve the set task and the quality of the code.

DePaul's academic integrity policy

Admin: Attendance [15/63]

You are responsible for understanding the material presented in class.

You are responsible for any announcements made in class or on the class mailing list.

You must attend the midterm and final exams (unless you are in the DL section).

A medical note will be required for an absence from exams. Business trips or vacations are not valid reasons for missing the exams.

Block out these dates now!

Online students can take exams remotely. Online dates will likely include the weekend before the in class date.

Class materials and recorded lectures are available online. Exams are proctored.

Read the policies here:

http://www.cdm.depaul.edu/onlinelearning/Pages/OnlinePolicies.aspx

If you live in Chicago, you can take the exams at the Loop or Suburban campuses. If you live outside the Chicago area, you will need to find a proctor.

Your online section is paired with an on-campus section. These classes are recorded and uploaded into the Course Management system so you can view them within 24 hours of the live class. The first class is 2015/09/15. The lecture will be available online the following day.

Admin: Projects [16/63]

This course requires that you complete a project.

The project must be your work alone.

Project work begins after the midterm.

Admin: Contact Information [17/63]

Instructor:James Riely
Home Page:http://fpl.cs.depaul.edu/jriely
Email:[email protected]
Phone:1.312.362.5251
Address: School of Computing, DePaul University
243 South Wabash Avenue
Chicago, IL 60604-2301
Office:CDM 845
Office Hours: Tue/Thu 11:40pm-12:55pm in CDM 845
Class Page:http://fpl.cs.depaul.edu/jriely/450/
Class Hours: Tue 5:45pm-9:00pm in Lewis 1511 [Section 702]
Online, Anytime [Section 711]

My coordinates:

Contact by phone:

Contact by email:

Admin: Textbooks [18/63]

See file:links-main.

Required Books

Object-Oriented Design and Patterns (2e) [Amazon, AddAll]

by Cay Horstmann (Wiley, 2005)

Online companion to the book: http://horstmann.com/design_and_patterns.html

Java2SE API

(Online)

Java Tutorial

(Online)
Required Books (Pick one of the following three)

Head First Design Patterns [Amazon, AddAll]

by Eric Freeman, Elisabeth Freeman, Kathy Sierra, Bert Bates (O'Reilly, 1995)

ADD intro to DP.

Online companion to the book: http://www.oreilly.com/catalog/hfdesignpat/

Design Patterns Explained: A New Perspective on Object-Oriented Design (2e) [Amazon, AddAll]

by Alan Shalloway, James R. Trott (Addison-Wesley, 2004)

Online companion to the book. Design Patterns Matrix. Pearson site.

Design Patterns [Amazon, AddAll]

by Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (Addison-Wesley, 1995)

Online companion to the book: http://hillside.net/elements-of-reusable-object-oriented-software-book

Most students prefer Head First Design Patterns, but some prefer Design Patterns Explained. The original Design Patterns is a classic, but out of date; it is a decent reference, but a poor book to learn from for a begginer.

Admin: Tools [19/63]

Life is much better with tools.

The big four:

See here for notes on installing eclipse and related tooles.

Basics: Subclassing [20/63]

We will discuss subclassing after the midterm.

For now, all classes we write will be direct subclasses of java.lang.Object

Basics: How Do We Run Programs? [21/63]

Several logical steps

  1. Compile -- may compile pieces of program separately, creating multiple object files and "libraries" or "archives" ("jar" file)
  2. (Static linking and initialization)
  3. Load program into memory
  4. Dynamic linking and initialization
  5. Run

The first two steps are static -- happen at compile time.

The last three steps are dynamic -- happen at runtime.

Basics: Class Initialization [22/63]

Java loads and initializes classes dynamically.

General rule: (load and) initialize class when first needed.

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package basics.classLoading;
public class Main {
  private Main() {}
  // The following is a static initializer.
  // It will execute the first time a class is used.
  static { System.out.println("Main initialized"); }
  static public void main (String[] args) {
    System.out.print("1. ");  C.f();
    System.out.print("2. ");  C.f();
    System.out.print("3. ");  D.g();
    System.out.print("4. ");  D.g();
  }
}
class C {
  private C() {}
  static { System.out.print("C initialized; "); }
  static void f() { System.out.println("C.f()"); }
}
class D {
  private D() {}
  static { System.out.print("D initialized; "); }
  static void g() { System.out.println("D.g()"); }
}

Output is:

Main initialized
1. C initialized; C.f()
2. C.f()
3. D initialized; D.g()
4. D.g()

Basics: Functions [23/63]

What is a function?

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
package basics.functions;
public class Main {
  private Main() {}
  static public void main (final String[] args) { 
    //stdlib.Trace.graphvizShowSteps (true); stdlib.Trace.run ();
    Fun.f();
    Fun.g(2);
  }
}
class Fun {
  private Fun() {}
  static void f() { System.out.println("Fun.f()"); }
  static void g(final int x) {
    System.out.println("Fun.g(" + x + ")");
    if (x>0) {
      final int y = x-1;
      Fun.g(y);
    }
  }
}

Here is a visualization of the execution at four different point (time moving from left to right).

trace-basics-functions-003-Fun_f_12 trace-basics-functions-008-Fun_g_17 trace-basics-functions-012-Fun_g_17 trace-basics-functions-015-Fun_g_19

Output is:

Fun.f()
Fun.g(2)
Fun.g(1)
Fun.g(0)
        <p>
          Here is an ascii-art version of the third timestep shown.
        </p>          
+---------------------+   +-----------------+   +-----------------+
|  1 : Main.main ARI  |   |  2 : Fun.g ARI  |   |  3 : Fun.g ARI  |
|  -----------------  |   |  -------------  |   |  -------------  |
+---------------------+   +-----------------+   +-----------------+
                          |  x:int = 2      |   |  x:int = 1      |
                          |  y:int = 1      |   |  y:int = 0      |
                          +-----------------+   +-----------------+

Basics: Activation [24/63]

Each function call (or activation) creates an instance of the function variables (including parameters).

The variables are stored in an activation record instance (ARI).

The period from activation to return is the lifetime of the ARI.

We can draw ARI's, including an instance number to distinguish them.

trace-basics-functions-003-Fun_f_12 trace-basics-functions-008-Fun_g_17 trace-basics-functions-012-Fun_g_17 trace-basics-functions-015-Fun_g_19

I've left out lots of details. These are discussed in CSC447.

Basics: Objects [25/63]

What is an object?

Basic idea: an object is something that may respond to a message.

Further: an object may have state.

The state of the object may be used in generating a reponse to a message.

If an object is mutable, its response may vary over time.

Example: a printer queue.

Basics: Object-Oriented? [26/63]

Curious fact: in a java program, there is no direct way to describe an object.

Java is class-based.

Most OO-languages are class based -- descendents of simula and smalltalk.

Prototype-based languages work differently.

Basics: Object Classes and Static Classes [27/63]

Let's distinguish two types of classes:

file:Main1.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
package basics.objectclass;
// A static class
public class Main1 {
  private Main1() {}
  static public void main (String[] args) {
    //stdlib.Trace.graphvizShowSteps (true); stdlib.Trace.run ();
    Circle1 c = new Circle1(1);
    String s = ((c==null) ? "null" : c.toString());
    System.out.println(s);
  }
}
// An object class
final class Circle1 extends Object {
  final int radius;
  public Circle1(int radius) { super(); this.radius = radius; }
  public String toString() { return "Circle(" + radius + ")"; }
}

Java allows some shorthand.

The following declaration is equivalent to the one above.

file:Main2.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
package basics.objectclass;
// A static class
public class Main2 {
  private Main2() {}
  static public void main (String[] args) {
    //stdlib.Trace.graphvizShowSteps (true); stdlib.Trace.run ();
    System.out.println(new Circle2 (1));
  }
}
// An object class
final class Circle2 extends Object {
  int radius;
  public Circle2(int radius) { this.radius = radius; }
  public String toString() { return "Circle(" + radius + ")"; }
}

Important: compilers add/remove local variables as they wish (that are not parameters). Some locals get mapped to addresses on the stack and some to registers.

Do not optimize local variables for performance!

Basics: Constructors/Methods/Fields [28/63]

Object classes have three kinds of members:

file:Main3.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
package basics.objectclass;
// A static class
public class Main3 {
  private Main3() {}
  static public void main (String[] args) {
    //stdlib.Trace.graphvizShowSteps (true); stdlib.Trace.run ();
    Circle3[] list = new Circle3[3];
    for (int i = 0; i < list.length; i++)
      list[i] = new Circle3 (i*10);
    for (Circle3 c : list)
      System.out.println(c);
  }
}
// An object class
final class Circle3 extends Object {
  int radius;
  public Circle3(int radius) { this.radius = radius; }
  public String toString() { return "Circle(" + radius + ")"; }
}
trace-basics-objectclass-005-Circle3_17 trace-basics-objectclass-006-Main3_main_9 trace-basics-objectclass-007-Main3_main_8 trace-basics-objectclass-015-Main3_main_8 trace-basics-objectclass-018-Circle3_toString_18 trace-basics-objectclass-019-Main3_main_10

Basics: Object Diagrams [29/63]

An object diagram is a snapshot of a running system, showing live objects.

Here are the ARIs when executing Circle.toString:

+---------------------+   +---------------------------+
|  0 : Main.main ARI  |   |  1 : Circle.toString ARI  |
|  -----------------  |   |  -----------------------  |
+---------------------+   +---------------------------+
|  c:Circle = c0      |   |  this:Circle = c0         |
|  s:String = null    |   +---------------------------+
+---------------------+

Note the difference between the ARIs.

The ARI for toString has a special variable, this, which identifies the object receiving the message.

This is the main difference between a static method and a non-static method.

Basics: Fields [30/63]

Fields can be used to distinguish different objects of a class.

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package basics.fields;
public class Main {
  private Main() {}
  static public void main (String[] args) {
    //stdlib.Trace.graphvizShowSteps (true); stdlib.Trace.run ();
    Int x = new Int(42);
    Int y = new Int(27);
    System.out.println(x);
    System.out.println(y);
  }
}
final class Int {
  private final int v;
  public Int(int v) { this.v = v; }
  public String toString() { return "Int(" + v + ")"; }
}

The object diagram at line 00008 is:

+--------------+   +--------------+      +---------------------+
|   i0 : Int   |   |   i1 : Int   |      |  0 : Main.main ARI  |
|   --------   |   |   --------   |      |  -----------------  |
+--------------+   +--------------+      +---------------------+
|   v:int = 42 |   |   v:int = 27 |      |  x:Int = i0         |
+--------------+   +--------------+      |  y:Int = i1         |
                                         +---------------------+

It is traditional in the UML to show objects, but not ARIs.

This can be confusing.

Although the names given to objects are arbitrary, perhaps the following are more helpful:

+--------------+   +--------------+
|    x : Int   |   |    y : Int   |
|    -------   |   |    -------   |
+--------------+   +--------------+
|   v:int = 42 |   |   v:int = 27 |
+--------------+   +--------------+

Unfortunately, this convention is not always more helpful.

trace-basics-fields-003-Int_14 trace-basics-fields-004-Main_main_6 trace-basics-fields-005-Main_main_7 trace-basics-fields-006-Int_14 trace-basics-fields-007-Main_main_7 trace-basics-fields-008-Main_main_8 trace-basics-fields-009-Int_toString_15 trace-basics-fields-010-Main_main_9 trace-basics-fields-011-Int_toString_15 trace-basics-fields-012-Main_main_10

Basics: Passing Parameters [31/63]

What does the following print?

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
package basics.swap;
public class Main {
  private Main() {}
  static public void main (String[] args) {
    //stdlib.Trace.graphvizShowSteps (true); stdlib.Trace.showBoxedPrimitivesAsPrimitive(false); stdlib.Trace.run ();
    Integer x = 42;
    Integer y = 27;
    Main.swap(x,y);
    System.out.println(x);
    System.out.println(y);
  }
  static private void swap (Integer a, Integer b) {
    Integer t = a;
    a = b;
    b = t;
  }
}

Basics: Passing Parameters [32/63]

What does the following print?

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
package basics.swap;
public class Main {
  private Main() {}
  static public void main (String[] args) {
    //stdlib.Trace.graphvizShowSteps (true); stdlib.Trace.showBoxedPrimitivesAsPrimitive(false); stdlib.Trace.run ();
    Integer x = 42;
    Integer y = 27;
    Main.swap(x,y);
    System.out.println(x);
    System.out.println(y);
  }
  static private void swap (Integer a, Integer b) {
    Integer t = a;
    a = b;
    b = t;
  }
}
trace-basics-swap-004-Main_main_8 trace-basics-swap-005-Main_swap_13 trace-basics-swap-006-Main_swap_14 trace-basics-swap-007-Main_swap_15 trace-basics-swap-008-Main_swap_16 trace-basics-swap-009-Main_main_9

Java is call-by-value.

Call by Value Links

Cup Size -- a story about variables

by JavaRanch.

Pass-by-Value Please

By JavaRanch.

Does Java pass by reference or pass by value? Why can't you swap in Java?

By Tony Sintes.

Look at the ARIs just after line 00014 is executed:

   +--------------+   +--------------+   
   | i0 : Integer |   | i1 : Integer |   
   | ------------ |   | ------------ |   
   +--------------+   +--------------+   
   |   v:int = 42 |   |   v:int = 27 |   
   +--------------+   +--------------+   
                                       
+-----------------+   +-----------------+
|  0 : Main.main  |   |  1 : Main.swap  |
|  -------------  |   |  -------------  |
+-----------------+   +-----------------+
|  x:Integer = i0 |   |  a:Integer = i1 |
|  y:Integer = i1 |   |  b:Integer = i0 |
+-----------------+   |  t:Integer = i0 |
                      +-----------------+

Output is:

42
27

Basics: Values and References [33/63]

Java has a reference model for objects. This is like C# are different from C++. In C# terminology, Java objects are boxed, whereas base values are unboxed.

What does the following print?

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
package basics.valref;
public class Main {
  private Main() {}
  static public void main (String[] args) {
    //stdlib.Trace.graphvizShowSteps (true); stdlib.Trace.run ();
    int vi = 27;  MutInt ri = new MutInt(42);
    int vj = vi;  MutInt rj = ri;
    vi += 1;      ri.plus(1);
    System.out.println(vi);
    System.out.println(vj);
    System.out.println(ri);
    System.out.println(rj);
  }
}
final class MutInt {
  private int v;
  public MutInt(int v) { this.v = v; }
  public String toString() { return "MutInt(" + v + ")"; }
  public void plus(int z) { v += z; }
}

Basics: Values and References [34/63]

Java has a reference model for objects. This is like C# are different from C++. In C# terminology, Java objects are boxed, whereas base values are unboxed.

What does the following print?

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
package basics.valref;
public class Main {
  private Main() {}
  static public void main (String[] args) {
    //stdlib.Trace.graphvizShowSteps (true); stdlib.Trace.run ();
    int vi = 27;  MutInt ri = new MutInt(42);
    int vj = vi;  MutInt rj = ri;
    vi += 1;      ri.plus(1);
    System.out.println(vi);
    System.out.println(vj);
    System.out.println(ri);
    System.out.println(rj);
  }
}
final class MutInt {
  private int v;
  public MutInt(int v) { this.v = v; }
  public String toString() { return "MutInt(" + v + ")"; }
  public void plus(int z) { v += z; }
}
trace-basics-valref-006-Main_main_8 trace-basics-valref-007-MutInt_plus_19 trace-basics-valref-008-Main_main_9

Consider the object diagram after line 00007:

   +--------------+      +-----------------+
   | i0 : MutInt  |      |  0 : Main.main  |
   | ------------ |      |  -------------  |
   +--------------+      +-----------------+
   |   v:int = 42 |      | vi:int = 27     |
   +--------------+      | vj:int = 27     |
                         | ri:MutInt = i0  |
                         | rj:MutInt = i0  |
                         +-----------------+

This phenomenon is called aliasing.

Note that aliasing occurs whenever an object is passed as an argument to a function.

Aliasing mutable values must be handled with care.

Output is:

28
27
MutInt(43)
MutInt(43)

Basics: UML Notation for References [35/63]

In the UML, references are usually shown using arrows.

Instead of this:

   +--------------+       +-----------------+
   | i0 : MutInt  |       |  0 : Main.main  |
   | ------------ |       |  -------------  |
   +--------------+       +-----------------+
   |   v:int = 42 |       | vi:int = 27     |
   +--------------+       | vj:int = 27     |
                          | ri:MutInt = i0  |
                          | rj:MutInt = i0  |
                          +-----------------+

We draw this:

   +--------------+       +-----------------+
   | i0 : MutInt  |   ri  |  0 : Main.main  |
   | ------------ | <---- |  -------------  |
   +--------------+   rj  +-----------------+
   |   v:int = 42 | <---- | vi:int = 27     |
   +--------------+       | vj:int = 27     |
                          +-----------------+

Basics: UML Class Diagrams: Static Class [36/63]

A static class looks like this:
   +----------------+
   |   <<static>>   |
   |      Main      |
   +----------------+  
   | + main():void  |
   | -------------  |
   +----------------+

Basics: UML Class Diagrams: Object Class [37/63]

An object class looks like this:
   +---------------------+
   |       MutInt        |
   +---------------------+
   | - v:int             |
   +---------------------+
   | + MutInt()          |
   | + toString():String |
   | + plus(int):void    |
   +---------------------+

A static class describes the class itself.

An object class describes the instances of the class.

Basics: Aggregation (Reference) [38/63]

file:Person.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
package basics.aggregation;
final class Person {
  //stdlib.Trace.graphvizShowSteps (true); stdlib.Trace.run ();
  final private String name;
  public Person(String name) { this.name = name; }
  public String toString() { return "Person(" + name + ")"; };
}

Reference to objects of another class:

   +--------+   name  +--------+
   | Person |<>------>| String |
   +--------+         +--------+

In UML, this is called aggregation.

Note that the aggregation refers to the instances of the class, not the class itself.

Class diagrams are static (describe the program text).

Basics: Composition (Exclusive Reference) [39/63]

file:Person.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
package basics.composition;
import java.util.Random;
final class Person {
  static private Random random = new Random();
  final  private String name;
  public Person() { name = Integer.toString(random.nextInt()); }
  //public Person(String name) { name = name.clone(); }
  public String toString() { return "Person(" + name + ")"; };
}

Exclusive reference to objects of another class:

   +--------+    name  +--------+
   | Person |<@>------>| String |
   +--------+          +--------+

In UML, this is called composition.

Consequences of composition:

(Yuck! We've mixed static and non-static members!)

Basics: Dependency [40/63]

file:Person.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
package basics.dependency;
import java.util.Random;
final class Person {
  final private String name;
  public Person(String name) { this.name = name; }
  public String toString() { return "Person(" + name + ")"; };
}
class PersonFactory {
  private PersonFactory() {}
  static private Random random = new Random();
  static public  Person randomPerson() {
    return new Person(Integer.toString(random.nextInt()));
  }
}
   +---------------+
   |   <<static>>  |   <<creates>>   +--------+
   | PersonFactory |- - - - - - - - >| Person |
   +---------------+                 +--------+

This dependency indicates that PersonFactory mentions Person, but holds no references to Person objects.

Typical reasons for a dependency:

Basics: The Ugly Truth [41/63]

The dichotomy between static-class and object-class is not enforced in java.

Guideline: separate static and object functions into different classes

Example: java.lang.Math is a static class; java.util.Random is an object class.

Note: this guideline is often violated in the java APIs. For example: java.lang.String and all the wrapper classes, such as java.lang.Integer.

Basics: Mixed Classes [42/63]

It is useful to think of a mixed class in terms of its two functions:

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
package basics.mixedclass;
public class Main {
  private Main() {}
  static public void main (final String[] args) {
    //stdlib.Trace.graphvizShowSteps (true); stdlib.Trace.run ();
    try {
      if (args.length != 1)
        throw new NumberFormatException();
      int vx = Integer.parseInt(args[0]);
      Integer rx = new Integer(vx);
      System.out.println("  Number: " + rx.toString());
      System.out.println("Hashcode: " + rx.hashCode());

    } catch (NumberFormatException e) {
      System.out.println("Error: Bad input");
    }
  }
}

Output of ``java basics/mixedclass/Main 39'' is:

  Number: 39
Hashcode: 39

Output of ``java basics/mixedclass/Main'' is:

Error: Bad input

Output of ``java basics/mixedclass/Main 39 27'' is:

Error: Bad input

Output of ``java basics/mixedclass/Main 10000000039'' is:

Error: Bad input

Basics: Multiplicity [43/63]

Multiplicities put constraints on number of references held by an object.

Recall java.util.List and java.io.BufferedReader.

   +---------------+
   | <<interface>> |       * +--------+
   |     List      |<>------>| Object |
   +---------------+         +--------+

   +----------------+   0..1 +--------+
   | BufferedReader |<>----->| Reader |
   +----------------+        +--------+

Note: if mutiplicity is 1, then reference must be non-null when constructor terminates.

This may require that we throw and IllegalArgumentException if an argument to the constructor is null.

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package basics.checkargs;
public class Main {
  private Main() {}
  static public void main (final String[] args) {
    //stdlib.Trace.graphvizShowSteps (true); stdlib.Trace.run ();
    try {
      System.out.println(new Person("bob"));
      System.out.println(new Person(null));
    } catch (IllegalArgumentException e) {
      System.out.println("Error creating Person: " + e); 
    }
  }
}
final class Person {
  final private String name;
  public Person(String name) {
    if (name == null)
      throw new IllegalArgumentException("null name");
    this.name = name;
  }
  public String toString() { return "Person(" + name + ")"; };
}
   +--------+   name  1 +--------+
   | Person |<>-------->| String |
   +--------+           +--------+

Output is:

Person(bob)
Exception in thread "main" java.lang.IllegalArgumentException
        at basics.checkargs.Person.<init>(Main.java:13)
        at basics.checkargs.Main.main(Main.java:6)

Basics: Association [44/63]

jia02-assoc-rel_fig

The diagram describes the relationship of objects without reference to implementation.

The diagram uses associations which may be either dependencies or references.

Associations are more abstract (less specific, less concrete) than dependencies and references.

The diagram also show the use of roles and names in associations.

An association without an arrowhead may be bidirectional.

Basics: UML Summary [45/63]

Reference/Exclusive Reference:

jia02-relation_fig

Dependency (no reference):

jia02-depend_fig

Association (could be either reference or dependency):

jia02-assoc_fig

Basics: Test [46/63]

final class D {
  A _xa;
  B _xb;

  D (B xb) {
   _xa = new A();
   _xb = xb;
  }
  void f(C xc) { ... }
}

Draw two diagrams showing the relationships between D and A, B and C.

  1. Be as abstract as possible.
  2. Be as concrete as possible.

Testing: Overview [47/63]

What is testing?

How Google Tests Software

What kinds of testing are there?

For us, the most important distinctions are:

Testing: Junit Tests [48/63]

Create a test class. (Test classes are object classes.)

Each test method is treated like a separate program.

import junit.framework.Assert;
import junit.framework.TestCase;
public class TEST1 extends TestCase {
  public TEST1(String name) {
    super(name);
  }
  public void testA() { ... }
  public void testB() { ... }
}

Tests are run more-or-less as:

TEST1 t1 = new TEST1("testA");
t1.setUp();
t1.testA();
t1.tearDown();

TEST1 t2 = new TEST1("testB");
t2.setUp();
t2.testB();
t2.tearDown();

Each test has one of three possible outcomes:

Rules for a test class:

Testing: Junit Assertions [49/63]

junit.framework.Assert is a static class.

The basic assertion method is assertTrue, which comes in two forms:

Assert.assertTrue(boolean):void
Assert.assertTrue(String,boolean):void

If the boolean value is false, then the enclosing test will fail.

In the second form, the String is printed when the assertion is false.

public void testA() { ...
  Assert.assertTrue(x.getColor() == Color.RED)
  Assert.assertTrue("Wrong color!", x.getColor() == Color.RED)
}

I prefer the version without the String. The messages from junit are already informative.

If there are multiple assertions in a single test, then the enclosing test fails if any assertion is false.

Testing: Junit Derived Assertions [50/63]

See the Junit API.

Assert also contains some useful derived methods:

Testing: Examples [51/63]

file:PairTEST.java [source] [doc-public] [doc-private]
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package basics.testing;

import static org.junit.Assert.*;
import org.junit.Test;
import basics.immutabledata.Pair;

public class PairTEST {
  /**
   *  Constructor should throw IllegalArgumentException if either
   *  argument is null.
   */
  @Test
  public void testConstructor() {
    try {
      new Pair<String,String>(null,"cat");
      // constructor should have thrown exception
      fail();
    } catch (IllegalArgumentException e) { }
    try {
      new Pair<String,String>("dog",null);
      // constructor should have thrown exception
      fail();
    } catch (IllegalArgumentException e) { }
    try {
      new Pair<String,String>(null,null);
      // constructor should have thrown exception
      fail();
    } catch (IllegalArgumentException e) { }
    try {
      new Pair<String,String>("dog","cat");
    } catch (IllegalArgumentException e) {
      // constructor should have succeeded
      fail();
    }
  }

  /**
   *  first() and second() should return references to the objects
   *  given to the constructor.
   */
  @Test
  public void testFirstAndSecond() {
    Integer i = 42;
    Integer j = 91;
    Pair<Integer,Integer> p = new Pair<Integer,Integer>(i, j);
    assertSame(i, p.first());
    assertSame(j, p.second());
  }

  /**
   *  Here is a weaker version of the above test; this version allows
   *  Pair() to clone its arguments.
   */
  @Test
  public void testFirstAndSecond_WeakerVersion() {
    Integer i = 42;
    Integer j = 91;
    Pair<Integer,Integer> p = new Pair<Integer,Integer>(i, j);
    assertEquals(i, p.first());
    assertEquals(j, p.second());
  }

  @Test
  public void testToString() {
    Pair<Integer,Integer> p = new Pair<Integer,Integer>(42, 91);
    assertEquals("Pair(42,91)", p.toString());
  }

  @Test
  public void testEquals() {
    Pair<Integer,Integer> p1 = new Pair<Integer,Integer>(42, 91);
    Pair<Integer,Integer> p2 = new Pair<Integer,Integer>(42, 91);
    Pair<Integer,Integer> p3 = new Pair<Integer,Integer>(43, 91);
    Pair<Integer,Integer> p4 = new Pair<Integer,Integer>(42, 92);
    assertTrue(p1.equals(p1));
    assertTrue(p1.equals(p2));
    assertFalse(p1.equals(p3));
    assertFalse(p1.equals(p4));
    assertFalse(p1.equals(new Object()));
  }

  @Test
  public void testHashCode() {
    Pair<Integer,Integer> p1 = new Pair<Integer,Integer>(42, 91);
    Pair<Integer,Integer> p2 = new Pair<Integer,Integer>(42, 91);
    Pair<Integer,Integer> p3 = new Pair<Integer,Integer>(43, 91);
    Pair<Integer,Integer> p4 = new Pair<Integer,Integer>(42, 92);
    assertEquals(p1.hashCode(), p1.hashCode());
    assertEquals(p1.hashCode(), p2.hashCode());
    assertTrue(p1.hashCode() != p3.hashCode());
    assertTrue(p1.hashCode() != p4.hashCode());
  }

  @Test
  public void testCompareTo() {
    Pair<Integer,Integer> p1 = new Pair<Integer,Integer>(42, 91);
    Pair<Integer,Integer> p2 = new Pair<Integer,Integer>(42, 91);
    Pair<Integer,Integer> p3 = new Pair<Integer,Integer>(43, 91);
    Pair<Integer,Integer> p4 = new Pair<Integer,Integer>(42, 92);
    assertTrue(0 == p1.compareTo(p1));
    assertTrue(0 == p1.compareTo(p2));
    assertTrue(0 >  p1.compareTo(p3));
    assertTrue(0 >  p1.compareTo(p4));
    assertTrue(0 == p2.compareTo(p1));
    assertTrue(0 <  p3.compareTo(p1));
    assertTrue(0 <  p4.compareTo(p1));
    // Using generic types, the following will not compile.
    // try {
    //   p1.compareTo(new Object());
    //   // compareTo should have thrown exception
    //   fail();
    // } catch (ClassCastException e) { }
  }
}

Testing: Homework 1 [52/63]

See here.

Taxonomy: Why a Taxonomy? [53/63]

Taxonomy = classification

It is useful to think of classes in terms of how they are used.

We have already seen a basic taxonomy:

Taxonomy: Object Classes [54/63]

Much of this class is about giving names to various kinds of object classes:

We now consider three kinds of object class:

Taxonomy: Immutable Data Classes [55/63]

We already have seen a basic version: Person.

Here is a more robust example:

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
package basics.immutabledata;
public class Main {
  private Main() {}
  static public void main (final String[] args) {
    //stdlib.Trace.graphvizShowSteps (true); stdlib.Trace.run ();
    System.out.println(new Pair<>(42, "dog"));
  }
}

file:Pair.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package basics.immutabledata;
final public class Pair<S extends Comparable<S>, T extends Comparable<T>>
implements Comparable<Pair<S,T>>
{
  final private S x;
  final private T y;
  public Pair(S x, T y) {
    if (x == null || y == null)
      throw new IllegalArgumentException();
    this.x = x;
    this.y = y;
  }
  public S first() { return x; }
  public T second() { return y; }
  public String toString() { return "Pair(" + x + "," + y + ")"; }
  public boolean equals(Object thatObject) {
    if (thatObject == null)
      return false;
    if (this == thatObject)
      return true;
    // equals should not throw ClassCastException
    if (!(this.getClass().equals(thatObject.getClass())))
      return false;
    @SuppressWarnings("unchecked")
    Pair<S,T> that = (Pair<S,T>) thatObject;
    return x.equals(that.x)
        && y.equals(that.y);
  }
  private int hcode;
  public int hashCode() {
    if (hcode == 0) {
      hcode = 17;
      hcode = 37*hcode + x.hashCode();
      hcode = 37*hcode + y.hashCode();
    }
    return hcode;
  }
  public int compareTo(Pair<S,T> that) {
    // compareTo may throw ClassCastException
    int ix = x.compareTo(that.x);
    if (ix != 0)
      return ix;
    return y.compareTo(that.y);
  }
}

Output is:

(42,dog)

Taxonomy: Guidelines for Immutable Data Classes [56/63]

An instance of an immutable data class is immutable if the objects passed into the constructor never change.

Pair p1 = new Pair("dog", "cat");
StringBuilder b2 = new StringBuilder("dog");
Pair p2 = new Pair(b2, "cat");
b2.append("ma");
Pair p3 = new Pair(new StringBuilder("dog"), "cat");

Note that p1 is immutable, but p2 is mutable.

What about p3?

Hashcode Links

Java Practices article

Apache HashCodeBuilder

Vipan Singla's comments on hashCode

Brian Goetz's comments on hashCode

Mark Roulo's comments on hashCode

Taxonomy: Advantages of Immutable Objects [57/63]

Immutable objects do not change.

Aliasing does not matter!

We can freely copy references without fear that the object will change.

This has several corollaries:

Guideline: make all data classes immutable

Taxonomy: The Builder Pattern [58/63]

Mutable data classes are useful for building objects incrementally.

This is called the builder pattern.

A builder is an object that is used to create an object of another class. Usually the builder is used to create exactly one other object, then discarded.

The important part of the following example is toPair, which converts the mutable class to the immutable companion class.

   +------------------------+ <<creates>>  +---------------+
   | PairBuilder            |- - - - - - ->| <<immutable>> |
   +------------------------+              |      Pair     |
   | setFirst(Object):void  |              +---------------+
   | setSecond(Object):void |
   | toPair():Pair          |
   +------------------------+

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package basics.mutablebasic;
import basics.immutabledata.Pair;
public class Main {
  private Main() {}
  static public void main (final String[] args) {
    //stdlib.Trace.graphvizShowSteps (true); stdlib.Trace.run ();
    final PairBuilder<Integer,String> pb1 = new PairBuilder<Integer,String>();
    pb1.setFirst(42);
    pb1.setSecond("dog");
    System.out.println(pb1);
    final Pair<Integer,String> p1 = pb1.toPair();
    System.out.println(p1);

    final PairBuilder<Integer,String> pb2 = new PairBuilder<Integer,String>();
    final Pair<Integer,String> p2 = pb2.toPair();
  }
}
final class PairBuilder<S extends Comparable<S>, T extends Comparable<T>> {
  private S x;
  private T y;
  public PairBuilder() { }
  public void setFirst(S x) { this.x = x; }
  public void setSecond(T y) { this.y = y; }
  public Pair<S,T> toPair() {
    if (x == null || y == null)
      throw new NullPointerException();
    return new Pair<S,T>(x,y);
  }
}

Output is:

(42,dog)
(42,dog)
Exception in thread "main" java.lang.NullPointerException
        at basics.mutabledata.MutPair.toPair(Main.java:26)
        at basics.mutabledata.Main.main(Main.java:12)

Taxonomy: A Builder in the Java APIs [59/63]

java.lang.StringBuilder is a builder for java.lang.String.

   +---------------------+ <<creates>>  +---------------+
   | StringBuilder        |- - - - - - ->| <<immutable>> |
   +---------------------+              |     String    |
   | append(String):void |              +---------------+
   | toString():String   |
   +---------------------+

Here is an example, showing the usage:

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
package basics.stringbuffer;
public class Main {
  private Main() {}
  static public void main (String[] args) {
    //stdlib.Trace.graphvizShowSteps (true); stdlib.Trace.run ();
    StringBuilder b = new StringBuilder();
    b.append("I am ");
    b.append("a dog");
    String s = b.toString();
    System.out.println(s);
    b.append("matic jerk");
    System.out.println(s);
  }
}

Taxonomy: Mutable Data Classes [60/63]

Mutable classes that are used as data are a pain.

Guidelines:

When dealing with mutable data objects, there are three choices:

  1. leave equals/hashCode alone and do not override compareTo
  2. make equals/hashCode/compareTo rely only on immutable data
  3. use dangerous equals/hashCode and hope no-one uses the objects as keys in a hash table

In general, solution 1 is probably the best one. In homework 1, I chose option 2.

file:MutPair.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package basics.mutabledata;
// Do not override equals or hashCode for mutable data.
// Do not make mutable data Comparable.
final public class MutPair<S,T>
implements Cloneable
{
  private S x;
  private T y;
  public MutPair() { }
  public MutPair(S x, T y) { this.x = x; this.y = y; }
  public void setFirst(S x) { this.x = x; }
  public void setSecond(T y) { this.y = y; }
  public S first() { return x; }
  public T second() { return y; }
  public String toString() { return "MutPair(" + x + "," + y + ")"; }
  // Clone does not play nice with generics because it returns Object.
  // Deep clone is difficult to implement generically because:
  // (1) cast to MutPair is allowed, not to MutPair<S,T>; we need the
  //     latter in order to get at the fields.
  // (2) Cloneable type does not have public clone method, so we
  //     cannot call x.clone() or y.clone(); this would be true even
  //     if we had "MutPair<S extends Cloneable,T extends Cloneable>"
  public Object clone() {
    try {
      return super.clone();
    } catch (CloneNotSupportedException e) {
      throw new InternalError();
    }
  }
}

Taxonomy: Hashing Mutable Data is Dangerous [61/63]

It is possible to lose mutable values in a hashtable.

java.util.Date is mutable and overrides hashCode.

file:basics/hashmutable/Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package basics.hashmutable;
import java.util.Date;
import java.util.Hashtable;
// example from http://www.vipan.com/htdocs/hashcode_help.html
public class Main {
  public static void main (String[] args) {
    //stdlib.Trace.graphvizShowSteps (true); stdlib.Trace.run ();

    Hashtable<Date,String> map = new Hashtable<Date,String>();
    long time = System.currentTimeMillis();
    Date dt1 = new Date(time);
    Date dt2 = new Date(time);
    map.put(dt1, "blah");

    System.out.println("dt1.toString() = " + dt1.toString());
    System.out.println("dt2.toString() = " + dt2.toString());
    System.out.println("Is dt2.equals(dt)? = " + dt2.equals(dt1));
    System.out.println("map.get(dt1) = " + map.get(dt1) );
    System.out.println("map.get(dt2) = " + map.get(dt2) );

    // Change dt by adding a day to its time
    dt1.setTime(time + 24*60*60*1000L);

    System.out.println("\nAfter dt.setTime(newTime):");
    System.out.println("dt1.toString() = " + dt1.toString());
    System.out.println("dt2.toString() = " + dt2.toString());
    System.out.println("Is dt2.equals(dt)? = " + dt2.equals(dt1));
    System.out.println("map.get(dt1) = " + map.get(dt1) );
    System.out.println("map.get(dt2) = " + map.get(dt2) );

    //System.out.println("\nmap = " + map.toString() );
  }
}
trace-basics-hashmutable-009-Main_main_17 trace-basics-hashmutable-015-Main_main_26

Output is:

dt1.toString() = Thu Jan 03 12:46:33 EST 2002
dt2.toString() = Thu Jan 03 12:46:33 EST 2002
Is dt2.equals(dt)? = true
map.get(dt1) = blah
map.get(dt2) = blah

After dt1.setTime(newTime):
dt1.toString() = Fri Jan 04 12:46:33 EST 2002
dt2.toString() = Thu Jan 03 12:46:33 EST 2002
Is dt2.equals(dt)? = false
map.get(dt1) = null
map.get(dt2) = null

map = {Fri Jan 04 12:46:33 EST 2002=blah}

Taxonomy: Collection Classes [62/63]

A collection may hold references to many other objects.

Usually the objects are of some uniform type, eg, a list of dogs, a set of colors.

The java.util provides several collection classes. (See the java tutorial.)

We may implement our own collections. Usually these make use of the java.util classes.

In the following example, IntegerStack delgates most of its responsibilities to List.

file:IntegerStack.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
package basics.collection;
import java.util.List;
import java.util.ArrayList;
import java.util.EmptyStackException;
final public class IntegerStack {
  final private List<Integer> l;
  public IntegerStack() { l = new ArrayList<Integer>(); }
  public boolean isEmpty() { return l.isEmpty(); }
  public void push(Integer x) {
    if (x == null)
      throw new IllegalArgumentException();
    l.add(x);
  }
  public Integer pop() {
    if (l.isEmpty())
      throw new EmptyStackException();
    return l.remove(l.size()-1);
  }
}

Taxonomy: UML representation of Collections [63/63]

Here is a good, abstract drawing:

                           +--------------+     *  +---------+
                           | IntegerStack |<>----->| Integer |
                           +--------------+        +---------+

Here is a drawing showing the implementation:

   +--------------+  1     +--------------+     *  +---------+
   |    List      |<----<@>| IntegerStack |<>----->| Integer |
   +--------------+        +--------------+        +---------+

More concrete:

   +--------------+  1     +--------------+     *  +---------+
   |  ArrayList   |<----<@>| IntegerStack |<>----->| Integer |
   +--------------+        +--------------+        +---------+

Too concrete:

   +--------------+     1  +--------------+     *  +---------+
   | IntegerStack |<@>---->|  ArrayList   |<>----->| Integer |
   +--------------+        +--------------+        +---------+

This last diagram obscures the fact that IntegerStack is a collection of Integers.


Revised: 2008/09/10 16:46