SE450: Lecture 8 (Object Creation)

Contents [0/32]

Constructors: Object Construction [1/32]
Constructors: this constructors [2/32]
Constructors: super constructors [3/32]
Clone: Deep versus Shallow [4/32]
Clone: Clone is magic [5/32]
Clone: clone() [6/32]
Clone: Full examples [7/32]
Clone: Prototype Pattern [8/32]
Serialization: Basics [9/32]
Serialization: Dealing with references [10/32]
Serialization: Transient Fields [11/32]
Serialization: Customization [12/32]
Serialization: Forbidding Serialization [13/32]
Singleton: Static class [14/32]
Singleton: (Polymorphic) Singleton [15/32]
Singleton: Lazy Instantiation [16/32]
Singleton: Often the singleton is public [17/32]
Singleton: Often the singleton is public [18/32]
Singleton: Often the singleton is public [19/32]
Singleton: Often the singleton is public [20/32]
Singleton: Be careful not to use a singleton it is created [21/32]
Singleton: Cloning and serialization [22/32]
Enumerations: Type-safe Enumeration [23/32]
Enumerations: Enumeration with polymorphic methods [24/32]
Factory: Shapes [25/32]
Factory: Static Factory Method [26/32]
Factory: Benefit [27/32]
Factory: Static Factory Class [28/32]
Factory: Polymorphic Factory Class [29/32]
Factory: Polymorphic Factory Method [30/32]
Factory: Overdesign? [31/32]
Flyweighting a factory: Keeping object references unique [32/32]

Constructors: Object Construction [1/32]

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
package subclass.constructor;
class Main {
  public static void main(String[] argv) {
    new X();
    new X();
  }
}
class O {
  int i, j;
  { i = 42; System.err.println("i"); }
  O() { System.err.println("O"); }
  { j = 27; System.err.println("j"); }
}
class X extends O {
  int k;
  X() { System.err.println("X"); }
  { k = 27; System.err.println("k"); }
}

Don't let the "this" reference escape during construction

Constructors: this constructors [2/32]

