Object Design Workshop by Bill Venners
Designing with Types
Lecture Handout
Agenda
-
Prefer dynamic binding to
instanceof
and downcasting.
-
Use
instanceof
and downcasting to ask "Can you do something
for me."
-
Prefer compile-time type information to run-time class information.
instanceof
and Downcasting
The OO Way
Why Dynamic Binding?
-
Polymorphism and dynamic binding enable you to write code that doesn't need
to know about (isn't coupled to) subtypes.
-
Because the JVM does dynamic binding automatically, you don't have to use
mention subclasses in
instanceof
expressions
-
Thus it's easier to add new subclasses
Discussion
-
Guideline: Prefer dynamic binding to
instanceof
and
downcasting.
Can you dance()
?
-
Guideline: Use
instanceof
and downcasting to ask
"Can you do something for me."
-
Primary use of
instanceof
is to find out whether you can
perform some kind of operation on an object
-
This is necessary when a subtype extends (adds more methods to) the
interface of the supertype
1 // In file ex5/Hippopotamus.java
2
3 class Hippopotamus {
4
5 void sing() {
6 System.out.println("Hippo singing!");
7 }
8 }
9
1 // In file ex5/DancingHippopotamus.java
2
3 class DancingHippopotamus
4 extends Hippopotamus {
5
6 void dance() {
7 System.out.println("Hippo dancing!");
8 }
9 }
1 // In file ex5/Example5.java
2 import java.util.ArrayList;
3 import java.util.Collection;
4 import java.util.Iterator;
5
6 class Example5 {
7
8 public static void main(String[] args) {
9
10 makeHippoPerform(new Hippopotamus());
11 makeHippoPerform(new DancingHippopotamus());
12 }
13
14 // Client must pass a Hippopotamus
15 static void makeHippoPerform(
16 Hippopotamus hippo) {
17
18 hippo.sing();
19 if (hippo instanceof DancingHippopotamus) {
20 DancingHippopotamus dh =
21 (DancingHippopotamus) hippo;
22 dh.dance();
23 }
24 }
25 }
-
Uses
instanceof
to find out whether an object is an instance
of DancingHippopotamus
-
For each
DancingHippopotamus
it finds, it downcasts the
reference and invokes dance()
-
Can't use dynamic binding because
dance()
not defined in
superclass.
dance()
-eth Thou?
-
The other way to ask, "Can you do something for me," is to use
reflection.
1 // In file ex5/Hippopotamus.java
2
3 class Hippopotamus {
4
5 void sing() {
6 System.out.println("Hippo singing!");
7 }
8 }
9
1 // In file ex5/DancingHippopotamus.java
2
3 class DancingHippopotamus
4 extends Hippopotamus {
5
6 void dance() {
7 System.out.println("Hippo dancing!");
8 }
9 }
1 // In file ex5/Example5b.java
2 import java.lang.reflect.Method;
3 import java.lang.reflect.InvocationTargetException;
4
5 class Example5b {
6
7 public static void main(String[] args) {
8
9 makeHippoPerform(new Hippopotamus());
10 makeHippoPerform(new DancingHippopotamus());
11 }
12
13 // Client must pass a Hippopotamus
14 static void makeHippoPerform(
15 Hippopotamus hippo) {
16
17 hippo.sing();
18 Class c = hippo.getClass();
19 try {
20 Method method = c.getDeclaredMethod(
21 "dance", new Class[0]);
22
23 try {
24 method.invoke(hippo, new Object[0]);
25 }
26 catch (InvocationTargetException e) {
27 System.out.println("Can't dance!");
28 }
29 catch (IllegalAccessException e) {
30 System.out.println("Not allowed to dance!");
31 }
32 }
33 catch (NoSuchMethodException e) {
34 }
35 }
36 }
Why instanceof
and Downcasting?
-
Remember: Prefer polymorphism and dynamic binding to
instanceof
and downcasting.
-
But don't include a
dance()
service in
Hippopotamus
if that's not really what Hippopotamus
means.
-
Why is
instanceof
and downcasting better than reflection?
-
The code that uses
instanceof
and downcasting is
easier to read, more concise, easier to debug,
and easier to maintain than reflection code
-
Compare reflection code:
13 // Client must pass a Hippopotamus
14 static void makeHippoPerform(
15 Hippopotamus hippo) {
16
17 hippo.sing();
18 Class c = hippo.getClass();
19 try {
20 Method method = c.getDeclaredMethod(
21 "dance", new Class[0]);
22
23 try {
24 method.invoke(hippo, new Object[0]);
25 }
26 catch (InvocationTargetException e) {
27 System.out.println("Can't dance!");
28 }
29 catch (IllegalAccessException e) {
30 System.out.println("Not allowed to dance!");
31 }
32 }
33 catch (NoSuchMethodException e) {
34 }
35 }
-
To
instanceof
and downcasting code:
14 // Client must pass a Hippopotamus
15 static void makeHippoPerform(
16 Hippopotamus hippo) {
17
18 hippo.sing();
19 if (hippo instanceof DancingHippopotamus) {
20 DancingHippopotamus dh =
21 (DancingHippopotamus) hippo;
22 dh.dance();
23 }
24 }
-
Reflection code is likely much slower than
instanceof
and
downcasting code
-
Reflection robs you of strong typing at compile time that can help you
catch errors sooner.
-
Reflection code has less meaning (less semantically rich) than
instanceof
and downcasting code
Discussion
-
Guideline: Use
instanceof
and downcasting to ask
"Can you do something for me."
Runtime Class Information
-
Guideline: Prefer compile-time type information to run-time class
information.
-
Given a reference to an object, can get info about that object's class
-
Makes possible many interesting designs and implementation techniques
-
Information is power
-
Power corrupts
Class and Type
-
Classes and interfaces define (are) types
-
Can declare variables of class and interface types
-
Type defines set of operations that may be performed on variables of that
type and the meaning of those operations
-
Type says, "What an object can do for me"
-
"Variables have type; Objects have class."
The Class File
Java compiler creates class files, a binary format that contains information
about the class, such as:
- the type's name
- the superclass's name
- any modifiers (such as
public
or abstract
)
- the number of interfaces the type directly implements
or extends
- the names of those interfaces
- the number of fields
- for each field, a field descriptor (field name, type, modifiers)
- the number of methods
- for each method:
- a method descriptor (method name, return type or void, number
and types of parameters)
- modifiers (
static
, abstract
,
private
, and so on)
- the bytecodes
- an exception table
Class Info in the JVM
-
JVM loads class files
-
Parses file and puts class info in method area
-
Creates an instance of class
java.lang.Class
to "represent"
the type
-
Object images on the heap must in some way be connected to class data
in the method area
Using Class Information
-
Perform a cast (checked at runtime)
-
Use
instanceof
-
Invoke an instance method
-
Use
java.lang.Class
Class Class
Printing Class Information
1 // In file ex5/Hippopotamus.java
2
3 class Hippopotamus {
4
5 void sing() {
6 System.out.println("Hippo singing!");
7 }
8 }
9
1 // In file ex5/DancingHippopotamus.java
2
3 class DancingHippopotamus
4 extends Hippopotamus {
5
6 void dance() {
7 System.out.println("Hippo dancing!");
8 }
9 }
1 // In file ex5/Example5c.java
2 import java.util.ArrayList;
3 import java.util.Collection;
4 import java.util.Iterator;
5 import java.lang.reflect.*;
6
7 public class Example5c {
8
9 public static void main(String[] args) {
10
11 ArrayList hippos = new ArrayList();
12 hippos.add(new Hippopotamus());
13 hippos.add(new DancingHippopotamus());
14 hippos.add(hippos);
15 hippos.add(new String("Hi There!"));
16
17 Iterator it = hippos.iterator();
18 while (it.hasNext()) {
19 printClassInfo(it.next());
20 }
21 }
22
23 static void printModifiers(int modifiers) {
24
25 if (Modifier.isPrivate(modifiers)) {
26 System.out.print("private ");
27 }
28 else if (Modifier.isProtected(modifiers)) {
29 System.out.print("protected ");
30 }
31 else if (Modifier.isPublic(modifiers)) {
32 System.out.print("public ");
33 }
34
35 if (Modifier.isAbstract(modifiers)) {
36 System.out.print("abstract ");
37 }
38 if (Modifier.isStatic(modifiers)) {
39 System.out.print("static ");
40 }
41 if (Modifier.isFinal(modifiers)) {
42 System.out.print("final ");
43 }
44 if (Modifier.isTransient(modifiers)) {
45 System.out.print("transient ");
46 }
47 if (Modifier.isVolatile(modifiers)) {
48 System.out.print("volatile ");
49 }
50 if (Modifier.isSynchronized(modifiers)) {
51 System.out.print("synchronized ");
52 }
53 if (Modifier.isNative(modifiers)) {
54 System.out.print("native ");
55 }
56 }
57
58 static void printClassInfo(Object o) {
59
60 System.out.println("------------------------");
61
62 Class c = o.getClass();
63
64 System.out.println("Name: " + c.getName());
65
66 Class superclass = c.getSuperclass();
67 if (superclass != null) {
68 System.out.println("Superclass: "
69 + superclass.getName());
70 }
71
72 System.out.println("Interfaces:");
73 Class[] interfaces = c.getInterfaces();
74 for (int i = 0; i < interfaces.length; ++i) {
75 System.out.println("\t" + interfaces[i].getName());
76 }
77
78 System.out.println("Fields:");
79 Field[] fields = c.getDeclaredFields();
80 for (int i = 0; i < fields.length; ++i) {
81 System.out.print("\t");
82 printModifiers(fields[i].getModifiers());
83 System.out.println((fields[i].getType()).getName()
84 + " " + fields[i].getName() + ";");
85 }
86
87 System.out.println("Methods:");
88 Method[] methods = c.getDeclaredMethods();
89 for (int i = 0; i < methods.length; ++i) {
90
91 System.out.print("\t");
92 printModifiers(methods[i].getModifiers());
93
94 System.out.print((methods[i].getReturnType()).getName()
95 + " " + methods[i].getName() + "(");
96
97 Class[] params = methods[i].getParameterTypes();
98 for (int j = 0; j < params.length; ++j) {
99 System.out.print(params[j].getName());
100 if (j != 0 && j != params.length - 1) {
101 System.out.print(", ");
102 }
103 }
104
105 System.out.println(");");
106 }
107 }
108 }
109
1 ------------------------
2 Name: Hippopotamus
3 Superclass: java.lang.Object
4 Interfaces:
5 Fields:
6 Methods:
7 void sing();
8 ------------------------
9 Name: DancingHippopotamus
10 Superclass: Hippopotamus
11 Interfaces:
12 Fields:
13 Methods:
14 void dance();
15 ------------------------
16 Name: java.util.ArrayList
17 Superclass: java.util.AbstractList
18 Interfaces:
19 java.util.List
20 java.lang.Cloneable
21 java.io.Serializable
22 Fields:
23 private transient [Ljava.lang.Object; elementData;
24 private int size;
25 Methods:
26 private void RangeCheck(int);
27 public void add(intjava.lang.Object);
28 public boolean add(java.lang.Object);
29 public boolean addAll(intjava.util.Collection);
30 public boolean addAll(java.util.Collection);
31 public void clear();
32 public java.lang.Object clone();
33 public boolean contains(java.lang.Object);
34 public void ensureCapacity(int);
35 public java.lang.Object get(int);
36 public int indexOf(java.lang.Object);
37 public boolean isEmpty();
38 public int lastIndexOf(java.lang.Object);
39 private synchronized void readObject(java.io.ObjectInputStream);
40 public java.lang.Object remove(int);
41 protected void removeRange(intint);
42 public java.lang.Object set(intjava.lang.Object);
43 public int size();
44 public [Ljava.lang.Object; toArray();
45 public [Ljava.lang.Object; toArray([Ljava.lang.Object;);
46 public void trimToSize();
47 private synchronized void writeObject(java.io.ObjectOutputStream);
48 ------------------------
49 Name: java.lang.String
50 Superclass: java.lang.Object
51 Interfaces:
52 java.io.Serializable
53 java.lang.Comparable
54 Fields:
55 private [C value;
56 private int offset;
57 private int count;
58 private static java.lang.ThreadLocal btcConverter;
59 private static java.lang.ThreadLocal ctbConverter;
60 private static final long serialVersionUID;
61 private static final [Ljava.io.ObjectStreamField; serialPersistentFields;
62 public static final java.util.Comparator CASE_INSENSITIVE_ORDER;
63 Methods:
64 public char charAt(int);
65 public int compareTo(java.lang.Object);
66 public int compareTo(java.lang.String);
67 public int compareToIgnoreCase(java.lang.String);
68 public java.lang.String concat(java.lang.String);
69 public static java.lang.String copyValueOf([C);
70 public static java.lang.String copyValueOf([Cint, int);
71 public boolean endsWith(java.lang.String);
72 public boolean equals(java.lang.Object);
73 public boolean equalsIgnoreCase(java.lang.String);
74 private static sun.io.ByteToCharConverter getBTCConverter(java.lang.String);
75 public [B getBytes();
76 public void getBytes(intint, [B, int);
77 public [B getBytes(java.lang.String);
78 private [B getBytes(sun.io.CharToByteConverter);
79 private static sun.io.CharToByteConverter getCTBConverter(java.lang.String);
80 public void getChars(intint, [C, int);
81 public int hashCode();
82 public int indexOf(int);
83 public int indexOf(intint);
84 public int indexOf(java.lang.String);
85 public int indexOf(java.lang.Stringint);
86 public native java.lang.String intern();
87 public int lastIndexOf(int);
88 public int lastIndexOf(intint);
89 public int lastIndexOf(java.lang.String);
90 public int lastIndexOf(java.lang.Stringint);
91 public int length();
92 public boolean regionMatches(intjava.lang.String, int, int);
93 public boolean regionMatches(booleanint, java.lang.String, int, int);
94 public java.lang.String replace(charchar);
95 public boolean startsWith(java.lang.String);
96 public boolean startsWith(java.lang.Stringint);
97 public java.lang.String substring(int);
98 public java.lang.String substring(intint);
99 public [C toCharArray();
100 public java.lang.String toLowerCase();
101 public java.lang.String toLowerCase(java.util.Locale);
102 public java.lang.String toString();
103 public java.lang.String toUpperCase();
104 public java.lang.String toUpperCase(java.util.Locale);
105 public java.lang.String trim();
106 public static java.lang.String valueOf(char);
107 public static java.lang.String valueOf(double);
108 public static java.lang.String valueOf(float);
109 public static java.lang.String valueOf(int);
110 public static java.lang.String valueOf(long);
111 public static java.lang.String valueOf(java.lang.Object);
112 public static java.lang.String valueOf(boolean);
113 public static java.lang.String valueOf([C);
114 public static java.lang.String valueOf([Cint, int);
Why Design with Types?
-
Reflection useful for: bean builders, object inspectors, object serialization,
debuggers, ...
-
Use reflection when you don't know what's coming, and you want to do
something to objects
-
Programming with types is more efficient than reflection, exceptions.
-
Can find more errors sooner, because compiler does type checking.
-
Encourages meaning-rich code: a type name implies an interface, a set
of services, and a semantic contract.
-
Types form a vocabulary with which team members talk about the program --
they are the units of communication
Runtime Class Info is Not Evil
-
I think it's a way of gaining flexibility and not losing it,
if you design your application to be able to handle
any Java object that can be thrown at it :) Always having to know
the type of things that are coming at you is a woeful left-over
from strong-typing fanatics.
-
An object sorter:
// In file rtci/ex4/ObjectSorter.java
import java.util.*;
public class ObjectSorter {
private HashMap hm = new HashMap();
public void add(Object o) {
Class key = o.getClass();
if (hm.containsKey(key)) {
ArrayList value = (ArrayList) hm.get(key);
value.add(o);
}
else {
ArrayList value = new ArrayList();
value.add(o);
hm.put(key, value);
}
}
// Returns an iterator for all the Class objects
public Iterator getClassIterator() {
return (hm.keySet()).iterator();
}
public Collection getObjects(Class key) {
return (Collection) hm.get(key);
}
}
Discussion
-
Guideline: Prefer compile-time type information to run-time class
information.