Computer Science 15-110, Fall 2009
Class Notes:  More Writing Classes


  1. Instance Methods
    1. Variable-Length Parameter Lists
  2. Object Methods
    1. hashCode
  3. Static Members
    1. Static Variables
      1. Example:  Counting instances
  4. Sortable Objects (Comparable + compareTo)
    1. New class instances are not inherently "sortable"
    2. "Sortable" == Implements Comparable Interface
  5. Examples
    1. Sortable Fractions
    2. Sortable Mutable Integers
    3. Sortable Names (Sort by last name, then first name)

More Writing Classes

  1. Instance Methods
    1. 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();
        }
      }

       
  2. Object Methods
    1. 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;
        }
      }
       
  3. Static Members
    1. Static Variables
      1. 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");
          }
        }
         
  4. 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);
        }
      }

       
  5. 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