HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Last month, I introduced the terms types, variables, classes, objects and references. If you understood the examples and correctly answered the test question I provided at the end of the article, you're well on your way to mastering the essential concepts that govern how all Java programs work. If you missed the article, look for it on SYS-CON's Web site at http://www.JavaDevelopersJournal.com. This month, I'm going to concentrate on eight tenets of Java programming. These tenets are the "truths" that will guide you safely through the darkest complexities of Java syntax as it relates to assignments, casting, constants and method parameters. Here they are:

  1. Variables have types, objects have classes.
  2. The type of a variable never changes.
  3. The value contained in a reference variable is a reference, not an object.
  4. The class of an object never changes
  5. Class types are related to other types by inheritance and (optionally) interfaces.
  6. A reference variable can be assigned a reference to any object whose class
    a) is the same as, or is a subtype of, the type indicated in the variable's declaration;
    or b) implements an interface that is the same as, or is a subtype of, the type indicated in the variable's declaration.
  7. The type of a reference variable determines which messages can be sent to the object it refers to.
  8. The class of an object determines which instance methods are invoked in response to the messages it receives.
Knowing these tenets will serve you well. To experienced Java programmers, these tenets may be intuitive or even obvious, but they represent fundamental concepts that new Java programmers routinely wrestle with in their attempts to better understand the behaviors (usually error messages) of their programs.

Variables and Types
By way of review: variables are the placeholders that programmers use to represent the unknown or changing values that are manipulated when a program is run. As indicated by the first half of Tenet 1, "Variables have types," Java requires that the name and type of every variable be declared to the compiler prior to the variable's first use. This is done by specifying a type followed by the variable's name. For example, the statement:

int intVar;

declares a new variable named "intVar" that is of type int. The type of a variable limits the values that can be stored in the variable. Since the variable "intVar" was declared to be of type int, it can only hold integer values. Tenet 2 suggests that once you've declared the type of a variable, it can never be changed. Java, in contrast to languages like some of the xBASE derivatives, does not support "un-typed" or "polymorphic" variables that can, for instance, contain an integer value in one part of a program and a character later on. The combination of Tenet 1 and Tenet 2 should make it pretty clear what it means for Java to be a "strongly-typed" language. You cannot, intentionally or otherwise, break the "type" system. Only values that match the declared type of a variable, can be stored in the variable.

As I mentioned last month, there are two kinds of types in Java: primitive types and reference types. The primitive types are byte, short, int, long, float, double, char and boolean. The value stored in a variable of a primitive type is the binary representation of a number. The reference types are arrays, classes, and interfaces. As Tenet 3 suggests, the only things that can be stored in variables of a reference type are references. A reference is a value that the Java Virtual Machine utilizes to identify an object. Just as my email address provides you with the means to send messages to me, a reference provides the Java programmer with the means to send messages to an object. In object-oriented programming parlance, "sending a message" to an object is roughly equivalent to calling a function in a non-OOP language. The critical concept to understand is that, in Java, you never deal directly with objects, only with references to objects. Before exploring this further, there are a few points I should cover regarding classes and objects.

Classes Define Types
Object-oriented programming languages, like Java, allow programmers to "extend" the programming language by defining and working with new data-types that are pertinent to the problem being addressed. Every class you create is a new type that can be used in the same manner as the types/classes provided in the standard libraries. Listings 1 and 2 contain the definitions for several classes and interfaces. Provided they are included in your CLASSPATH or are imported into your program, you can use the names of the classes and interfaces in the declaration of variables as follows:

Animal animalVar;
Fish fishVar;
WaterDweller waterDwellerVar;
FreshWaterDweller freshWaterVar;
SaltWaterDweller saltWaterVar;

Note that we're able to declare variables of the types Animal, WaterDweller, FreshWaterDweller and SaltWaterDweller, even though it's not possible to create objects from abstract classes or interfaces.

Creating Objects
An object is the physical representation of a class type. The second half of Tenet #1, "objects have classes," refers to the fact that every object is created from a particular class definition. The "class" of an object is the name of the class used in the expression that created the object. Usually, an object is created using the new keyword followed by the name of a class:

new Whale();

It may seem self-evident that, in this case, the "class" of the newly created object is Whale. Tenet #4, "The class of an object never changes," just reinforces the fact that no matter what operations are performed, the object itself is never changed or converted to anything else. As long as it exists, its "class" will remain Whale. This leads us to the matter of how long objects exist. An object will continue to exist as long as at least one variable contains a reference to it. Any expression that creates a new object, such as the "new Whale()" statement used above, returns a reference to the object it created. Since, in the above example, I neglected to store the reference that was returned, I lost my opportunity to ever do anything with the object. The newly created object will sit around, inaccessible by the program, until the garbage collector decides to destroy it and reclaim the memory it occupies.

