Java Tutorial

Introduction

This is a brief introduction to Java based on my experience and various sources on Java available on the Internet. It assumes experience in object-orientation, and it refers to C and C++ but not in an essential way.

Java is a programming language that was designed to be especially useful for creating applications that could be run from a Web browser. It was not the first one or the only one to offer this feature, but it is one of the most popular.

However, Java is not just a platform for creating small Web applications. It is a general-purpose programming language which can, in principle, be used for any application area. I was responsible for developing one of the largest Java applications of its time (over 400,000 lines). In this tutorial, the Web aspects of Java will, for the most part, be ignored. The emphasis is on Java as a general-purpose programming language.

The Java language is often touted as being a variation on C++. Although it has a superficial (syntactic) resemblance to C++, it is semantically very different. In many ways, Java is semantically much closer to Smalltalk than it is to C++.

One also sometimes sees claims that Java is a simpler language than C++. It is not clear what this claim is based on. Java has many features that are built into the language rather than being supplied by optional class libraries. These features include a built-in class library, threads, synchronization, security, garbage collection and reflection. This makes the language much more complicated, and the newest version of Java, version 1.1, has increased this complexity still more with many new features in the language itself, as well as a large number of new classes in its development kit. At the same time Java omits many features of C++ such as overloaded operators, conversions, concrete data types, constants, pointer arithmetic and templates. This simplifies the language, but this simplification has a price. Omitting some features of C++, such as pointer arithmetic, may be an advantage because so many subtle bugs in C and C++ are the result of the misuse of this feature. However, the lack of other features, such as constants, overloaded operators, concrete data types and templates means that many important program designs are not expressible in Java. In any case, since the number of features built into Java compared to C++ is about the same as the number of features that Java does not have compared to C++, there is no compelling evidence that Java is either significantly less complicated or significantly more complicated than C++. Moreover, this was true of the previous version of Java. There is no question that Java 1.1 is significantly more complicated than C++.

Hello, World!

The simplest example of a program has traditionally been the Hello, World! program. The following program illustrates not only the basic program but also the style commonly used for Java programs. You should try extracting this program to a file and running the compiler (java), documenter (javadoc) and run-time environment (java) to see what happens.

/**
 * The classic <i>Hello, world!</i> program in Java.
 * This program illustrates a number of conventions such
 * as the comment format which will be recognized by javadoc.
 * Class names usually begin with an upper case letter,
 * while methods begin with a lower case letter.
 * The file name is the class name followed by the .java
 * extension.  To compile use the command:
 * <pre>
 *   java Hello.java
 * </pre>
 * To generate the javadoc HTML file use the command:
 * <pre>
 *   javadoc Hello.java
 * </pre>
 * The documentation can be viewed by going to the file
 * <pre>
 *    Hello.html
 * </pre>
 * from any Web browser.  To run the program use the command:
 * <pre>
 *   java Hello
 * </pre>
 */
public class Hello {
  /**
   * Main Program.
   * <p>
   * Every program must have exactly one public static
   * main method which takes a String[] argument.  Like
   * C or C++ programs, the program terminates when the
   * main method terminates.
   * @param argv The array of command-line arguments.
   */
  public static void main (String[] argv) {
    System.out.println ("Hello, world!");
  }
}

One important property of Java is that all functions and variables are within some class. There are no "global" variables or functions as in C. In particular, the main function of any Java program must be a method of some class.

Another property is that functions are always declared and defined at the same time. There is no notion of a "header" file where classes are declared separately from the file where the class members are defined.

The main function of a Java program has just one parameter, of type String[]. Although arrays in Java are syntactically similar to arrays in other languages, they differ greatly semantically. The array notion in C or C++ is a low-level allocation mechanism which is not well suited to high-level use. For example, a variable of type int[] in C or C++ is not an object but a constant pointer to the first object in an array of integers. By contrast, the array notion in Java is a high-level notion. A variable of type int[] is a pointer to an object that contains both the length of the array and the actual array of integers. For example, the length of the argv array is written argv.length. Once allocated, the length of a Java array cannot be changed.