this constructors [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
package subclass.ex8;

public class M {
  public static void main(String[] argv) {
    new B();
    new C();
  }
}
class A {
  A()      {System.out.println("A()");}
}
class B extends A {
  B() {
    this(1);
    System.out.println("B()");
  }
  B(int x) {System.out.println("B(int)");}
}
class C extends B {
  C()      {System.out.println("C()");}
}

Constructors: super constructors [3/32]

super constructors [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
package subclass.ex7;

public class M {
  public static void main(String[] argv) {
    new B(1);
    new C();
  }
}
class A {
  A(int i) {System.out.println("A(int)");}
}
class B extends A {
  B(int i) {
    super(i);
    System.out.println("B(int)");
  }
}
class C extends B {
  // The following will not compile!
  //  C() {System.out.println("C");}
  C() {
    super(0);
    System.out.println("C");
  }
}

Clone: Deep versus Shallow [4/32]

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
30
31
32
33
34
35
package clone.deepVshallow;
public class Main {
  public static void main(String[] argv) throws CloneNotSupportedException {
    Node w = new Node(1, new Node(2, new Node(3)));
    Node v = w;
    Node x = (Node) w.shallow_copy();
    Node y = (Node) w.deep_copy();
    Node z = (Node) w.shallow_clone();

    System.out.println("w==v: " + ((w == v) ? "true" : "false"));
    System.out.println("w==x: " + ((w == x) ? "true" : "false"));
    System.out.println("w==y: " + ((w == y) ? "true" : "false"));
    System.out.println("w==z: " + ((w == z) ? "true" : "false"));
    System.out.println("w.equals(v): " + ((w.equals(v)) ? "true" : "false"));
    System.out.println("w.equals(x): " + ((w.equals(x)) ? "true" : "false"));
    System.out.println("w.equals(y): " + ((w.equals(y)) ? "true" : "false"));
    System.out.println("w.equals(z): " + ((w.equals(z)) ? "true" : "false"));
    System.out.println("w.shallow_equals(v): " +
        ((w.shallow_equals(v)) ? "true" : "false"));
    System.out.println("w.shallow_equals(x): " +
        ((w.shallow_equals(x)) ? "true" : "false"));
    System.out.println("w.shallow_equals(y): " +
        ((w.shallow_equals(y)) ? "true" : "false"));
    System.out.println("w.shallow_equals(z): " +
        ((w.shallow_equals(z)) ? "true" : "false"));
    System.out.println("w.deep_equals(v): " +
        ((w.deep_equals(v)) ? "true" : "false"));
    System.out.println("w.deep_equals(x): " +
        ((w.deep_equals(x)) ? "true" : "false"));
    System.out.println("w.deep_equals(y): " +
        ((w.deep_equals(y)) ? "true" : "false"));
    System.out.println("w.deep_equals(z): " +
        ((w.deep_equals(z)) ? "true" : "false"));
  }
}

file:Node.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
package clone.deepVshallow;
class Node implements Cloneable {
  private int i;
  private Node next;

  public Node(int i, Node next) { this.i = i; this.next = next; }
  public Node(int i)            { this(i,null); }

  public Object shallow_copy() {
    return new Node(i, next);
  }

  public Object shallow_clone() throws CloneNotSupportedException {
    return super.clone();
  }

  public boolean shallow_equals(Object o) {
    if (!(this.getClass().equals(o.getClass())))
      return false;
    Node that = (Node) o;
    return (i == that.i) && (next == that.next);
  }

  public Object deep_copy() {
    Node next_copy = (next==null) ? null : (Node) next.deep_copy();
    return new Node(i, next_copy);
  }

  public Object deep_clone() throws CloneNotSupportedException {
    Node result = (Node) super.clone();
    result.next = (next==null) ? null : (Node) next.deep_clone();
    return result;
  }

  public boolean deep_equals(Object o) {
    if (!(this.getClass().equals(o.getClass())))
      return false;
    Node that = (Node) o;
    return (i == that.i)
        && ((next==null) ? (that.next==null) : next.deep_equals(that.next));
  }
}

Clone: Clone is magic [5/32]

An alternative is to use a copy method or copy constructor, but these do have the same behavior with respect to subclassing.

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
30
31
32
33
package clone.magic;
public class Main {
  public static void main(String[] argv) throws CloneNotSupportedException {
    B x = new B(42,27);
    System.out.println(x);
    System.out.println(new A(x));
    System.out.println(x.copy());
    System.out.println(x.clone());
  }
}

class A implements Cloneable {
  int i;
  public A(int i) { this.i = i; }

  // A copy constructor
  public A(A that) { this.i = that.i; }

  // A copy method
  public Object copy() { return new A(i); }

  // The clone method
  public Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
  public String toString() { return "A("+i+")"; }
}

class B extends A {
  int j;
  public B(int i, int j) { super(i); this.j = j; }
  public String toString() { return "B("+i+","+j+")"; }
}

Output is:

B(42,27)
A(42)
A(42)
B(42,27)

Clone: clone() [6/32]

The intent is that

x.clone() != x

x.clone().getClass() == x.getClass()

x.clone().equals(x)

The last intent is not a requirement, but is generally true.

By convention, you obtain the returned object by calling super.clone(). If all superclasses do this, then x.clone().getClass() == x.getClass() will be true.

Object doesn't implement Cloneable itself. (Object.clone is protected.)

The Cloneable interface is meant to be a mixin interface, however it doesn't do a very good job of this.

Parts of Java that support cloning:

You can:

Clone: Full examples [7/32]

To disallow cloning, make you class final and do not override clone().

public final class A { ... }

If your class is non-final and public, you either need to expect that a subclass will implement clone, or disallow cloning:

public class A {
  ...
  protected final Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException();
  }
}

To implement cloning:

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
20
21
22
23
24
25
26
package clone.stack;
public final class IntegerStack implements Cloneable {
  private int[] buffer;
  private int top;

  public IntegerStack(int maxContents) {
    buffer = new int[maxContents];
    top = -1;
  }
  public void push(int val) {
    buffer[++top] = val;
  }
  public int pop() {
    return buffer[top--];
  }
  public Object clone() {
    try {
      IntegerStack result = (IntegerStack) super.clone();
      result.buffer = buffer.clone();
      return result;
    } catch (CloneNotSupportedException e) {
      // cannot happen
      throw new RuntimeException(e);
    }
  }
}

Clone: Prototype Pattern [8/32]

For mutable objects, one might consider implementing a factory by cloning prototype objects, rather than calling a constructor each time.

Here is a static factory using constructors: dir:factory/person [source]

Here it is refactored to use prototypes: dir:clone/prototype [source]

In most cases, the constructor version is preferable in Java.

Serialization: Basics [9/32]

An object which implements the Serializable interface can be written and read from an OutputStream and InputStream.

These stream objects must be wrapped inside either an ObjectOutputStream or ObjectInputStream.

These classes contain methods writeObject(Object):void and readObject():Object.

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
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package serialization;
import java.io.*;

public class Main1 {
  public static void main(String args[]) {
    try {
      ObjectOutputStream os
      = new ObjectOutputStream (new FileOutputStream("out.dat"));
      os.writeObject(new Entry("Save Me", 1));
      os.close();

      ObjectInputStream is
      = new ObjectInputStream (new FileInputStream("out.dat"));
      Object o = is.readObject();
      is.close();

      Entry e = (Entry) o;
      System.out.println("Entry restored from file is: " + e.toString());
    } catch (Exception e) { e.printStackTrace(); }
  }
}

class Entry implements Serializable {
  private static final long serialVersionUID = 2008L;
  private String message =  "";
  private int messageNumber = 0;

  public Entry(String message, int messageNumber) {
    this.message = message;
    this.messageNumber = messageNumber;
  }
  public String getMessage() {
    return message;
  }
  public int getMessageNumber() {
    return messageNumber;
  }
  public String toString() {
    return message +  " " + Integer.toString(messageNumber);
  }
}

Serialization: Dealing with references [10/32]

Objects will usually contain references to other objects.

Serialization writes out all referenced objects (and any objects they reference).

Effectively, it writes out the whole graphs of objects.

All referenced classes must be serializable for this to work.

file:Main2.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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package serialization;
import java.io.*;

public class Main2 {
  public static void main(String args[]) {
    try {
      Person person = new Person("Matt", 30);
      PersonEntry entry = new PersonEntry(person, 1);
      ObjectOutputStream os
      = new ObjectOutputStream (new FileOutputStream("out.dat"));
      os.writeObject(entry);
      os.close();

      ObjectInputStream is
      = new ObjectInputStream (new FileInputStream("out.dat"));
      Object o = is.readObject();
      is.close();

      PersonEntry entry2 = (PersonEntry) o;
      System.out.println("Entry restored from file is" + entry2.toString());
    } catch (Exception e) { e.printStackTrace(); }
  }
}

class PersonEntry implements Serializable {
  private static final long serialVersionUID = 2008L;
  private Person person = null;
  private int personNumber = 0;

  public PersonEntry(Person person, int personNumber) {
    this.person = person;
    this.personNumber = personNumber;
  }
  public Person getPerson() {
    return person;
  }
  public int getPersonNumber() {
    return personNumber;
  }
  public String toString() {
    return person.toString() +  " Number " + Integer.toString(personNumber);
  }
}

@SuppressWarnings("serial")
class Person implements Serializable {
  private String name = "";
  private int age = 0;

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }
  public String getName() {
    return name;
  }
  public int getAge() {
    return age;
  }
  public String toString() {
    return "Name: " + name + " Age: " + Integer.toString(age);
  }
}