Since it is notoriously difficult to do many useful things with objects you can't access, that are prone to disappearing at the whim of the garbage collector, you usually create new objects and assign the returned reference to a variable in a single statement:

whaleVar = new Whale();

Class/Interface Hierarchies
With Tenet 5, "Class types are related to other types by inheritance and (optionally) interfaces," things begin to get much more interesting. Java classes are arranged into a hierarchy of classes whose root is the class Object. Every Java class descends from exactly one other class, as indicated in the "extends" clause of the class definition. If a class definition doesn't have an "extends" clause, the class is implicitly descended from the Object class.

Figure 1 illustrates the hierarchy of the classes defined in Listing 1. A class is said to be a subclass of all of the classes from which it is a direct descendant. A class is said to be the superclass of all of the classes which descend from it. Thus, the Animal class is the superclass of all of the classes in Figure 1 except the Object class. Likewise, the Salmon class is a subclass of the Fish class. It is also a subclass of the Animal and Object classes. The words "is/an" can be used to describe the relationship between a subclass and any of its superclasses:

Figure 1
Figure 1:

A GoldFish is a Fish
A Fish is an Animal
A GoldFish is an Animal
A Dog is an Object

OK, so you might not have known that a Dog is an Object. The point is that a subclass is just a more specific type of the class from which it is descended. To generalize, we can substitute the word subtype for the word subclass and the word supertype for the word superclass. Then, as shown in Figure 2, the same description applies to the hierarchical organization of interfaces.

Figure 2
Figure 2:

Basically, aninterface is a type that is defined by a set of method declarations. It provides a way to overcome some of the limitations that Java programmers face, since a class can have only one direct superclass. As shown in Listing 1, the Fish, GoldFish and Whale classes each implement one of the interfaces shown in Listing 2. Normally, in addition to declaring that it "implements" an interface, a class would have to actually implement for all of the methods the interface declares. For simplicity, I've chosen not to declare any methods within any of the interfaces used in this example. That way, I can demonstrate the effect that interfaces have on assignments and casting without bogging the example classes down with excessive detail.

Reference Variables
Tenet 6 summarizes the rules which govern whether a variable of a particular type is permitted to hold a reference to an object of a particular class. Let's begin by declaring a variable of type Mammal:

Mammal mammalVar;

As I indicated previously, when I refer to an object's class, I am specifically referring to the name of the class used in the expression that created the object. According to the first part of Tenet 6 - Option A, a variable is always capable of holding a reference to an object whose class matches the variable's type:

mammalVar = new Mammal();

The remaining part of Option A indicates that a variable can also hold a reference to any object whose class is a subtype of the variable's type. Therefore, since Dog and Whale are both subtypes of Mammal, the following assignments will also succeed:

mammalVar = new Dog();
mammalVar = new Whale();

Since Mammal is a class type, Option B does not apply. Using the words "is a", as we did earlier, is often a more intuitive way to determine if the variable is capable of holding a reference to a particular object. First, state the class of the object, then the words "is a", and finally, the type of the variable. The previous three assignments would be stated as:

A Mammal is a Mammal
A Dog is a Mammal
A Whale is a Mammal

As long as you know that a whale really is a mammal, then each of the statements makes sense, meaning the assignment would succeed. If the class of the object was Animal, the false statement:

An Animal is a Mammal

would make it obvious (since not all animals are mammals) that the assignment would not be permitted.

Let's move on to an example in which the type of the variable is an interface type:

WaterDweller waterDwellerVar;

This time, the pertinent part of Tenet 6 is Option B, which, applied to this particular case, indicates that the variable is capable of holding a reference to any object whose class implements the interface WaterDweller or any subtype of the interface WaterDweller. Since, as shown in Figure 2, FreshWaterDweller and SaltWaterDweller are both subtypes of WaterDweller the following assignments would succeed:

waterDwellerVar = new Fish();
waterDwellerVar = new GoldFish();
waterDwellerVar = new Whale();

What may not be intuitive is that the following assignment would also succeed:

waterDwellerVar = new Salmon();

The Salmon class "inherits" the implementation of the WaterDweller interface from the Fish class.

Casting
There are a couple of points I need to make about the innocuous looking and seemly intuitive "=" operator that I've been using throughout these examples. The "=" sign is the assigment operator. Wherever it appears, the value on the right side of the operator is being stored in the variable on the left side. While this might seem obvious, there are cases when this involves more than just moving a value from one place to another. As I mentioned earlier, in every assignment, the type of the value on the right side must match the type of the variableon the left. This assertion is seemingly contradicted by the fact that the following assignments succeed, despite that, in the second line, a value of type int is being assigned to a variable of type long.

