Princeton University
Computer Science Dept.

Computer Science 441
Programming Languages
Fall 1998

Lecture 15


More Java

Here are a few general points about Java which are useful to know in understanding the language.

The final keyword

A class, method, or instance variable declaration may be prefixed with the keyword final. A feature declared final may not be changed.

A variable declared to be final must clearly be a constant (be sure to initialize it in the declaration or you will never be able to set it). A final method is one that may not be overridden. A final class is one that may not be extended.

In each case, the declaration of an entity to being final provides a guarantee to both the programmer and the compiler that the entity will never be changed again. This may result in a gain of efficiency for the compiler and better understanding for the programmer, but if mis-used can result in a loss of flexibility (the usual binding time trade-offs!).

A constant is almost always declared as public static final ... as it is generally visible, there only needs to be one copy for the entire class (there need not be a separate copy in each object) and it may not be changed.

Type equivalence and subtypes

If class SC extends class C, then an object created from SC (i.e., of type SC) can be used in any context expecting an object of type C. Thus SC is treated as a subtype of C. This is sound because objects of the subclass have at least all the methods of the superclass.

If class C implements interface IC then objects of type C can be used in any context expecting an object of type IC.

While we haven't shown examples yet, one interface may also extend another. This also results in subtyping.

Subtyping is by declaration (name) in Java. Thus one type may be used as a subtype of another only if there is a chain of extends and implements going from the first to the second. For example if we have

   SC extends C
   C implements IC
   IC extends IBC
then an object from class SC can be used in any context expecting an object of interface IBC.Dynamic method invocation Because Java objects keep track of their own method suites, the static type of an object may not tell you what code will be executed when you send a message to that object.

As above, at run-time the contents of a variable of type (class or interface) C may actually be an object created by an extension. Thus one typically cannot determine statically whether the code from C (presuming C is a class) or one of its extensions will be executed. If C is an interface, then one has even less information statically about what code may be executed. This is generally considered a GOOD thing in object-oriented programming!

Objects are references

As in most purely object-oriented programming languages, objects in Java are held as implicit references. This means that if you test two objects are "==" each other, then the answer will be true iff they are the same object. Similarly, assignment (=) of objects in Java represents sharing, not copying. Thus if emp is an expression whose value is different from null and whose static type extends Employee, and e is a variable of type Employee, then
   e = emp;
results in e being a reference to the same value as originally given by emp.

It is worth noting here that constants holding objects aren't necessarily very constant. If e is a constant holding an HourlyEmployee then you may not assign to it, but you may send it messages like setHourlyPay(6.25) which will result in a change to the instance variables of the object. Thus constants do not change their identity, but object constants may change state!

... but base types are not

Base types in Java (int, float, char, bool, etc) do not contain objects. These are normal values of the sort you would find in any procedural programming language. Thus assignment with variables of these types has the usual copy semantics and equality tests for the same value rather than the same location in memory.

Java does not have explicit pointers, as these are unnecessary. New objects are created by preceding the constructor of a class with the keyword new. Like ML, objects that are no longer accessible are swept up by the garbage collector and recycled.

... though base types can be wrapped up as objects!

Later on we will see that it is sometimes awkward to have base types held differently from object classes. (This has to do with the handling of "generic" container classes, classes like List, Stack, Tree, etc. which can be designed to hold elements of any object type.) As a result, for every base type, Java provides a corresponding object type. They typically start with a capital letter and are spelled out in full. Thus Integer corresponds to int, Boolean to bool, Double to double, Character to char, etc. Note that String is already an object type, so there is no need to wrap it.

You can create objects of these types using the fairly obvious constructors: new Integer(3), new Character('z'), etc. You can extract the values with fairly obviously named methods: i.intValue(), abc.charValue(), etc. Objects of these types are immutable. Thus you cannot change the value held inside an object of the wrapper classes. Of course if you have a variable of type Integer, you can create a new value of the type and assign it to replace the old value. Make sure you understand the distinction I am making here!

Parameter passing

Like most pure object-oriented languages, Java only supports by "call-by-sharing". At the implementation level, this is the same as call-by-value. I.e., the actual parameter value is copied into the formal parameter before executing the body. At the end of the execution of the body, the formal parameter just goes away - no copy back occurs. I prefer to describe it as call-by-sharing because, while you may not replace the value via an assignment in the method body, you may change the state of an object value. In particular, if a parameter evaluates to an object, one can send messages to it which can change its state.

For those not used to it, this can seem very restrictive, especially since one cannot cheat like in C and pass an explicit pointer. However, one soon adjusts to the style and I suspect you will grow comfortable with it quite quickly.

