Computer Science 15-110, Spring 2010
Class Notes:  Writing Classes


  1. Sample Code
  2. Classes Lexicon
  3. Instance Variables
    1. Instance Variables + Scope
    2. Constants + "final"
    3. Initialization + Default Values (null, 0, false)
    4. Shadowing + "this"
  4. Instance Methods
    1. Methods Lexicon
    2. Method Overloading
      1. For Type-Specific Processing
      2. For Default Values
    3. Variable-Length Parameter Lists
    4. Constructors
      1. Default Constructor
      2. Custom (Non-Default) Constructor
      3. Custom (Non-Default) Constructor -> No Default Constructor
      4. Multiple Constructors
        1. Duplicating Code
        2. Sharing Code with "this" constructor
        3. Sharing Code with "init" Method
    5. Encapsulation (Data Hiding)
      1. Accessors (getters -- safely get state)
      2. Mutators (setters -- safely set state)
    6. Object Methods
      1. toString
      2. equals
      3. hashCode
  5. Static Members
    1. Static Constants
    2. Static Variables
      1. Example:  Random instance
      2. Example:  Counting instances
    3. Static Methods
    4. Scope of Static and Instance Members
  6. Arrays of Objects
  7. Sortable Objects (Comparable + compareTo)
    1. New class instances are not inherently "sortable"
    2. "Sortable" == Implements Comparable Interface
  8. Examples
    1. Sortable Fractions
    2. Sortable Mutable Integers
    3. Sortable Names (Sort by last name, then first name)