Serialization: Transient Fields [11/32]

If fields reference a non-serializable object, declare those fields transient.

Transient fields are not written when the object is serialized (written).

Transient fields are set to null when the object is deserialized (read).

Serialization: Customization [12/32]

Control over which refered objects are stored and how they are reconstructed.

Implement the Externalizable interface. This interface allows you to provide the methods writeExternal(ObjectOutput):void and readExternal(ObjectOutput).

You must provide a no-argument constructor as this is called when readExternal() is used to reconstruct objects.

file:Person2.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
package serialization;
import java.io.*;

class Person2 implements Externalizable {
  private static final long serialVersionUID = 2008L;
  private String name = ""; private int age = 0;
  public Person2() { }
  public Person2(String name, int age) { this.name = name; this.age = age;}
  public String getName() { return name; }
  public int getAge() { return age; }
  public String toString() {return "Name: " + name + " Age: " + Integer.toString(age);}

  public void writeExternal(ObjectOutput out)
      throws IOException
  {
    out.writeObject(name);
    out.writeInt(age);
  }
  public void readExternal(ObjectInput in)
      throws IOException, ClassNotFoundException
  {
    name = (String) in.readObject();
    age = in.readInt();
  }
}

Serialization: Forbidding Serialization [13/32]

Mark your class as final and do not implement Serializable.

Singleton: Static class [14/32]

file:S.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
package singleton.staticClass;
public class S {
  static private int i;

  private S() {}
  static public int inc() { return i++; }
}

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
package singleton.staticClass.main;
import singleton.staticClass.S;
public class Main {
  public static void main (String[] args) {
    System.out.println(S.inc());
    System.out.println(S.inc());
  }
}

Singleton: (Polymorphic) Singleton [15/32]

