Computer Science 15-100, Summer 2009
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

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);
        }
      }

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