C SC 225 Lecture 11: Java Object Model
[ previous
| schedule
| next ]
Selected Java Object Model Topics (Horstmann chapter 7)
Determining object type
- You are familiar with the instanceof operator...or are you?
- What are some situations where you might use instanceof?
- Will it yield exact type information?
- It will tell you whether or not the first operand is subtype of the second
- This includes same type, since any class is a subtype of itself
- What if the first operand is null? Returns false, since it is not an instance of anything!
- Are you familiar with the getClass() method?
- Returns the actual runtime class of an object, not the declared type of its reference variable (if it has one)
- Defined in java.lang.Object and inherited by all other classes
- Cannot be overridden (declared final)
- Wait, back up! getClass() Returns a class? Is that a String?
- It returns a reference to a Class object. This is not a String!
- Want to know more about the Class class?
- No mystery; it's in the java.lang package and is described in the
API
- Every class at runtime has exactly one associated Class object (singleton pattern), created by the JVM class loader
- Every variation of a parameterized (generic) type yields the same
Class object
- Since they are singletons, Class objects can be compared for equality using == and !=
- You can apply .class to any type name to get its Class object
- A type name can be any named type (a class, an interface, a primitive type, void)
- Examples: String.class, Observer.class, int.class, void.class
- It can also be an array of any named type, which yields a different Class object
- For example, Observer.class is not the same as Observer[].class
- But you can't apply .class to a parameterized (generic) type unless you leave off the parameter
- Actually, Class itself is a parameterized type!
- An object of type Class contains lots of information about the class it describes -- 57 methods, mostly getters!
- The getName() method returns a String containing the fully-qualified class name
- The isArray() method returns true or false depending on whether it is an array class
- We will visit additional methods below.
- Your program can use type information to reason about object and classes
Object equality checks
- Yes, we have studied equals() before; what do you remember about it?
- Did you know that the Java Language Specification says that equals() must be:
- reflexive: so x.equals(x) must be true
- symmetric: so x.equals(y) if and only if y.equals(x)
- transitive so if x.equals(y) and y.equals(z) then x.equals(z) must be true
- tolerant of nulls; x.equals(y) must be false if y is null
- Obviously the reference preceding the dot in all these examples cannot be null!
- What if both refer to the same object (==)? Easy, they are equal
- What if both object contents are equal but they are of different classes? Equal, or not? Not!
- So, can we get by using only instanceof to check class types? No!
- Use getClass(). What about typename.class ? No! Why not? See next item.
- What if they are of the same subclass but cannot access superclass contents? Call super.equals()
- If the superclass equals() used the hard-coded typename.class instead
of the soft-coded getClass(), then super.equals() would always return false!
- Ow, this has gotten complicated! To summarize, we check things in this order:
- If this and other are the same object, return true
- If other is null, return false
- If they are not of the same class, as determined by comparing this.getClass() == other.getClass(), return false
- If they are of the same subclass but super.equals(other) is false, return false
- If still here after all the above checks, compare instance variables and decide.
Object hashing
- Like equals(), every object has a hashCode() method
- The hashCode() method produces an int hash code value.
- The hash code value is used for hash tables, such as by the HashMap class
- hashCode() should be consistent with equals().
- In other words, two objects that
test true for equals() should produce the same hash code and two that test false for
equals() should produce different hash codes.
- The default hashCode(), inherited from Object, produces the 32 bit address of
the object. This is consistent with the default equals(), which returns true only if
both references are to the same object (==).
- This means, if you override equals() in a subclass, you need to override hashCode()
also!
- Reasoning: if you override equality to be based on contents not address yet you use the default hash code algorithm,
then two different objects can be equal but their hash codes will be different.
- This can get tricky for object containing collections. If order does not matter, then calculate
hash code by adding hash codes of collection elements. But if order does matter, this will
not work; you need to factor in element position as well.
- For order-critical hashing, Horstmann suggests iterating over the collection and multiplying
the previous iteration's hash by a prime number before adding in the current element's hash value. E.g.:
int hash = 0;
for (Object obj : collection) {
hash = 31 * hash + obj.hashCode();
}
- Don't worry about overflow; as you know, integers just wrap around and that's OK here.
- See how this is handled by java.util.HashMap and java.util.HashSet
Cloning and Serialization
- We covered these topics sufficiently in CSC 205.
- "Sufficiently" means you know enough to be afraid! This is good; it means if you need to clone or
serialize you will not be tempted to do so without consulting a good reference.
- Cloning: Often cannot be avoided because default is "shallow", but undertake with caution.
- Serialization: Not needed as frequently as cloning, but then again can you anticipate
the needs of your client? The default works well in nearly all cases.
Object Reflection
- reflection : An OO concept in which a program can reason about its objects
(and an object can reason about itself, literally reflecting).
- This relates directly back to the first topic above, the Class object that describes
every class at runtime.
- There are other classes like Class that are useful for reflection. Here are some examples:
- The Method class describes a given method and allows it to be invoked.
- The Field class describes a field and allows it to be accessed regardless of privacy!
- The Constructor class describes a constructor and allows it to be invoked.
- Now, let's look at some of the other Class getters!
- getDeclaredMethods() returns an array of Method objects, each representing a method defined in this class (not inherited)
- getDeclaredFields() returns an array of Field objects, each representing a field defined in this class (not inherited)
- getConstructors() returns an array of Constructor objects, each representing a constructor defined in this class
- getSuperclass() returns the Class object representing the parent class
- getInterfaces() returns an array of Class objects representing the interfaces implemented by this class (or extended by this interface)
- isAnonymousClass() returns true if this is an anonymous class and false otherwise
- and many, many more!
- Likewise, the Method class in java.lang.reflect has a number of interesting methods:
- getName() to find out the name (as a String) of this method
- getParameterTypes() to get an array of Class objects, each of which represents a parameter of this method
- getReturnType() to find out the declared kind of value the method returns
- invoke() to actually invoke this method!
- The Field class in java.lang.reflect has some good ones too:
- getName() to find out the name (as a String) of this field
- getType() to get a Class object for the declared type of this field
- setAccessible() to make the field accessible even if it is private and you are in another class!
- get() to get an Object containing the value of this field
- set() to actually modify the value of this field!
[ C
SC 225 | Peter
Sanderson | Math Sciences server
| Math Sciences home page
| Otterbein ]
Last updated:
Peter Sanderson (PSanderson@otterbein.edu)