Standard I/O in Java is handled by the System class. For example, System.out is a static variable that points to the standard output file. It is roughly the same as stdout in C or cout in C++. System.out.println is a method that prints its parameter (usually a string) on the standard output file, followed by a newline character. If this final newline is not desired, then one can use the System.out.print method instead.

Control Structures, Expressions and Pointers

The syntax of control structures (such as for and if), assignment statements, simple variable declarations, and comments are almost the same in Java as in C or C++. For example, the following Java main function prints its own command line:

  public static void main (String[] argv) {
    String commandLine = argv[0];
    for (int i = 1; i < argv.length; i++) {
      commandLine = commandLine + " " + argv[i];
    }
    System.out.println (commandLine);
  }

Notice that the plus sign is used for string concatenation.

Although commandLine is a pointer to a String object, it is not necessary to specify that with an asterisk as in C or C++. It is sometimes claimed that Java doesn't have pointers. In fact, the opposite is true. Except for variables of the primitive types, every Java variable is a pointer. Because of this limitation, Java has no special notation for declaring a pointer, nor is there any notation for computing an address or dereferencing a pointer. Interestingly, this is one of the rare examples in which Java and C++ are similar semantically while differing syntactically rather than the other way around.

Values and Objects

There are eight primitive types in Java:

TypeDescription
booleanSimilar to the C++ bool type but cannot be converted to int. An integer or pointer cannot be used in a boolean context (such as an if condition they way it can in C or C++.
charSimilar to the C char type but uses 16 bits
byteAn 8-bit signed integer
shortA 16-bit signed integer
intA 32-bit signed integer
longA 64-bit signed integer
floatA 32-bit floating point number
doubleA 64-bit floating point number

As in most object-oriented programming languages, objects are instances of classes. Each class has a name and members. Each class is also a member of a package of classes. Members can either be data members or function members. Members can also be either static or nonstatic. Data members are called fields. If a field is nonstatic, then it can have a different value in each object. A static field has just one value no matter how many instances there are in the class. Function members define the behavior of a class. They are called methods in Java. The following example illustrates these concepts:

package registrar;

/**
 * Student Class
 * <p>
 * A Student object consists of a name and identifier.
 * The total number of Student instances is maintained in a
 * static variable and can be retrieved by a static method.
 */
public class Student {
  private String name;
  private String identifier;
  private static int count = 0;
  /**
   * Construct a Student object
   * @param n The name of the new student
   * @param id The identifier of the new student
   */
  public Student (String n, String id) {
    name = n;
    identifier = id;
    count++;
  }
  /**
   * Compute the total number of Student objects
   * @return The total number of Students
   */
  public static int total() {
    return count;
  }
  /**
   * Deallocate a Student object
   */
  protected void finalize() {
    count--;
  }
}

Each file can define any number of classes, but exactly one must be public. None of the others are visible outside the file. Classes are grouped into packages, a notion that is important for access control to be discussed later. If no package specification is given, then the classes in the file belong to the default package. The name of the file must be the same as the name of the public class together with the .java file extension.

A method having the same name as the class is a constructor for instances of the class. There is no concept of a destructor as in C++. However, one can define a method called finalize which is guaranteed to be called when the object is deallocated so it is roughly equivalent to a destructor. This is discussed in more detail in the next section.

All pointer variables are initialized to null unless explicitly initialized to some other value. Variables of the primitive types are initialized to a default value depending on the type, unless initialized to some other value.

Java does not support the C++ notion of a reference type which is mainly used for passing parameters by reference. In particular, Java does not support passing parameters by reference. This makes it awkward to return more than one value from a method. One can achieve the same effect by defining a class so that an instance of the class will contain the desired return values. One can then define the return type of the method to be this class. There are other techniques that can also be used to compensate for this limitation of Java, but they are all awkward in some fashion. The best way to deal with this is to design the program so that it does not need such return values in the first place. As a result, Java programs tend to be designed differently from programs in other object-oriented programming languages.

There are many other examples of this phenomenon. Despite the superficial similarity between Java and C++, one cannot "port" code from one to the other very easily. It is usually necessary to redesign the whole program structure.

Garbage Collection

New objects are created by the new operator in Java in almost the same way as in C++. This allocates space for the object and also calls a constructor. However, there is no delete operator in Java, and one cannot define a destructor for a class. The Java system automatically deallocates objects when no references to them remain. This is done by a process within the Java run-time system called the garbage collector. When an object is deallocated, the finalize method is called. This is roughly analogous to a destructor. However, there is no way to predict when the garbage collector will deallocate an object or indeed whether it will do it at all for any particular object.

Garbage collectors are very useful for preventing certain errors that can occur when objects are not carefully allocated and deallocated. One can add a garbage collector to any object-oriented programming language, and there are many available for C++. Java and Smalltalk differ from C++ in having a built-in garbage collector.

It is often claimed that garbage collectors eliminate the problems of dangling references and memory leaks that occur in programs that do not use a garbage collector. A dangling reference is a pointer to an object that has been deallocated. A memory leak is an object that can no longer be referenced but has not been deallocated. Both of these are serious problems. The use of a dangling reference can cause the program to behave unpredictably or even to terminate abnormally. A memory leak, if it involves a large number of objects, can cause the program to run more slowly and possibly terminate if it can no longer allocate space for new objects because so much space has become unusable.

Unfortunately, it is a myth that a garbage collector will solve either of these problems. Sloppy design and programming can result in situations that are semantically equivalent to a dangling reference or a memory leak, even if one is using a garbage collector. I have observed both of these situations in large Java programs. Indeed, it is possible that these problems are more likely to occur in a Java program precisely because one does not think these problems can occur so one does not make any effort to design the program carefully so as to prevent them. The moral is that good design is necessary no matter what tools one is using.

Access Control

An important feature of any object-oriented language is "data hiding". One can restrict access to a member of a class to a specified scope. Access control makes it much easier to determine all of the places where a field or method may be used. This makes it much easier to verify that the field or method will be used and modified correctly according to its specifications.

Java differs from C++ in having a much more complicated and awkward access control mechanism. Java has 5 kinds of access control specification compared to the 3 kinds in C++. Moreover, the various Java books I have reviewed differ from one another in their explanations of what the Java access controls mean and also differ from what the access controls actually do. Yet despite this added complexity, it is often not possible to restrict the scope of a variable or method in as precise a manner as one can in C++. In large Java projects, one will often allow variables to be visible in a much larger scope than one would like simply because one cannot specify otherwise using Java's access control mechanism. After attempting to use Java's convoluted access control mechanism, one soon longs for the elegant simplicity of C++.

The Java access control specifiers are:

SpecifierDescription
privateThe scope is restricted to the methods of the class itself.
publicThe scope is unlimited.
protectedThe scope is limited to methods of subclasses and classes in the same package. Not the same as the C++ protected access control.
private protectedThe scope is limited to methods of subclasses. This is the same as the C++ protected access control, but it is no longer supported in Java 1.1.
When no access control specifier is given, the access control is said to be "friendly". It restricts the scope to the methods of the classes in the package containing the class.

Please note that the descriptions above are only rough approximations to the full specification in each case (except for public and private). Please refer to a more complete introduction to Java, or do some experimentation on your own.

It is sometimes claimed that the Java keyword final is roughly equivalent to const in C++. This is not true. The only situation in which final is similar to const is for a variable of a primitive type. This allows one to define named constants, similar to what one can do with enum in C or C++.

The following example illustrates how one could define a type roughly equivalent to a C++ enumeration. This example also introduces the use of exceptions and inheritance.

class Weekday {
  private int day = MONDAY;
  final static int SUNDAY = 0;
  final static int MONDAY = 1;
  final static int TUESDAY = 2;
  final static int WEDNESDAY = 3;
  final static int THURSDAY = 4;
  final static int FRIDAY = 5;
  final static int SATURDAY = 6;
  /**
   * Construct a day of the week
   * @param d An integer from 0 to 6 specifying the day to be used.
   * @throws WeekdayException if the parameter is out of range.
   */
  public Weekday (int d) throws WeekdayException {
    if (d >= 0 && d <= 6) day = d;
    else throw new WeekdayException
      ("Attempt to construct a nonexistent day of the week");
  }
  /**
   * The string equivalent of a day of the week
   * @return The string representing the day of the week
   */
  public String toString() {
    switch (day) {
      case SUNDAY: return "Sunday";
      case MONDAY: return "Monday";
      case TUESDAY: return "Tuesday";
      case WEDNESDAY: return "Wednesday";
      case THURSDAY: return "Thursday";
      case FRIDAY: return "Friday";
      default: return "Saturday";
    }
  }
  /**
   * A simple main program for testing the Weekday class
   */
  public static void main (String[] argv) {
    try {
      Weekday d = new Weekday (Weekday.SATURDAY);
      System.out.println (d);
    } catch (WeekdayException we) {
      System.out.println (we);
    }
  }
}
/**
 * Exception class for Weekday
 */
class WeekdayException extends Exception {
  /**
   * Construct a new Weekday Exception
   * @param msg The message that explains why the exception occurred.
   */
  WeekdayException (String msg) {
    super (msg);
  }
  /**
   * The string equivalent of this exception
   * @return The string representation of the exception.
   */
  public String toString() {
    return "An error occurred in the Weekday class: " + super.toString();
  }
}

However, constant values are a relatively minor use of const in C++ (and one can use enum instead). This keyword in C++ is primarily used to declare constant parameters, constant objects and constant methods. A well designed C++ program will use const heavily. Doing so will prevent a large number of common programming errors that are otherwise very difficult to find. This feature is not available in Java.

The fact that Java does not support constant objects is not entirely obvious. If a pointer variable is declared to be final in Java, it is the pointer that is constant, not the object it points to. The object itself can still be modified.

It is possible to specify that a Java method is final, but this does not make the method constant. It means that the method cannot be overridden in a subclass.

Strings

We have already seen examples of the use of the String type. Unlike C++, this type is a built-in type of the language.

The Java String type makes use of overloaded operators and conversion operators, neither of which is available for user-defined classes. The + operator is overloaded on Strings to mean concatenation, while the use of an object in a String context will cause it to be converted to a String using the toString method of the object. Every object has a default toString method, but it is very common to override the default meaning. Several examples of String conversions and overridden toString methods were given in the previous section.

The String class has many other useful methods, such as substring, comparison, matching and even simple parsing. Refer to the String documentation for more details.

Once constructed, a String object cannot be modified. The String operations that appear to be modifying Strings in the examples above are actually allocating new String objects and changing the pointer variables to point to these new objects. A different class called StringBuffer is available for directly modifying the value of a String object. Unless performance is an important issue and String manipulations are affecting the performance, it is usually better to use String rather than StringBuffer.

Inheritance and Interfaces

A Java interface is a class declaration without any fields, constructors or method implementations. It consists of just a collection of nonstatic method declarations without any method implementations. Here is an example of a commonly used interface:

public interface Enumeration {
  boolean hasMoreElements();
  Object nextElement();
}
This interface is used for iterating through a collection of objects. For example, consider a class CourseSection an instance of which represents a course section in a school. Suppose that this class has a method students() that returns an Enumeration over the students in the class. The following is a common idiom for iterating over a collection of objects:

  for (Enumeration e = section.students(); e.hasMoreElements(); ) {
    Student s = (Student) e.nextElement();
    // ...
  }

The nextElement method always returns an Object, not a Student so it is necessary to perform a cast to make any nontrivial use of the objects in the iteration. Casts are discussed in the next section.

Methods in an interface are always public and abstract. Data members can only be constant values (i.e., public, static and final).

Java interfaces are roughly analogous to an abstract class in C++, except that an abstract class in C++ can have data members and can provide default implementations for some of its function members. Java also has abstract classes that are similar to abstract classes in C++. However, in most cases where one would use an abstract class in C++, one would use an interface in Java.

Interfaces are important for the notion of inheritance in Java. In C++ there is just one mechanism for inheritance. In Java there are two inheritance mechanisms. The usual notion uses the extends keyword. It is used for declaring that one class or interface is derived from another class or interface, respectively. Java allows only single inheritance for classes. A class can extend at most one other class. An interface, on the other hand, can extend any number of other interfaces. Furthermore, a class cannot extend an interface or vice versa.

The relationship betweeen classes and interfaces is a different form of inheritance which uses the implements keyword. In contrast to the extends mechanism, a class can implement any number of interfaces. So while Java only supports single inheritance for classes, it supports multiple inheritance of interfaces. This was done because it has been observed that most uses of multiple inheritance, in practice, are a form of interface inheritance. Since multiple inheritance introduces a great deal of complexity to a language, interface inheritance is actually a simplification of the inheritance mechanism despite introducing some new concepts.

Intuitively, interface inheritance means that the class is specifying that it will provide all the services in the interface and possibly other services as well. This is sometimes called subtype inheritance. Extension inheritance, on the other hand, means that every instance of the subclass may be regarded as an instance of the superclass. In other words, the subclass inherits all of the properties (except constructors) of the superclass, including both data and behavior, and all of the services (behavior) are done in the same way unless explicitly overridden.

Every class in Java extends, directly or indirectly, the class Object, except for the class Object itself which is the "root" class of the hierarachy of all classes. One should study the Object class carefully, as it specifies the methods shared by all Java classes.

Casts and Conversions

Casts and conversions are two very different concepts. A conversion is a transformation of an object or value from one type to another. For example, one can convert an integer to a floating-point number, or an integer to a string. A cast is a deliberate violation of the type system. The underlying representation of an object or value of one type is reinterpreted as an object or value of another type. For example, both an int and a float are represented by 32 bits of data. If one converts the int 1 to a float, one obtains 1.0. The underlying binary representations are different, but the numbers are the same (or at least an approximation). If one casts the int 1 to a float, one obtains a number that depends on the underlying machine architecture. For example, on an Intel Pentium one gets 1.4013e-45.

In C, the same notation (and terminology) is used for both, and Java does the same. C++ not only distinguishes casts and conversions, it also distinguishes several different kinds of cast.

Conversions are normal operations which are often invoked implicitly. For example, a number used in a string context will automatically be converted to a string without any explicitly specified conversion. This is true both in C++ and Java.

Casts, on the other hand, are unusual operations. Because they violate the type system, one should avoid them if possible and take great care when they are necessary. A well designed C++ program will have very few, if any, casts. Based on my experience, large Java programs use casts frequently and routinely. An average of one cast for every 5 or 6 lines of code is typical. The structure of the language makes them necessary in a large number of situations that have no analog in other object-oriented languages.

One can determine the type of an object by using the instanceof operator. The enumeration example above would then look like this:

  for (Enumeration e = section.students(); e.hasMoreElements(); ) {
    Object o = e.nextElement();
    if (o instanceof Student) {
      Student s = (Student) o;
      // ...
    }
  }

However, a better way to deal with this situation is to perform the cast without checking that it will work, and catch the exception if it fails. This is discussed in the next section.

Exceptions

A well written program in any language should check for unexpected situations and deal with them rather than just allow the program to terminate abnormally. The mechanism for doing this is called exception handling An exception is an object which is thrown by the method when it encounters an anomolous situation. The exception can then be caught and handled. Handling an exception is often done by extracting information from the exception object and then throwing another exception.

This is important for ensuring that the program is properly modularized. The method that caused the exception should not be concerned with how the exception is to be handled. Such concerns violate the modularity of the program by inserting high-level code into low-level methods. This makes the low-level methods less reusable and causes classes to be more tightly coupled.

Examples of exception handling have already been given. Here is the enumeration example as it should be programmed:

  try {
    for (Enumeration e = section.students(); e.hasMoreElements(); ) {
      Student s = (Student) e.nextElement();
      // ...
    }
  } catch (ClassCastException e) {
   //  Handle exception
  }

One common way to handle an exception when a program is being debugged is to "dump core". The way this is done in Java is to print the stack trace. This is especially important when the call nesting level is relatively deep. In such a case it helps to have a trace of all the calls that were made between the method that threw the exception and the one that caught it.

In Java one must declare the types of the user-defined exceptions that can be thrown by the method or by any method that it calls. The compiler will check that all user-defined exceptions are eventually caught. The built-in exceptions need not be declared or caught, but it is a good idea to catch them anyway.

One can have several catch clauses after a try block, and one can also have a finally clause which will be executed after the block completes no matter how this happens.

Threads

The main reason why Java was introduced in this course is that it supports threads. A thread is an object which executes concurrently with the other threads that exist at the same time. Here is one way to construct and run a thread:

    Thread t = new Thread (command);
    t.start();
    // ...

The command object passed to the Thread constructor is any object that implements the Runnable interface. This interface has just one method named run which is the method that is executed by the newly created thread when the thread's start method is called.

It is interesting to compare this technique with how it would have been designed in C. In C, the command would have been a pointer to a function rather than an object. In Java, there are no pointers to functions, so one uses objects implementing an interface to serve the same role. Of course, interfaces are more general than pointers to functions, and they have many other uses as well.

Threads are similar to the operating system concept of a process except that processes execute in separate address spaces, while all the threads execute in the same address space. This means that two threads could make use of the same object concurrently. This can lead to unpredictable results, so it is necessary to ensure that threads cooperate with each other on shared objects.

Thread synchronization is done by setting locks on objects. This is similar to database locking except that all Java locks are exclusive locks. Other modes, such as shared locks or intention locks, are not supported. This seriously limits the level of concurrency possible in a Java program, and it also greatly increases the likelihood that a deadlock will occur. However, this mechanism is enough for the purposes of the very small programs we will be writing in this course. Incidentally, in Java the exclusive lock on an object is called its monitor.

In Java, one does not explicitly set and release locks. This is done implicitly by executing a method declared to be synchronized or by executing a block that sychronizes on the object. When such a method or block starts, an exclusive lock is set on the object. When the method or block returns, the lock is released. As in databases, a thread can hold the same lock more than once. This can happen if a synchronized method or block executes another synchronized method or block using the same object. The Java run-time system increments a lock count, exactly as in a database lock manager, each time a synchronized method or block is started, and decrements it each time a synchronized method or block returns.

Incidentally, a static method can also be synchronized. When this is done, the object that is locked is the object of type Class corresponding to this class. Each class has a unique corresponding instance of type Class. One can acquire the Class instance of an object by executing the getClass method of any object.

The following is an example of the use of synchronized methods. This example uses the Vector class and Enumeration interface. Neither of these is one of the built-in Java classes. So they have to be "included" in the java file. Java uses the import keyword for inclusion of a class or package. To include the entire java.util package, the statement import java.util.*; should be used.

import java.util.Vector;
import java.util.Enumeration;

/**
 * The Dumpster Class checks in objects but never releases them.
 */
public class Dumpster {
  private static Vector contents = new Vector();
  /**
   * Check an object into the global dumpster
   * @param obj The object to be placed in the global dumpster.
   */
  public static synchronized void dump (Object obj) {
    contents.addElement (obj);
  }
  /**
   * Construct a string that represents the current dumpster contents.
   * @return The string representation of the global dumpster.
   */
  public static synchronized String contains() {
    String s = "The dumpster currently contains the following:\n";
    for (Enumeration e = contents.elements(); e.hasMoreElements(); ) {
      s += e + "\n";
    }
    return s;
  }
}

If the Dumpster class is generalized to allow objects to be removed as well as inserted, then we have a solution to the classical concurrency problem known as the Producer-Consumer problem. We are indebted to a student, Michael Vigneau, in this same course a few years ago for the following graphic illustration of the Producer-Consumer problem.

Graphic Example of The Producer/Consumer Problem

If there is just one producer and one consumer, then it is not necessary to use locks. Simple load-store synchronization has much better performance. In principle, one can use load-store synchronization whenever the number of producers and consumers is bounded and known in advance. The solution using locking is appropriate only when the producers and consumers are not known in advance.

The SharedStack class is an example of a solution to the Producer-Consumer problem in which the capacity of the buffer is unbounded. In this case, only a consumer may ever have to wait, which occurs when the buffer is empty. If the buffer is bounded, then both producers and consumers may have to wait. Producers may have to wait for space to become available, while consumers may have to what until something is in the shared stack.

Whether bounded or unbounded, the Producer-Consumer problem cannot be solved with locks alone. The consumer must wait until something is in the buffer. While waiting it must sleep. This is accomplished with the wait method. The producer must somehow convey to any waiting consumers that an item is now available. In other words, it has to wake up at least one consumer. This is accomplished with the notify method. The wait and notify methods are in the Object class, so every object has them.

import java.util.*;

/**
 * A global shared stack class.
 */
public class SharedStack {
  private static Vector stack = new Vector();
  /**
   * Push a new object onto the shared stack.
   * @param obj The object to be pushed onto the stack.
   */
  public static synchronized void produce (Object obj) throws ClassNotFoundException {
    stack.addElement (obj);
    Class.forName ("SharedStack").notify();
  }
  /**
   * Pop the top object off the stack or wait, if needed, until there is one.
   * @return The top object on the stack.
   */
  public static synchronized Object consume() throws ClassNotFoundException {
    while (stack.isEmpty()) {
      try {
        Class.forName ("SharedStack").wait();
      } catch (InterruptedException e) {
      }
    }
    Object obj = stack.lastElement();
    stack.removeElementAt (stack.size() - 1);
    return obj;
  }
  /**
   * Construct a string that represents the current shared stack.
   * @return The string representation of the global shared stack.
   */
  public static synchronized String contains() {
    String s = "The shared stack currently contains the following:\n";
    for (Enumeration e = stack.elements(); e.hasMoreElements(); ) {
      s += e + "\n";
    }
    return s;
  }
}

We leave it as an exercise to generalize the SharedStack class to the case of a bounded buffer.

Miscellaneous Features

A variety of classes are available in the Java toolkit as well as many other sources. Certain classes are built into Java in a fundamental way. These are in the java.lang package. You need to know all of these classes in order to understand Java.

The classes in the java.io package define the file and stream input and output classes. These classes are not built-in, but they are used by the built-in classes so it is helpful to be somewhat familiar with this package. This package is roughly analogous to the C++ iostream library.

The java.util package has many frequently used utility classes such as the Vector class and Enumeration interface used in the examples above. Although these classes are not built-in, they are used by the built-in classes so one needs to be familiar with this package, just as one needs some familiarity with java.io.


Ken Baclawski
207 Cullinane Hall
College of Computer Science
Northeastern University
360 Huntington Avenue
Boston, MA 02115
kenb@ccs.neu.edu
(617) 373-4631 / Fax: (617) 373-5121

Copyright © 1998 by Kenneth Baclawski. All rights reserved.