15-200 Lecture 15 (Monday, June 16, 2008)

The Object class

Let's take a minute to recall why, when creating our own array-based collection, we made an array of Objects, rather than using an array of some other type. 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.
  

Ultimately, this implies that an array of Object references can be used to manage all but primitive data.

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.

Autoboxing

In Java 1.4 it was a pain to use primitives within collections or in other contexts where types of Objects were expected. We had to wrap the primitive into a Integer, Long, Double, Float, Character, Boolean, or the like, first. Then, after removing it from the data structure, we had to "unwrap" to get back at the primitive: intValue(), doubleValue(), longValue(), &c. And, although equivalent in the abstract, the same operations didn't apply to each. For example, the mathematical operators weren't defined on Integer, Double, Long, Float, &c.

Java 1.5 resovles this with "autoboxing". Basically, any time a numerical operator is used on an Object (wrapped primitive) or there would otherwise be a type mismatch, the compiler will automatically insert the code to wrap the primitive value or unwrap the Object, as appropriate. Please consider the examples below:

  Integer bigI = 5; // 5 gets wraped
  bigI = bigI + 3; // I gets unwrapped, the add happens, and then wrapped again
  
  int i = 2;
  LinkedList linkedList = new LinkedList();
  linkedList.add(i); // i is wrapped

  i = linkedList.get(0); // the Integer returned is unwrapped
  

Now let's consider the example below. Autoboxing won't help us. Can you guess why?

  int i = 5;

  i.toString(); // No autoboxing. I stays an int and this doesn't compile
  

The problem in the example above is that Java has no idea that it should be boxing i. It would need to know that i should be an Integer before looking at the Integer class to find the toString() method. How does it know that toString() isn't the Long's toString()? Or the Double's?

Remember, java will only box or unbox if it would otherwise recognize a type compatibility problem or if there is a mathematical operator being applied to a wrapped type.