file:S.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
package singleton.state;
public class S {
  private S() {}
  static private SState state;
  static {
    if ("linux".equals(System.getProperty("os.name"))) {
      state = new SLinux();
    } else {
      state = new SOther();
    }
  }
  static public int inc() { return state.inc(); }

  static private interface SState {
    public int inc();
  }
  static private class SLinux implements SState {
    private int i;
    public int inc() {return ++i;}
  }
  static private class SOther implements SState {
    private int i;
    public int inc() {return --i;}
  }
}

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
package singleton.state.main;
import singleton.state.S;
public class Main {
  public static void main (String[] args) {
    System.out.println(S.inc());
    System.out.println(S.inc());
  }
}

Singleton: Lazy Instantiation [16/32]

file:S.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
package singleton.lazy;
public class S {
  private S() {}
  static private SState state;
  static public int inc() {
    if (state == null) {
      if ("linux".equals(System.getProperty("os.name"))) {
        state = new SLinux();
      } else {
        state = new SOther();
      }
    }
    return state.inc();
  }

  static private interface SState {
    public int inc();
  }
  static private class SLinux implements SState {
    private int i;
    public int inc() {return ++i;}
  }
  static private class SOther implements SState {
    private int i;
    public int inc() {return --i;}
  }
}

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
package singleton.lazy.main;
import singleton.lazy.S;
public class Main {
  public static void main (String[] args) {
    System.out.println(S.inc());
    System.out.println(S.inc());
  }
}

In dynamically initialized languages, lazy instantiation is usually silly.

Singleton: Often the singleton is public [17/32]

file:S.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
package singleton.pub;
public interface S {
  public static final S instance =
      ("linux".equals(System.getProperty("os.name"))) ? new SLinux() : new SOther();
      public int inc();
}
final class SLinux implements S {
  private int i;
  public int inc() {return ++i;}
}
final class SOther implements S {
  private int i;
  public int inc() {return --i;}
}

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
package singleton.pub.main;
import singleton.pub.S;
public class Main {
  public static void main (String[] args) {
    S s = S.instance;
    System.out.println(s.inc());
    System.out.println(s.inc());
  }
}

If you make the singleton field public, then it should be final.

If a field is final, then it must be assigned in the initializer.

Singleton: Often the singleton is public [18/32]

file:S.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
package singleton.pub2;
public interface S {
  public static final S instance = SFactory.newS();
  public int inc();
}
class SFactory {
  static S newS() {
    if ("linux".equals(System.getProperty("os.name")))
      return new SLinux();
    else
      return new SOther();
  }
}
final class SLinux implements S {
  private int i;
  public int inc() {return ++i;}
}
final class SOther implements S {
  private int i;
  public int inc() {return --i;}
}

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
package singleton.pub2.main;
import singleton.pub2.S;
public class Main {
  public static void main (String[] args) {
    S s = S.instance;
    System.out.println(s.inc());
    System.out.println(s.inc());
  }
}

Sometimes you can't do everything in an initializer expression. In this case, you may need a static method.

Singleton: Often the singleton is public [19/32]

file:S.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 singleton.pub3;
public abstract class S {
  private static S newS() {
    if ("linux".equals(System.getProperty("os.name")))
      return new SLinux();
    else
      return new SOther();
  }
  public static final S instance = newS();
  public abstract int inc();
}
final class SLinux extends S {
  private int i;
  public int inc() {return ++i;}
}
final class SOther extends S {
  private int i;
  public int inc() {return --i;}
}

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
package singleton.pub3.main;
import singleton.pub3.S;
public class Main {
  public static void main (String[] args) {
    S s = S.instance;
    System.out.println(s.inc());
    System.out.println(s.inc());
  }
}

It's a bit more natural using an abstract class.

