Core Concepts

To understand the advanced concepts of the Java language, there are a few core concepts that you must have firmly in mind. Without these concepts, much of this tutorial will not make a lot of sense. The concepts of pointers in Java, its class hierarchy, and RTTI (runtime type identification) are three of the most important on this list.

Constant Problems with Pointers

Java and C++ use a very analogous syntax to symbolize their instructions to the computer's CPU. In fact, there are probably more similarities between these languages than the two entrenched camps of supporters would like to admit. One difference between Java and C++ that is often mentioned, though, is that Java does not use pointers. Pointers in C++ were a constant source of problems and were determined to be the coding equivalent of evil incarnate. There was, and is to this day, a large group of apps in C++ that suffer from the effects of this particular wrong. Therefore, Sun decided to leave them out of Java—at least that's the theory. In reality, Java uses what C++ calls references. In fact, other than primitives, all variables in Java are references. References and pointers are very similar; both contain the memory location of a particular object. Pointers, however, allow you to do arithmetic whereas references do not. The fact that Java uses so many references introduces some difficulties that novice and proficient Java developers often get burned by. The code shown in Example 1-1 demonstrates one of these difficulties.

Example 1-1. Collections are passed as references
package oracle.hcj.review;
public class PointersAndReferences {
 public static void someMethod(Vector source) {
 Vector target = source;
 target.add("Swing");
 }
}


Here, you simply copy the passed-in source vector and add a new element to the copy (named target); at least that is how it appears. Actually, something quite different happens. When you set target equal to source in the code, you copy a reference to the target vector, and not the contents of the vector itself. Since both variables now point to the same vector, this function actually adds an element to the source vector that was passed into the method; this was almost certainly not the desired effect! We will discuss how to prevent this problem in . Since you can change the contents of an incoming collection, Java actually does have pointers—that is, Java references embody the same computer science principles that pointers do. So, when someone tells you that Java doesn't have pointers, you can correct them by saying, "Java has pointers, it just doesn't have pointer arithmetic." Also, Java's use of references isn't a bad thing. In fact, the references are actually necessary to the Java language. Without them, you would have to pass everything by value. This would entail copying every single object each time an object was passed to a method. If the object were a String, this copying probably wouldn't be a big deal. However, if the object is a large array or set, the copy could take a long time. Therefore, passing everything by value would make Java code run extremely slowly. Furthermore, some objects simply don't make sense to copy at all. If you have a GUI panel and wish to pass the GUI panel to another component that needs to refer to it, you certainly don't want two copies of the panel floating around in memory. Instead, you want to send a reference to the existing panel. All of these issues point out the need for references in Java.

Everything Is a Class and Object Is God

In Java, all constructed types are regarded as objects. In fact, the only items in Java that are not objects are the primitive types, such as int and boolean; and even these primitive types can be treated as objects under some circumstances, such as when you use reflection. I would expect that most Java programmers would find this trivial since they already know that every nonprimitive in Java is an object. The point that I am really trying to make, though, is that every constructed type in Java descends from the class java.lang.Object. Even if you don't declare a class as extending java.lang.Object, you still get this behavior. Example 1-2 shows a class that explicitly extends java.lang.Object, while Example 1-3 is a class that has no explicit extension.

Example 1-2. Explicitly extending java.lang.Object
public class SomeClass extends Object {
}


Example 1-3. Implicitly extending java.lang.Object
public class SomeClass { }


In Example 1-2, SomeClass makes its object hierarchy clear; its superclass is Object. Example 1-3, while visibly different, is actually equivalent to Example 1-2. The Java compiler will automatically assign the superclass of Object to this version of SomeClass. In fact, even a class that implements interfaces instead of extending another class extends java.lang.Object. As a result, these two declarations are identical:

public class SomeClass extends Object implements Serializable {
}
public class SomeClass implements Serializable {
}


So, unlike C++, you cannot create a class that does not have a superclass. Object is the superclass to all classes in the entire language. In the example code for this tutorial, you will find the class oracle.hcj.review.ObjectIsGod, which demonstrates this concept:

> ant -Dexample=oracle.hcj.review.ObjectIsGod run_example run_example:
 [java] class oracle.hcj.review.ObjectIsGod$SomeClass --|> class java.lang.Object
 [java] class oracle.hcj.review.ObjectIsGod$SomeOtherClass --|> class java.lang.Object


There are three main reasons why java.lang.Object is always at the top of the inheritance tree:

  • For Java to know the type of objects it must load, and to enforce type safety, it has to have some base type with which it can refer to all objects.
  • Java currently lacks the concept of the parameterized type (also known as a template). This means that all the objects that are be stored in collection classes ultimately have to descend from one class.
  • The fact that all objects have java.lang.Object as a superclass gives you supreme power when it comes to using reflection to turbocharge your code. Without this sort of inheritance, many techniques in reflection would be impractical. (We will discuss reflection in detail in .)

RTTI

Runtime type identification, or RTTI, is an extremely powerful tool. With RTTI, objects become very friendly and readily tell you what they are, how they are used, and more. In Java, RTTI is built right into the core of the virtual machine. You've almost certainly used RTTI, even if you don't realize it; it's all over the place. For example, consider that every object in Java can tell you what type it is through the getClass( ) method. Whenever you invoke getClass( ), you use RTTI. Example 1-4 shows the getclass( ) method in action.

Example 1-4. Demonstration of basic RTTI usage
package oracle.hcj.review;
public class RTTIDemo {
 public final static void basicRTTIDemo ( ) {
 Float y = new Float(15.0);
 String name = "Fred";
 System.out.println(y.getClass( ));
 System.out.println(name.getClass( ));
 }
}


This method will, quite politely, print the following:

>ant -Dexample=oracle.hcj.review.RTTIDemo run_example run_example:
 [java] class java.lang.Float
 [java] class java.lang.String


Since the getClass( ) method is a method defined by the class java.lang.Object, it is guaranteed to be there for all objects. Java also uses RTTI to protect a programmer from her own errors, as Example 1-5 demonstrates.

Example 1-5. RTTI enforces type safety
package oracle.hcj.review;
public class RTTIDemo {
 public static class A {
 }
 public static class B extends A {
 }
 public static class C {
 }
 public final static void castingWithRTTI ( ) {
 A a = null;
 A a1 = new A( );
 B b = new B( );
 C c = new C( );
 a = (A)b; // no problem
 b = (B)a; // still no problem, casting back to what it was created as. a = a1; // Same type so no problem
 Object d = (Object)c; // no problem because of implicit inheritance
 c = (C)d; // casting back
 b = (A)a1; // compiler error: a1 is not a B. b = (B)c; // compiler error: c is not a B. }
}


This code shows how to create an object of a subclass, cast it to its base class, and then cast it back to the subclass. RTTI works in the background to ensure that your casting is always legal; in other words, an object can safely be cast only to its own type, or to a type that it is inherited from. In the sample code for the tutorial, Example 1-5 is replicated with the bad casts commented out. If you uncomment these lines, you will see that the program won't even compile, and you will get errors such as those shown here:

>ant -Dexample=oracle/hcj/review/RTTIDemo.java compile_example compile_example:
 [javac] Compiling 1 source file to C:\dev\hcj\bin
 [javac] C:\dev\hcj\src\oracle\hcj\review\RTTIDemo.java:54: incompatible types
 [javac] found : oracle.hcj.review.RTTIDemo.A
 [javac] required: oracle.hcj.review.RTTIDemo.B
 [javac] b = (A)a1; // compiler error: a1 is not a B.
 [javac] ^
 [javac] C:\dev\hcj\src\oracle\hcj\review\RTTIDemo.java:55: inconvertible types
 [javac] found : oracle.hcj.review.RTTIDemo.C
 [javac] required: oracle.hcj.review.RTTIDemo.B
 [javac] b = (B)c; // compiler error: c is not a B.
 [javac] ^
 [javac] 2 errors


RTTI is useful for preventing common errors in programming. It also has many other uses; we will get to the juicy details of exploiting RTTI in and .

      
Comments