Return to lecture notes index
May 21, 2005 (Lecture 5)

The Object class

At this point, we've discussed references to a single type of object, but not a generic reference that can name any type of object. And, that's really what is needed here.

So, does Java have such a data type? A generic, untyped reference? Well, no -- Java is a strongly typed language -- variables can only hold one type of data. Reference variables can only reference one type of object.

So, Java does something a little bit sneakier -- it makes use of inheritence. Even though you don't explicitly request it, each base class you declare automatically extends the Object class. This class is just like any other class, except that it is defined by the good folks at Sun, not your or me, and that it is inherited automatically.

As a result, all objects in Java, Person objects Dog objects, Set objects, are types of Object. So, if you declare an Object reference, it can name any type of object. For example:

  Dog d = new Dog(); 
  Object o = new Person(); // Now "o" names a Person
  o = d; // No "o" names a Dog.
  

Casting

The examples above illustrated what is known as implicit casting. That sounds complicated, but it realyl isn't. Think of Hollywood -- a directory casting actors into parts. When an actor is cast into a role, s/he becomes the character.

Casting data types is exactly the same thing. For example, the examples above cast a Person as a generic object. They did the same thing for a Dog. Since we didn't explicitly ask Java to cast, but it did anyway, this is called an implicit cast. Next, we'll talk about when and why we need to sometimes use explicit casting, and why it isn't always safe.

Explicit Casting: When Implicit Casting Isn't Safe

We've got to be a little bit careful in our use of Object references. A Dog "is a(n)" Object, and a Person "is a(n)" Object. Since each is an Object, we can use an Object reference to name either. IIn this case, implicit casting is okay. Consider the diagram below:

Notice that the "is a" arrows point upward. A Person is an Object, and a Dog is an Object, so an Object is not necessarily a Person or a Dog: If we pick one, it could be the other. As a result, it is okay to use an reference of a particular type to name objects of derived types, but not necessarily okay to use a reference to name types above it.

To clarify this a bit further, if I tell you, here is an "Object o", unless you know for sure, you shouldn't assume that it is a Person or a Dog, or anything else -- you could be wrong. And that will break the program. As a result, Java won't allow an implicit cast in these circumstances. Remember, the object that we are naming isn't changing -- it remains whatever it was. Our name is just becoming less informative.

If you are certain that the downward cast is safe, you can use an explicit cast. The syntax is the same as C or C++: You just list the type in parenthesis before the reference to the object that you are casting into a different role. Consider the example below:

  Object o = new Person();
  Person p = (Person) o; // This is safe -- we know that we created a Person
  

It is important to realize that unsafe explicit casts will not be detected at compile time. Instead, they will generate runtime ClassCastExceptions. These can be a bit tricky to debug.

Casting Primitive Types

Primitive types can often be cast between each other -- but the results might or might not make sense. For example, a float can be cast to a double, becasue they represent the same thing, and a double is wider. The reverse can also happen: you can cast from a double to a float. But, going in the reverse direction, might result in the loss of precision. In other words, since the number format is narrower, it might overflow or truncate part of the decimal portion. The same is true for casts between ints and longs.

But, keep in mind that primitives are not Objects. They are values without behaviors or other attributes. As a result, primitives can never be cast as Objects.

Wrapper Classes

So, if we want to name primitives within data structures that reference Objects, how do we do it? To solve this problem, Java provides the Wrapper classes. For each primitive type, Java has a corresponing class that defines objects containing the primitive value. In other words, an instance of a wrapper class is an Object that contains a primitive value. An Object reference can name an instance of a wrapper class, and we can get the primitive value from it whenever we want using a method of the wrapper class.

The wrapper classes are named using the following convention: take the name of the primitive type and capitalize the first letter. For example, Float objects wrap float primitives, Long objects wrap long primitives, and Boolean objects wrap boolean primitives. In the case of abbreviated type names such as int and char, the full type name is used for the wrapper class, for example, Integer and Character.

Each wrapper calss has a constructor that can be used to wrap the primitive value. There are typically constructors for both the primitive type, and the value represented as a String.

int x = 5;
Integer i;
i = new Integer(x);

Each wrapper class also has a method to get the primitive value. This method is name ____value(), where the ___ is the name of the primitive type, for example, intValue(), floatValue(), booleanValue(). In each case the type fo the return value is the same as the type of the wrapped value.

	int x;	  
Integer i;
i = new Integer(5);
x = i.intValue(); // returns the value stored in the object

In addition, there are many other methods on the wrapper classes. Most of them we're not going to worry about, but I do want to mention one of the static methods, that exists in its own form in each of the wrapper classes. parse____(), such parseInt(), parseFloat(), parseBoolean(), &c. This method converts a string representing the primitive value to a primitive value of that type, for example:

  int i = Integer.parseInt ("10");
  double d = Float.parseDouble ("10.00");
  

Notice that to the left of the "." is the name of a wrapper class, not an instance thereof. That is because static methods, which are sometimes known as class methods, aren't really behaviors of objects. Instead, they are more like C or C++ functions. They are simply organized within a class, because they relate to instances of that class. organizing static methods this way helps to prevent the pollution of the global name space, for example many confusingly similarly named add() methods, while making the methods context clear to the programmer.