(I dislike java's restrictions on static members in interfaces.)

Singleton: Often the singleton is public [20/32]

file:S.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 singleton.pub4;
public abstract class S {
  private static S instance;
  static {
    if ("linux".equals(System.getProperty("os.name")))
      instance = new SLinux();
    else
      instance = new SOther();
  }
  public static S get() {return instance;}
  public abstract int inc();
}
final class SLinux extends S {
  private int i;
  public int inc() {return ++i;}
}
final class SOther extends S {
  private int i;
  public int inc() {return --i;}
}

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
package singleton.pub4.main;
import singleton.pub4.S;
public class Main {
  public static void main (String[] args) {
    S s = S.get();
    System.out.println(s.inc());
    System.out.println(s.inc());
  }
}

You can also use a public method rather than a public field.

Singleton: Be careful not to use a singleton it is created [21/32]

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
30
31
32
33
34
package singleton.classloading;

/**
 * This example shows that you need to be careful to ensure that
 * singletons are not accessed before they are created!
 */
class S {
  private static S instance;
  public static int numInstances;
  public static S getInstance() {
    if (instance == null) {
      instance = new S();
    }
    return instance;
  }

  private S() {
    S.numInstances++;
    B.doNothing();  // B is loaded here, during A's constructor
  }
}

class B {
  // B caches the only instance of S
  public static S a = S.getInstance();
  public static void doNothing() {}
}

class Main {
  public static void main(String[] args) {
    System.out.println(S.getInstance() == B.a); // false!
    System.out.println(S.numInstances);         // 2!
  }
}

Singleton: Cloning and serialization [22/32]

Dissallow cloning in singleton classes.

final class SLinux implements S {
  // ...
  private Object readResolve() throws java.io.ObjectStreamException {
    return SObject.get();
  }
}
final class SOther implements S {
  // ...
  private Object readResolve() throws java.io.ObjectStreamException {
    return SObject.get();
  }
}

readResolve() is called after an object is deserialized.

Normally readResolve() cleans up the object a little and returns this, but here we want it to throw away the deserialized object.

Enumerations: Type-safe Enumeration [23/32]

See http://java.sun.com/j2se/1.5.0/docs/guide/language/enums.html

Enums are java.lang.Comparable and java.io.Serializable.

file:Card.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
package enumeration2;

import java.util.ArrayList;
import java.util.List;

public class Card {
  public enum Rank { DEUCE, THREE, FOUR, FIVE, SIX,
    SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE }

  public enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES }

  private final Rank rank;
  private final Suit suit;
  private Card(Rank rank, Suit suit) {
    this.rank = rank;
    this.suit = suit;
  }

  public Rank rank() { return rank; }
  public Suit suit() { return suit; }
  public String toString() { return rank + " of " + suit; }

  private static final List<Card> protoDeck = new ArrayList<Card>();

  // Initialize prototype deck
  static {
    for (Suit suit : Suit.values())
      for (Rank rank : Rank.values())
        protoDeck.add(new Card(rank, suit));
  }

  public static List<Card> newDeck() {
    return new ArrayList<Card>(protoDeck); // Return copy of prototype deck
  }
}

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
package enumeration2;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
  private Main() {}
  public static void main(String args[]) {
    int numHands = Integer.parseInt(args[0]);
    int cardsPerHand = Integer.parseInt(args[1]);
    List<Card> deck  = Card.newDeck();
    Collections.shuffle(deck);
    for (int i=0; i < numHands; i++)
      System.out.println(deal(deck, cardsPerHand));
  }

  public static List<Card> deal(List<Card> deck, int n) {
    int deckSize = deck.size();
    List<Card> handView = deck.subList(deckSize-n, deckSize);
    List<Card> hand = new ArrayList<Card>(handView);
    handView.clear();
    return hand;
  }
}

Here are Java 1.4 versions:

file:Suit.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
46
47
48
49
50
package enumeration;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;


// Ordinal-based typesafe enum (From Bloch)
//
// No need to override equals, since only one instance of each suit.
// Note that instanceNum and name are instance fields.
// Note that cNumInstances is a class field.
// Note that the order of the constant definitions is important.
// Note that VALUES is an immutable collection.
// Java arrays are always mutable :-(
public final class Suit implements Comparable<Suit> {
  // Number of instances
  private static int cNumInstances = 0;

  // Ordinal for this instance
  private final int instanceNum;

  // Name of Suit
  private final String name;

  // Private constructor: All instances created in the class
  private Suit(String name) {
    this.name = name;
    this.instanceNum = Suit.cNumInstances++;
  }

  public String toString() {
    return name;
  }

  public int compareTo(Suit that) {
    return this.instanceNum - that.instanceNum;
  }

  public static final Suit CLUBS = new Suit("clubs");
  public static final Suit DIAMONDS = new Suit("diamonds");
  public static final Suit HEARTS = new Suit("hearts");
  public static final Suit SPADES = new Suit("spades");

  public static final List<Suit> VALUES;
  static {
    Suit[] values = { CLUBS, DIAMONDS, HEARTS, SPADES };
    VALUES = Collections.unmodifiableList(Arrays.asList(values));
  }
}

file:Rank.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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package enumeration;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;