Writing Classes

  1. Sample Code
    See Getting Started with Writing Classes
     
  2. Classes Lexicon
    Fields == Variables (and Constants) == Data == State == Properties == Attributes
    Methods == Functions == Procedures == Commands == Behaviors == Operations
    Class Members == Fields + Methods
    API == Application Programming Interface == Public Methods
    Class == API (Public Methods) + Private State + Private Helper Methods
    Object == Instance
     
  3. Instance Variables
    1. Instance Variables + Scope
      class Demo {
        private int x = 5;

        public void foo() {
          System.out.println("foo can see that x = " + x);
        }

        public void ack() {
          x++;
          System.out.println("ack just changed x to " + x);
        }

        public void bar() {
          System.out.println("bar can also see that x = " + x);
        }

        public static void main(String[] args) {
          Demo d = new Demo();
          d.foo();
          d.ack();
          d.bar();
        }
      }
       
    2. Constants + "final"
      public static final int SOME_CONSTANT = 5;
       
    3. Initialization + Default Values (null, 0, false)
      class Demo {
        private int x;     // uninitialized, assigned default value
        private String s;  // ditto
        private boolean b; // ditto
        private int y = 42;

        public Demo() {
          System.out.println("Instance variables with default values:");
          System.out.println(" x = " + x);
          System.out.println(" s = " + s);
          System.out.println(" b = " + b);
          System.out.println("And an initialized instance variable:");
          System.out.println(" y = " + y);
        }

        public static void main(String[] args) {
          new Demo();
        }
      }
       
    4. Shadowing + "this"
      class Demo {
        private int x = 5;

        public void foo(int x) {
          this.x += x;
        }

        public void printX() {
          System.out.println("x = " + x);
        }

        public static void main(String[] args) {
          Demo d = new Demo();
          d.printX();
          d.foo(10);
          d.printX();
        }
      }

       
  4. Instance Methods
    1. Methods Lexicon
      Method Signature == Method Name + Formal Parameters
      Method Header == Visibility + Type + Signature (Method Name + Formal Parameters)
      Method == Method Declaration == Method Header + Body
      Method Call == Method Invocation == Object Reference + "." + Method Name + Actual Parameters (or Arguments)
       
    2. Method Overloading
      1. For Type-Specific Processing
        class Demo {
          private int x = 5;

          public void printX() {
            System.out.println("x = " + x);
          }

          public void add(int dx) {
            System.out.println("adding int dx = " + dx);
            x += dx;
          }

          public void add(String s) {
            System.out.println("adding String s = " + s);
            add(Integer.parseInt(s));
          }

          public static void main(String[] args) {
            Demo d = new Demo();
            d.printX();
            d.add(10);
            d.printX();
            d.add("42");
            d.printX();
          }
        }
         
      2. For Default Values
        class Demo {
          private int x = 5;
          private static final int DEFAULT_ADD_VALUE = 42;

          public void printX() {
            System.out.println("x = " + x);
          }

          public void add(int dx) {
            System.out.println("adding int dx = " + dx);
            x += dx;
          }

          public void add() {
            System.out.println("adding default value = " + DEFAULT_ADD_VALUE);
            add(DEFAULT_ADD_VALUE);
          }

          public static void main(String[] args) {
            Demo d = new Demo();
            d.printX();
            d.add(10);
            d.printX();
            d.add();
            d.printX();
          }
        }

         
    3. Variable-Length Parameter Lists
      import java.util.Arrays;
      class Demo {
        private int x = 5;

        public void printX() {
          System.out.println("x = " + x);
        }

        public void add(int dx) {
          System.out.println("adding int dx = " + dx);
          x += dx;
        }

        public void add(int... a) {
          System.out.println("adding these values: " + Arrays.toString(a));
          for (int i=0; i<a.length; i++)
            add(a[i]);
        }

        public static void main(String[] args) {
          Demo d = new Demo();
          d.printX();
          d.add(10);
          d.printX();
          d.add(2,4,6);
          d.printX();
        }
      }

       
    4. Constructors
      1. Default Constructor
        class Demo {
          private int x = 5;

          public void printX() {
            System.out.println("x = " + x);
          }

          public static void main(String[] args) {
            Demo d = new Demo();
            System.out.println("Default constructor called!");
            d.printX();
          }
        }

        Which is basically the same as:

        class Demo {
          private int x = 5;

          public void printX() {
            System.out.println("x = " + x);
          }

          public Demo() {
          }

          public static void main(String[] args) {
            Demo d = new Demo();
            System.out.println("Explicit (non-default) constructor called!");
            d.printX();
          }
        }

         
      2. Custom (Non-Default) Constructor
        class Demo {
          private int x = 5;

          public void printX() {
            System.out.println("x = " + x);
          }

          public Demo(int x) {
            this.x = x;
          }

          public static void main(String[] args) {
            Demo d = new Demo(42);
            d.printX();
          }
        }

         
      3. Custom (Non-Default) Constructor -> No Default Constructor
        class Demo {
          private int x = 5;

          public void printX() {
            System.out.println("x = " + x);
          }

          public Demo(int x) {
            this.x = x;
          }

          public static void main(String[] args) {
            Demo d = new Demo()// will not compile!
            d.printX();
          }
        }

         
      4. Multiple Constructors
        1. Duplicating Code
          class Demo {
            private int x;

            public void printX() {
              System.out.println("x = " + x);
            }

            public Demo(int x) {
              System.out.println("Constructor:  x = " + x);
              this.x = x;
            }

            public Demo() {
              System.out.println("Constructor: no parameter, setting x = 42");
              this.x = 42;
            }

            public static void main(String[] args) {
              Demo d1 = new Demo();
              d1.printX();
              Demo d2 = new Demo(999);
              d2.printX();
            }
          }

           
        2. Sharing Code with "this" constructor
          class Demo {
            private int x;

            public void printX() {
              System.out.println("x = " + x);
            }

            public Demo(int x) {
              System.out.println("Constructor:  x = " + x);
              this.x = x;
            }

            public Demo() {
              this(42);
            }

            public static void main(String[] args) {
              Demo d1 = new Demo();
              d1.printX();
              Demo d2 = new Demo(999);
              d2.printX();
            }
          }


          But be sure "this" call is on first line!
            public Demo() {
              System.out.println("Call other constructor"); // will not compile!
              this(42);
            }
           
        3. Sharing Code with "init" Method
          class Demo {
            private int x;

            public void printX() {
              System.out.println("x = " + x);
            }

            public Demo(int x) {
              System.out.println("Constructor:  x = " + x);
              init(x);
            }

            public Demo() {
              System.out.println("Constructor: no parameter, setting x = 42");
              init(42);
            }

            private void init(int x) {
              System.out.println("In init, setting x = " + x);
              this.x = x;
            }

            public static void main(String[] args) {
              Demo d1 = new Demo();
              d1.printX();
              Demo d2 = new Demo(999);
              d2.printX();
            }
          }

           
    5. Encapsulation (Data Hiding)
      1. Accessors (getters -- safely get state)
        class Demo {
          private int[] a;

          public Demo() {
            a = null;
          }

          public Demo(int... a) {
            this.a = a;
          }

          // accessor (safely gets length, even if a is null)
          public int getLength() {
            return ((a == null) ? 0 : a.length);
          }

          public static void main(String[] args) {
            Demo d1 = new Demo();
            System.out.println(d1.getLength());
            Demo d2 = new Demo(1,2,3);
            System.out.println(d2.getLength());
          }
        }
         
      2. Mutators (setters -- safely set state)
        class Demo {
          private double x, y;
          private double d; // d is distance from (0,0) to (x,y)

          public Demo(double x, double y) {
            this.x = x;
            this.y = y;
            setDistance();
          }

          private void setDistance() {
            this.d = Math.sqrt(Math.pow(this.x,2) + Math.pow(this.y,2));
          }

          // mutator (safely maintains "d" when setting "x")
          public void setX(double x) {
            this.x = x;
            setDistance();
          }

          public String toString() {
            return "Demo(x=" + x + ",y=" + y + ",d=" + d + ")";
          }

          public static void main(String[] args) {
            Demo d = new Demo(5,12);
            System.out.println(d);
            d.setX(35);
            System.out.println(d);
          }
        }

         
    6. Object Methods
      1. toString
        class Demo {
          public static void main(String[] args) {
            System.out.println("With no toString method:");
            System.out.println(new Pair1(1,2));

            System.out.println("With a not-very-good toString method:");
            System.out.println(new Pair2(1,2));

            System.out.println("With a better toString method:");
            System.out.println(new Pair3(1,2));

            System.out.println("Or perhaps...");
            System.out.println(new Pair4(1,2));
          }
        }

        class Pair1 {
          private int x, y;
          public Pair1(int x, int y) { this.x = x; this.y = y; }
        }

        class Pair2 {
          private int x, y;
          public Pair2(int x, int y) { this.x = x; this.y = y; }
          public String toString() {
            return x + "," + y;
          }
        }

        class Pair3 {
          private int x, y;
          public Pair3(int x, int y) { this.x = x; this.y = y; }
          public String toString() {
            return "Pair3(" + x + "," + y + ")";
          }
        }

        class Pair4 {
          private int x, y;
          public Pair4(int x, int y) { this.x = x; this.y = y; }
          public String toString() {
            return "Pair4(x=" + x + ",y=" + y + ")";
          }
        }
         
      2. equals
        class Demo {
          public static void main(String[] args) {
            System.out.print("With no equals method: ");
            Pair1 p1a = new Pair1(1,2);
            Pair1 p1b = new Pair1(1,2);
            System.out.println(p1a.equals(p1b));

            System.out.print("With an equals method: ");
            Pair2 p2a = new Pair2(1,2);
            Pair2 p2b = new Pair2(1,2);
            System.out.println(p2a.equals(p2b));

            // Also show that unlike objects and null work, too
            System.out.println(p2a.equals("some string"));
            System.out.println(p2a.equals(null));
          }
        }

        class Pair1 {
          private int x, y;
          public Pair1(int x, int y) { this.x = x; this.y = y; }
        }

        class Pair2 {
          private int x, y;
          public Pair2(int x, int y) { this.x = x; this.y = y; }

          public boolean equals(Object object) {
            if (!(object instanceof Pair2) || (object == null))
              return false;
            Pair2 that = (Pair2) object;
            return ((this.x == that.x) &&
                    (this.y == that.y));
          }
        }

         
      3. hashCode
        // Simple "good-enough" general-purpose hashCode implementation
        public int hashCode() {
          int code = 1;
          Object[] state = { array of instance variables };
          for (int i=0; i<state.length; i++)
            code = 31*code + ((state[i] == null) ? 0 : state[i].hashCode());
          return code;
        }

        And a compelling example:

        import java.util.HashSet;
        class Demo {
          public static void main(String[] args) {
            System.out.println("To show why we need to write our own hashCode");
            System.out.println("method when we write our own equals method,");
            System.out.println("this demo uses a HashSet, which we have not");
            System.out.println("yet learned about.  Ask your instructor...");
            System.out.println();

            System.out.print("With no hashCode method: ");
            Pair1 p1a = new Pair1(1,2);
            Pair1 p1b = new Pair1(1,2);
            HashSet<Pair1> set1 = new HashSet<Pair1>();
            set1.add(p1a);
            System.out.println(set1.contains(p1b));

            System.out.print("With a hashCode method: ");
            Pair2 p2a = new Pair2(1,2);
            Pair2 p2b = new Pair2(1,2);
            HashSet<Pair2> set2 = new HashSet<Pair2>();
            set2.add(p2a);
            System.out.println(set2.contains(p2b));
          }
        }

        class Pair1 {
          private int x, y;
          public Pair1(int x, int y) { this.x = x; this.y = y; }
          public boolean equals(Object object) {
            Pair1 that = (Pair1) object;
            return ((this.x == that.x) && (this.y == that.y));
          }
        }

        class Pair2 {
          private int x, y;
          public Pair2(int x, int y) { this.x = x; this.y = y; }
          public boolean equals(Object object) {
            Pair2 that = (Pair2) object;
            return ((this.x == that.x) && (this.y == that.y));
          }

          // Simple "good-enough" general-purpose hashCode implementation
          public int hashCode() {
            int code = 1;
            Object[] state = { x, y };
            for (int i=0; i<state.length; i++)
              code = 31*code + ((state[i] == null) ? 0 : state[i].hashCode());
            return code;
          }
        }
         
  5. Static Members
    1. Static Constants
      public final static int CLUBS = 1;
      public final static int DIAMONDS = 2;
      public final static int HEARTS = 3;
      public final static int SPADES = 4;
       
    2. Static Variables
      1. Example:  Random instance
        // Create one shared random instance, rather than
        // needlessly creating one-per-instance
        public static Random random = new Random();

         
      2. Example:  Counting instances
        class Demo {
          private static int instances = 0;

          public Demo(String name) {
            Demo.instances++;
            System.out.println("Creating instance #" + instances +
                               " (name=" + name + ")");
          }

          public static void main(String[] args) {
            new Demo("fred");
            new Demo("wilma");
            new Demo("barney");
          }
        }


        Note that this compiles without the "Demo." qualifier in the constructor:

        class Demo {
          private static int instances = 0;

          public Demo(String name) {
            instances++;
            System.out.println("Creating instance #" + instances +
                               " (name=" + name + ")");
          }

          public static void main(String[] args) {
            new Demo("fred");
            new Demo("wilma");
            new Demo("barney");
          }
        }

         
    3. Static Methods
      class Demo {
        private String s;

        public Demo(String s) { this.s = s; }

        public void add(char c) {
          s += c;
        }

        public int getCharCount(char c) {
          return getCharCount(s, c);
        }

        // This helper method is private because it is not part of the API.
        // It is static because it does not require an object reference.
        private static int getCharCount(String s, char c) {
          if (s == null) return 0;
          int count = 1;
          for (int i=0; i<s.length(); i++)
            if (s.charAt(i) == c)
              count++;
          return count;
        }

        public static void main(String[] args) {
          Demo d = new Demo("abacab");
          System.out.println(d.getCharCount('b'));
          d.add('b');
          System.out.println(d.getCharCount('b'));
        }
      }
       
    4. Scope of Static and Instance Members
       
      1. Instance Members Can See Static Members
        class Demo {
          private static int x = 5;
          private int w = 3;

          private static void g() {
            System.out.println("In static method g....");
            System.out.println(" can see static variable x = " + x);
          }

          private void f() {
            System.out.println("In instance method f....");
            System.out.println(" can see instance variable w = " + w);
            System.out.println(" can see static variable x = " + x);
            System.out.println(" and can call static method g....");
            g();
          }

          public static void main(String[] args) {
            Demo d = new Demo();
            d.f();
          }
        }

         
      2. Static Members Cannot See Instance Members
        class Demo {
          private static int x = 5;
          private int w = 3;

          private static void g() {
            System.out.println("In static method g....");
            System.out.println(" can see static variable x = " + x);
            // static methods cannot see instance methods
            f();  // will not compile
            // and static methods cannot see instance variables
            System.out.println(w);  // will not compile
          }

          private void f() {
            System.out.println("In instance method f....");
            System.out.println(" can see instance variable w = " + w);
            System.out.println(" can see static variable x = " + x);
            System.out.println(" and can call static method g....");
            g();
          }

          public static void main(String[] args) {
            Demo d = new Demo();
            d.f();
          }
        }
         
  6. Arrays of Objects

    import java.util.Arrays;
    class Demo {
      public static void main(String[] args) {
        // Declare and allocate array
        Pair[] pairs = new Pair[5];

        // Confirm that it is initialized with null values
        System.out.println(Arrays.toString(pairs));

        // Now load the array with Pair instances
        for (int i=0; i<pairs.length; i++)
          pairs[i] = new Pair(i,10*i);

        // And print the values out again
        System.out.println(Arrays.toString(pairs));

        // Now use a statically-allocated array of Pairs:
        pairs = new Pair[]{ new Pair(42, 43), new Pair(44, 45) };

        // And print the values out yet again
        System.out.println(Arrays.toString(pairs));
      }
    }

    class Pair {
      private int x, y;
      public Pair(int x, int y) { this.x = x; this.y = y; }
      public String toString() {
        return "Pair(x=" + x + ",y=" + y + ")";
      }
    }
     
  7. Sortable Objects (Comparable + compareTo)
     
    1. New class instances are not inherently "sortable"
      import java.util.*;
      class Demo {
        public static void main(String[] args) {
          // Declare, allocate, and load array with (somewhat) random pairs
          Random random = new Random();
          Pair[] pairs = new Pair[10];
          for (int i=0; i<pairs.length; i++)
            pairs[i] = new Pair(i%3, random.nextInt(100));

          // And print the unsorted values
          System.out.println(Arrays.toString(pairs));

          // Now sort them
          Arrays.sort(pairs);  // compiles but does not run correctly.
                               // Then again, how could it?  How could Java know
                               // how to sort an array of Pair instances?

          // And print the sorted values
          System.out.println(Arrays.toString(pairs));
        }
      }

      class Pair {
        private int x, y;
        public Pair(int x, int y) { this.x = x; this.y = y; }
        public String toString() {
          // abbreviated for this example
          return "(" + x + "," + y + ")";
        }
      }
       
    2. "Sortable" == Implements Comparable Interface
      import java.util.*;
      class Demo {
        public static void main(String[] args) {
          // Declare, allocate, and load array with (somewhat) random pairs
          Random random = new Random();
          Pair[] pairs = new Pair[10];
          for (int i=0; i<pairs.length; i++)
            pairs[i] = new Pair(i%3, random.nextInt(100));

          // And print the unsorted values
          System.out.println(Arrays.toString(pairs));

          // Now sort them
          Arrays.sort(pairs);

          // And print the sorted values
          System.out.println(Arrays.toString(pairs));
        }
      }

      class Pair implements Comparable {
        private int x, y;
        public Pair(int x, int y) { this.x = x; this.y = y; }
        public String toString() {
          // abbreviated for this example
          return "(" + x + "," + y + ")";
        }

        // Must specify how to compare two Pair instances
        public int compareTo(Object object) {
          Pair that = (Pair) object;
          // Let's sort by x first, and in a tie, then by y
          if (this.x != that.x)
            return (this.x - that.x);
          else
           return (this.y - that.y);
        }
      }

       
  8. Examples (Written in class)
     
    1. Sortable Fractions
      // This is an excerpt of the Fraction class from
      // "Getting Started with Writing Classes", adapted here
      // to be sortable (by implementing the Comparable interface).
      
      import java.util.*;
      
      class Fraction implements Comparable {
        public static void main(String[] args) {
          Fraction[] a = { new Fraction(3,4), new Fraction(1,3), new Fraction(2,3),
                           new Fraction(5,12),new Fraction(7,12), new Fraction(1,2) };
          System.out.println(Arrays.toString(a));
          Arrays.sort(a);
          System.out.println(Arrays.toString(a));
        }
      
        // compareTo method (for Comparable interface)
        public int compareTo(Object object) {
          Fraction that = (Fraction)object;
          return (int)Math.signum(this.getDoubleValue() - that.getDoubleValue());
        }
      
        public double getDoubleValue() {
          return 1.0 * this.num / this.den;
        }
      
        // Instance variables
        private int num, den;
      
        public Fraction(int num, int den) {
          // handle the sign -- only the num can be negative
          if (den < 0) {
            den = -den;
            num = -num;
          }
          // and assign to the instance variables
          this.num = num;
          this.den = den;
          // reduce them
          int gcd = gcd(num, den);
          if (gcd > 0) {
            this.num /= gcd;
            this.den /= gcd;
          }
        }
      
        private static int gcd(int x, int y) {
          x = Math.abs(x);
          y = Math.abs(y);
          if ((x == 0) || (y == 0)) return 0;
          while (y != 0) {
            int r = x % y;
            x = y;
            y = r;
          }
          return x;
        }
      
        public String toString() {
          if (den == 0)
            return "NaF"; // Not A Fraction
          else if (num == 0)
            return "0";
          else if (den == 1)
            return ("" + num);
          else
            return num + "/" + den;
        }
      }
    2. Sortable Mutable Integers
      // SortableMutableInteger.java
      // Similar to the Integer class, only the value is settable.
      // Check out the test method for more details.
      
      import java.util.*;
      class SortableMutableInteger implements Comparable {
      
        public static void main(String[] args) {
          testSortableMutableIntegerClass();
        }
      
        public static void testSortableMutableIntegerClass() {
          SortableMutableInteger[] a1 = { smi(3), smi(5), smi(1), smi(4), smi(2) };
          System.out.println("Demonstrate that sorting works:");
          System.out.println("  unsorted: " + Arrays.toString(a1));
          Arrays.sort(a1);
          System.out.println("  sorted:   " + Arrays.toString(a1));
      
          System.out.println("Testing SortableMutableInteger class... ");
      
          // verify a1 is now sorted
          SortableMutableInteger[] a2 = { smi(1), smi(2), smi(3), smi(4), smi(5) };
          assert(Arrays.equals(a1, a2));
      
          // verify we can modify an element in a1 (it is mutable)
          assert(a1[0].getValue() == 1);
          a1[0].setValue(6);
          assert(a1[0].getValue() == 6);
          Arrays.sort(a1);
          SortableMutableInteger[] a3 = { smi(2), smi(3), smi(4), smi(5), smi(6) };
          assert(Arrays.equals(a1, a3));
          System.out.println("Passed all tests!");
        }
      
        // just an abbreviation for "new SortableMutableInteger(i)"
        public static SortableMutableInteger smi(int i) {
          return new SortableMutableInteger(i);
        }
      
        // Instance variable
        private int intValue;
      
        // constructor
        public SortableMutableInteger(int intValue) {
          this.intValue = intValue;
        }
      
        // accessor
        public int getValue() {
          return intValue;
        }
      
        // mutator
        public void setValue(int intValue) {
          this.intValue = intValue;
        }
      
        // toString
        public String toString() {
          return "SMI(" + intValue + ")";
        }
      
        // compareTo method (for Comparable interface)
        public int compareTo(Object object) {
          SortableMutableInteger that = (SortableMutableInteger) object;
          return this.intValue - that.intValue;
        }
      
        // equals
        public boolean equals(Object object) {
          SortableMutableInteger that = (SortableMutableInteger) object;
          return this.intValue == that.intValue;
        }
      
        // hashCode
        // Simple "good-enough" general-purpose hashCode implementation
        public int hashCode() {
          int code = 1;
          Object[] state = { this.intValue };
          for (int i=0; i<state.length; i++)
            code = 31*code + ((state[i] == null) ? 0 : state[i].hashCode());
          return code;
        }
      }
    3. Sortable Names (Sort by last name, then first name)
      // SortableName.java
      // A "name" is a "first name" and "last name", and these
      // are sortable by last-name-first.
      // Check out the test method for more details.
      
      import java.util.*;
      class SortableName implements Comparable {
      
        public static void main(String[] args) {
          testSortableName();
        }
      
        public static void testSortableName() {
          SortableName[] a1 = { sn("Grace Hopper"),sn("Charles Babbage"), sn("Grass Hopper"),
                                sn("Ada Lovelace"), sn("Eco Turing"), sn("Alan Turing") };
          System.out.println("Demonstrate that sorting works:");
          System.out.println("  unsorted: " + Arrays.toString(a1));
          Arrays.sort(a1);
          System.out.println("  sorted:   " + Arrays.toString(a1));
      
          System.out.println("Testing SortableName class... ");
      
          // verify a1 is now sorted
          SortableName[] a2 = { sn("Charles Babbage"), sn("Grace Hopper"), sn("Grass Hopper"),
                                sn("Ada Lovelace"), sn("Alan Turing"), sn("Eco Turing") };
          assert(Arrays.equals(a1, a2));
      
          // verify we can modify an element in a1 (it is mutable)
          assert(a1[0].getFirstName().equals("Charles"));
          assert(a1[0].getLastName().equals("Babbage"));
          a1[0].setFirstName("Blaise");
          a1[0].setLastName("Pascal");
          assert(a1[0].getFirstName().equals("Blaise"));
          assert(a1[0].getLastName().equals("Pascal"));
          Arrays.sort(a1);
          SortableName[] a3 = { sn("Grace Hopper"), sn("Grass Hopper"), sn("Ada Lovelace"),
                                sn("Blaise Pascal"), sn("Alan Turing"), sn("Eco Turing") };
          assert(Arrays.equals(a1, a3));
          System.out.println("Passed all tests!");
        }
      
        // just an abbreviation for "new SortableName(fullName)"
        public static SortableName sn(String fullName) {
          return new SortableName(fullName);
        }
      
        // Instance variables
        private String firstName, lastName;
      
        // constructor
        public SortableName(String fullName) {
          // simplified for demo purposes (does not handle cases like
          // "Madonna" or "Robert Louis Stevenson").
          int i = fullName.indexOf(' ');
          this.firstName = fullName.substring(0, i);
          this.lastName = fullName.substring(i+1);
        }
      
        // accessors
        public String getFirstName() {
          return firstName;
        }
      
        public String getLastName() {
          return lastName;
        }
      
        // mutators
        public void setFirstName(String firstName) {
          this.firstName = firstName;
        }
      
        public void setLastName(String lastName) {
          this.lastName = lastName;
        }
      
        // toString
        public String toString() {
          return "SN(" + firstName + " " + lastName + ")";
        }
      
        // compareTo method (for Comparable interface)
        public int compareTo(Object object) {
          SortableName that = (SortableName) object;
          // compare last names first
          int result = this.lastName.compareTo(that.lastName);
          if (result == 0)
            result = this.firstName.compareTo(that.firstName);
          return result;
        }
      
        // equals
        public boolean equals(Object object) {
          SortableName that = (SortableName) object;
          return (this.compareTo(that) == 0);
        }
      
        // hashCode
        // Simple "good-enough" general-purpose hashCode implementation
        public int hashCode() {
          int code = 1;
          Object[] state = { this.firstName, this.lastName };
          for (int i=0; i<state.length; i++)
            code = 31*code + ((state[i] == null) ? 0 : state[i].hashCode());
          return code;
        }
      }

carpe diem   -   carpe diem   -   carpe diem   -   carpe diem   -   carpe diem   -   carpe diem   -   carpe diem   -   carpe diem   -   carpe diem