int intVar = 10;
long longVar = intVar;

The reason that this works is that the compiler can determine that there's plenty of room to store the smaller value (a 32-bit int) into the larger value (a 64-bit long) without any chance of information being lost. Therefore, prior to making the assignment, the compiler automatically converts the int value to a long value.

In the opposite case, when a long value is being stored into an int variable, the compiler cannot guarantee that the value will fit. Therefore, the compiler forces you to include a cast operator which indicates that you are aware that the operation might cause some information to be lost, but want to do it anyway. The cast operator is the name of the type, that the value is to be converted to, enclosed within parenthesis:

intVar = (int) longVar;

Casting works slightly differently for variables of a reference type since, as Tenets 2 and 4 assert, neither the type of the variable nor the class of the object is ever changed. Let's look at another example:

Salmon salmonVar = new Salmon(); Fish fishVar = salmonVar;

In the first line, a variable named "salmonVar" of type Salmon is declared, a new object of class Salmon is created, and a reference to the newly created object is stored into "salmonVar". In the second line, a copy of the reference stored in the variable "salmonVar" is stored in the variable "fishVar." This assignment is permitted since the type of the variable "salmonVar" (Salmon) is a subtype of type of the variable "fishVar" (Fish). Note that the validity of the assignment has nothing to do with the class of the object, only the types of the respective variables are relevant. Just as the compiler is able to determine that no information will be lost when an int value is stored into a long variable, the compiler knows that a subtype will always be able to respond to all of the messages that can be sent to any of its supertypes.

As before, in the opposite case, when a supertype is being assigned to a subtype, the compiler cannot determine that the operation will always succeed, so it requires the programmer to confirm his intention by including a cast. Thus, to assign a copy of the reference contained in "fishVar" to a new variable of type Salmon, a cast to type Salmon is required:

Salmon newVar = (Salmon) fishVar;

Sending Messages
There are some ramifications that limit the usefulness of accessing an object through a variable whose type is a supertype of the object's class (as in "fishVar" above). In object-oriented programming languages, you don't directly "call" or "invoke" an object's methods. Instead, you "send a message" to an object that the object then responds to in the manner it deems to be most appropriate. A message is simply the name of an object's instance method and any required arguments. In general, a message is that part of a statement that follows the "dot operator" (the period). For example, the statement:

salmonVar.jump();

causes the message "jump()" to be sent to the object that the variable "salmonVar" refers to.

As Tenet 7 indicates, it is the type of a variable, which holds a reference to an object, that determines which messages can be sent, via that variable, to the object. The implication of Tenet #7 is that an attempt to send the "jump()" message using the reference stored in the variable fishVar would fail. Keep in mind that the problem isn't on the object's end. Tenet 4 still applies. The object's class is still Salmon, and any object of class Salmon can respond appropriately to the message "jump()". The problem is that the "fishVar" variable's type is Fish and there isn't any method named "jump" defined in the Fish class or in any of its superclasses. Only variables of type Salmon (or if it had any, Salmon's subclasses) can be used to send the "jump" message to the object.

Finally, as Tenet 8 indicates, it is the class of the object, which receives a message, that determines what happens in response to that message. It is very common for a subclass to "override" a method implemented in one of its superclasses. For example, the implementation of the movement method in the Mammal class returns the String "quadraped" since most mammals walk on four legs. The Whale class overrides the movement method so that it returns the more appropriate String "swim". The Whale object will always return "swim" in response to a movement message regardless of the type of the variable from which the message was sent.

The Test
OK, that's it! You're now qualified to analyze whatever Java syntax comes your way. Here's the test. Your objective is to determine which statements are legal (ignore the ones that aren't when determining the current state of the variables). Good luck, and may the Tenets be with you.

Test Answer

About the Author
Mark Robinson is the president/CEO of Cyberian Foundations, a software development/consulting firm that specializes in developing object-oriented business applications. He can be reached at [email protected]

	

Listing 1

public abstract class Animal
{
   public abstract String movement();
}


public class Fish extends Animal
             implements WaterDweller
{
   public String movement()
   {   return "swim";
   }
}


public class Salmon extends Fish
{
   public void jump()
   {   System.out.println( "Jump" );
   }
}


public class GoldFish extends Fish
             implements FreshWaterDweller
{
}


public class Mammal extends Animal
{
   public String movement()
   {   return "quadraped";
   }
}


public class Dog extends Mammal
{
}


public class Whale extends Mammal
             implements SaltWaterDweller
{
   public String movement()
   {   return "swim";
   }
}

Listing 2

public interface WaterDweller
{
}


public interface FreshWaterDweller
       extends WaterDweller
{
}


public interface SaltWaterDweller
       extends WaterDweller
{
}


 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.