// Ordinal-based typesafe enum (From Bloch)
//
// No need to override equals, since only one instance of each rank.
// Note that instanceNum and name are instance fields.
// Note that cNumInstances is a class field.
// Note that the order of the constant definitions is important.
// Note that VALUES is an immutable collection.
// Java arrays are always mutable :-(
public final class Rank implements Comparable<Rank> {
  // Number of instances
  private static int cNumInstances = 0;

  // Ordinal for this instance
  private final int instanceNum;

  // Name of Rank
  private final String name;

  // Private constructor: All instances created in the class
  private Rank(String name) {
    this.name = name;
    instanceNum = Rank.cNumInstances++;
  }

  public String toString() {
    return name;
  }

  public int compareTo(Rank that) {
    return this.instanceNum - that.instanceNum;
  }

  public int getValue() {
    if (this == ACE_HIGH) {
      return 1;
    } else {
      return instanceNum + 1;
    }
  }

  public static final Rank ACE_LOW = new Rank("ace");
  public static final Rank TWO = new Rank("two");
  public static final Rank THREE = new Rank("three");
  public static final Rank FOUR = new Rank("four");
  public static final Rank FIVE = new Rank("five");
  public static final Rank SIX = new Rank("six");
  public static final Rank SEVEN = new Rank("seven");
  public static final Rank EIGHT = new Rank("eight");
  public static final Rank NINE = new Rank("nine");
  public static final Rank TEN = new Rank("ten");
  public static final Rank JACK = new Rank("jack");
  public static final Rank QUEEN = new Rank("queen");
  public static final Rank KING = new Rank("king");
  public static final Rank ACE_HIGH = new Rank("ace");
  public static final List<Rank> VALUES;
  static {
    Rank[] values = {
        ACE_LOW, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE_HIGH
    };
    VALUES = Collections.unmodifiableList(Arrays.asList(values));
  }
}

file:Card.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
46
47
48
49
50
51
52
53
54
55
56
57
package enumeration;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


// Typesafe enum (From Bloch)
//
public final class Card {
  // Rank of Card
  private final Rank rank;

  // Suit of Card
  private final Suit suit;

  // Private constructor: All instances created in the class
  private Card(Rank rank, Suit suit) {
    this.rank = rank;
    this.suit = suit;
  }

  public String toString() {
    return rank + " of " + suit;
  }

  public int compareRank(Card c) {
    return rank.compareTo(c.rank);
  }

  public int compareSuit(Card c) {
    return suit.compareTo(c.suit);
  }

  public Rank getRank() {
    return rank;
  }

  public int getRankValue() {
    return rank.getValue();
  }

  public Suit getSuit() {
    return suit;
  }

  public static final List<Card> VALUES;
  static {
    List<Card> values = new ArrayList<Card>(56);
    for (Suit s : Suit.VALUES) {
      for (Rank r : Rank.VALUES) {
        values.add(new Card(r, s));
      }
    }
    VALUES = Collections.unmodifiableList(values);
  }
}

Enumerations: Enumeration with polymorphic methods [24/32]

Recall the code we had for expression trees: file:ExprFactory.java [source] [doc-public] [doc-private]

file:Op.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
package enumeration2;

public enum Op {
  ADD("+") { public int eval(int x, int y) { return x + y; } },
  SUB("-") { public int eval(int x, int y) { return x - y; } },
  MUL("*") { public int eval(int x, int y) { return x * y; } },
  DIV("/") { public int eval(int x, int y) { return x / y; } };

  private final String name;
  private Op(String name) { this.name = name; }

  public String toString() { return name; }
  public abstract int eval(int x, int y);
}

file:ExprFactory.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 enumeration2;

public class ExprFactory {
  private ExprFactory() {}
  static public Expr newConst(int v) {
    return new Const(v);
  }
  static public Expr newPlus(Expr l, Expr r) {
    return new BinOp(l, Op.ADD, r);
  }
  static public Expr newMinus(Expr l, Expr r) {
    return new BinOp(l, Op.SUB, r);
  }
  static public Expr newMult(Expr l, Expr r) {
    return new BinOp(l, Op.MUL, r);
  }
  static public Expr newQuot(Expr l, Expr r) {
    return new BinOp(l, Op.DIV, r);
  }
}

Here is the Java 1.4 version:

file:Op.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
package enumeration;

public abstract class Op {
  private final String name;
  Op(String name) { this.name = name; }
  public String toString() { return name; }

  public abstract int eval(int x, int y);

  public static final Op ADD = new OpAdd();
  public static final Op SUB = new OpSub();
  public static final Op MUL = new OpMul();
  public static final Op DIV = new OpDiv();
}