This: the name I call myself

Inside the methods of a class, the keyword can be used to refer to the object executing the method (i.e., the receiver of the message). In particular, one can get access to the instance variables of that object, send messages, or even use this as a parameter in another message send. In fact, every time you access an instance variable in a method by just writing its name, it is treated as an abbreviation for the same name prefixed by this. Here is an example using a class representing a doubly-linked node:
   public class DbleNode{
      protected int value;
      protected DbleNode next, previous;

      public DbleNode(int value, DbleNode next){
         this.value = value;
	 this.next = next;
      }
Let's take a quick time-out before writing the rest of the definition of the class. Notice that within the body of the constructor, DbleNode, the parameters value and next block the visibility of the instance variables value and next. Luckily we can refer to the instance variables by prefixing them with this. The net result is that the two assignment statements assign the parameter values to the corresponding instance variables.

This is a common idiom in Java that allows you to be lazy about thinking up new names for parameters that are different from the instance variables, but close enough to be suggestive of their intended meanings. Let's continue with the definition of the class:

      protected void setPrevOnly(DbleNode pred){
         previous = pred;  // could have written this.previous, but not necessary.
      }

      public void setNext(DbleNode newNext){
         next = newNext;
	 if (newNext != null)
	    newNext.setPrevOnly(self);
      }
   }
The last method is interesting in that self is sent as a parameter in a message to newNext. While Java would not prevent me from assigning to newNext's instance variable (a method can access all date - no matter what the protection - of other objects of the same class!), it is considered very bad style to access the instance variables of other objects. Hence my use of the setPrevOnly method.

Data structures in Java

Java does not need records or structures, because we can think of them as just being very stupid objects. (Recall that, unlike C++, Java is only intended to be an object-oriented language. C++ is intended to support several different styles of programming, including its use as a "better C".) Similarly linked structures are obtained by just creating classes with instance variables holding all of the objects you wish to link to.

Thus of the basic data structures or building blocks for data structures, only arrays must be dealt with. Arrays in Java are objects, and hence must be created. The declaration for an array of employees would be written:

   protected Employee[] emp;
Technically, you can also write the declaration as in C++:
   protected Employee emp[];
but I prefer the former as it makes it clear what the type is and makes a bit more sense when the declaration also initializes the array:
   protected Employee[] emp = new Employee[75];
Notice that the declaration of the array does not include the size of the array, only the type of elements that it holds. Thus at various points in the program emp could hold arrays of different sizes. It is only the call to the constructor that determines the size of the array to be created. In particular, then, Java avoids Pascal's problems with passing arrays as parameters since the size of the array is not part of the type.

The programmer does not need to declare a constructor for arrays (in fact it's hard to imagine where one would put such a constructor). Thus, once the Employee class exists it is easy to construct an array of employees. However, one must be sure not to confuse creating an array with creating its elements.

Before the call of the constructor above, the value of emp was null. After the execution of the constructor, becomes an array with 75 slots (ranging from 0 to 74), each of which contains null(!). If you wish to insert elements into the array, you must create them. Thus one would expect to see some sort of loop which fills each slot with a non-null employee value.

Multi-dimensional arrays are obtained by creating arrays of arrays. Java does not provide the same nice abbreviations as Pascal to make you think of them as multi-dimensional arrays. However, it does provide a bit more flexibility:

   Employee[][] squareEmps = new Employee[75][75];
      // this builds a 75 by 75 array.
   Employee[][] triangleEmps = new Employee[75][];
      // this builds an array with 75 rows, each of which is currently empty.
   for (index = 0; index < 75; index++)
      triangleEmps[index] = new Employee[index+1];
The for loop assigns row index the size of index+1. Thus the 0th row has size 1 up to the 74th row having size 75. Of course you still need to initialize the individual elements of the array. You would get access to an individual element by writing triangleEmps[3][2], for example.

If you read the previous comments about parameter passing in Java, you may be wondering how you can change the values in an array if the array is passed as a parameter. Luckily there is no problem. You still can't assign to an array parameter as a whole (well, actually you can but it won't have any impact on the original array ...), but you can assign to individual elements of the array. Thus if emp is a formal parameter of array type, then emp[3] = newEmployee changes the entry in slot 3 of the actual parameter, at least if it has a third slot.

By the way, Java, unlike C++, automatically checks array boundaries when accessing array entries. Arrays do keep track of their length, and you can get access to the length of an array by writing emp.length. Be sure not to include parentheses after length as it is an instance variable rather than a method (bad Java designers, bad!).


CS441 | CS Department | Princeton University