final class OpAdd extends Op {
  OpAdd() {  super("+"); }
  public int eval(int x, int y) { return x+y; }
}
final class OpSub extends Op {
  OpSub() {  super("-"); }
  public int eval(int x, int y) { return x-y; }
}
final class OpMul extends Op {
  OpMul() {  super("*"); }
  public int eval(int x, int y) { return x*y; }
}
final class OpDiv extends Op {
  OpDiv() {  super("/"); }
  public int eval(int x, int y) { return x/y; }
}

Factory: Shapes [25/32]

The simplest version exports the concrete class names.

file:Shape.java [source] [doc-public] [doc-private]
01
02
03
04
05
package factory.shape1;
import java.awt.Graphics;
public interface Shape {
  void paint(Graphics g);
}

file:Circle.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
package factory.shape1;
import java.awt.Graphics;
public class Circle implements Shape {
  public void paint(Graphics g) { /* ... */ }
  // ...
}

file:Square.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
package factory.shape1;
import java.awt.Graphics;
public class Square implements Shape {
  public void paint(Graphics g) { /* ... */ }
  // ...
}

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
package factory.shape1.main;
import factory.shape1.Shape;
import factory.shape1.Square;
import factory.shape1.Circle;
public class Main {
  public static void main (String[] args) {
    Shape[] a = new Shape[2];
    a[0] = new Circle();
    a[1] = new Square();
  }
}

Factory: Static Factory Method [26/32]

A factory allows the concrete class names to remain hidden.

file:Shape.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
package factory.shape2;
import java.awt.Graphics;
public interface Shape {
  void paint(Graphics g);
}
class Circle implements Shape {
  public void paint(Graphics g) { /* ... */ }
  // ...
}
class Square implements Shape {
  public void paint(Graphics g) { /* ... */ }
  // ...
}

file:ShapeFactory.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
package factory.shape2;
public class ShapeFactory {
  private ShapeFactory() {}
  static public Shape newInstance(String selector) {
    if ("Circle".equals(selector)) return new Circle();
    if ("Square".equals(selector)) return new Square();
    throw new IllegalArgumentException();
  }
}

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
package factory.shape2.main;
import factory.shape2.Shape;
import factory.shape2.ShapeFactory;
public class Main {
  public static void main (String[] args) {
    Shape[] a = new Shape[2];
    a[0] = ShapeFactory.newInstance("Circle");
    a[1] = ShapeFactory.newInstance("Square");
  }
}

Factory: Benefit [27/32]

Changing names does not affect client code.

file:Shape.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
package factory.shape3;
import java.awt.Graphics;
public interface Shape {
  void paint(Graphics g);
}
class Ellipse implements Shape {
  public void paint(Graphics g) { /* ... */ }
  // ...
}
class Rectangle implements Shape {
  public void paint(Graphics g) { /* ... */ }
  // ...
}

file:ShapeFactory.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
package factory.shape3;
public class ShapeFactory {
  private ShapeFactory() {}
  static public Shape newInstance(String selector) {
    if ("Ellipse".equals(selector))   return new Ellipse();
    if ("Circle".equals(selector))    return new Ellipse();
    if ("Rectangle".equals(selector)) return new Rectangle();
    if ("Square".equals(selector))    return new Rectangle();
    throw new IllegalArgumentException();
  }
}

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
package factory.shape3.main;
import factory.shape3.Shape;
import factory.shape3.ShapeFactory;
public class Main {
  public static void main (String[] args) {
    Shape[] a = new Shape[2];
    a[0] = ShapeFactory.newInstance("Circle");
    a[1] = ShapeFactory.newInstance("Square");
  }
}

Factory: Static Factory Class [28/32]

An alternative is to use multiple factory methods.

file:Shape.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
package factory.shape4;
import java.awt.Graphics;
public interface Shape {
  void paint(Graphics g);
}
class Ellipse implements Shape {
  Ellipse(int radius1, int radius2) { /* ... */ }
  public void paint(Graphics g) { /* ... */ }
  // ...
}
class Rectangle implements Shape {
  Rectangle(int height, int width) { /* ... */ }
  public void paint(Graphics g) { /* ... */ }
  // ...
}

file:ShapeFactory.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package factory.shape4;
public class ShapeFactory {
  private ShapeFactory() {}
  static public Shape newEllipse(int radius1, int radius2) {
    return new Ellipse(radius1, radius2);
  }
  static public Shape newCircle(int radius) {
    return new Ellipse(radius, radius);
  }
  static public Shape newRectangle(int height, int width) {
    return new Rectangle(height, width);
  }
  static public Shape newSquare(int height) {
    return new Rectangle(height, height);
  }
}

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
package factory.shape4.main;
import factory.shape4.Shape;
import factory.shape4.ShapeFactory;
public class Main {
  public static void main (String[] args) {
    Shape[] a = new Shape[2];
    a[0] = ShapeFactory.newCircle(1);
    a[1] = ShapeFactory.newSquare(1);
  }
}

Factory: Polymorphic Factory Class [29/32]

This is a Polymorphic version of the Static Factory Class described erlier.

For example, an abstract factory is used to create GUI widgets appropriate for linux (on linux machine) or windows (on a windows machine).

Abstract Factories are almost always Singletons.

This is called Abstract Factory by GoF.

Factory: Polymorphic Factory Method [30/32]

The object created depends upon the actual type of the creator.

This is called Factory Method by GoF.

The polymorphic factory method pattern is used to connect two hierarchies. A good example is java.util.Collection and java.util.Iterator.

Here is a silly example, using triples rather than general collections:

file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
package factory.factorymethod;
public class Main {
  public static void main (String[] args) {
    Triple<String> t1 = new FieldTriple<String>("dog", "cat", "pig");
    Triple<String> t2 = new ArrayTriple<String>("daisy", "rose", "tulip");
    for (String s : t1)
      System.out.println(s);
    for (String s : t2)
      System.out.println(s);
  }
}

file:Triple.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
46
47
package factory.factorymethod;
import java.util.Iterator;
import java.util.ArrayList;
interface Triple<E> extends Iterable<E> { }
class FieldTriple<E> implements Triple<E> {
  private E one; E two; E three;
  public FieldTriple(E one, E two, E three) {
    this.one = one; this.two = two; this.three = three;
  }
  public Iterator<E> iterator() { return new TheIterator(); }

  private class TheIterator implements Iterator<E> {
    private int i;
    public boolean hasNext() { return i < 3; }
    public E next() {
      i++;
      switch (i) {
      case 1: return one;
      case 2: return two;
      case 3: return three;
      }
      throw new java.util.NoSuchElementException();
    }
    public void remove() { throw new UnsupportedOperationException(); }
  }
}
// Arrays do not play nicely with generics, so use ArrayList
class ArrayTriple<E> implements Triple<E> {
  private ArrayList<E> a = new ArrayList<E>();
  public ArrayTriple(E one, E two, E three) {
    a.add(0,one); a.add(1,two); a.add(2,three);
  }
  public Iterator<E> iterator() { return new TheIterator(); }

  private class TheIterator implements Iterator<E> {
    private int i = -1;
    public boolean hasNext() { return i < 2; }
    public E next() {
      i++;
      if (i <= 2)
        return a.get(i);
      else
        throw new java.util.NoSuchElementException();
    }
    public void remove() { throw new UnsupportedOperationException(); }
  }
}

Factory: Overdesign? [31/32]

RequestProcessorFactoryFactory

Flyweighting a factory: Keeping object references unique [32/32]

It is sometimes an irritation to have more than one copy of an immutable object.

If we only had one copy of each object, for example, we could use == instead of equals.

This is sometimes refered to as a hash cons, or hash consing (LISP terminology).

file:Data.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
package flyweight;
import java.util.HashMap;

public class Data {
  private Data() {}
  private static HashMap<Integer,Video> hashmap = new HashMap<Integer,Video>();

  private static int hash3 (Object key1, Object key2, Object key3) {
    return key1.hashCode () + 5 * key2.hashCode () + 13 * key3.hashCode ();
  }

  /**
   * Creates and manages flyweight objects. Ensures that flyweights
   * are shared properly.  When a client requests a flyweight, the
   * Flyweight Factory object supplies an existing instance or
   * creates one, if none exists.
   */
  static public Video getVideo(String title, int year, String director) {
    if (  (title == null)
        || (director == null)
        || (year <= 1800)
        || (year >= 5000)) {
      throw new IllegalArgumentException();
    }
    title = title.trim();
    director = director.trim();
    if (  ("".equals(title))
        || ("".equals(director))) {
      throw new IllegalArgumentException();
    }
    Integer key = hash3(title, year, director);
    Video v = hashmap.get(key);
    if (   (v == null)
        || !(v.title().equals(title))
        || (v.year() != year)
        || !(v.title().equals(title))) {
      v = new VideoObj(title, year, director);
      hashmap.put(key, v);
    }
    return v;
  }
}


Revised: 2008/03/11 14:51