The content on this page is the work of Professor Glenn Blank.
Be advised that Professor Blank is no longer on the active faculty at Lehigh.
This content continues to be available as a courtesy, but it may not be maintained or current.

INTRODUCTION TO JAVA
by David Murray and revised by Glenn D. Blank
(http://www.cse.lehigh.edu/~glennb/oose/java/javalec.htm)

I. Introduction

Java is a general purpose object-oriented programming language, which researchers at Sun Microsystems originally developed to control intelligent consumer electronic devices. Now best known as a web programming language, Java's functionality extends beyond the web with support for the development of general purpose applications in both standalone and client-server environments on a variety of hardware platforms.

Java is largely a collection of constructs and features found in other programming languages:

  • Java borrows much of its syntax, class scoping, and exception handling from C++;
  • Java borrows its model of runtime extensibility, dynamic memory management and multithreading from Smalltalk; and
  • Java borrows the concept of interfaces from Objective-C.

II. Design Issues

A. Java is Object Oriented

Everything in Java is related to the class construct, which forms the basis for object oriented programming in the language.

1. Basics of Objects

At its simplest, object technology is a collection of analysis, design, and programming methodologies that focuses design on modeling the characteristics and behavior of objects in the real world. Objects have state and behavior. For example, a car can be modeled by an object. A car has state (how fast it's going, in which direction, its fuel consumption, and so on) and behavior (starts, stops, turns, slides, and runs into trees).

In the programming implementation of an object, its state is defined by its instance variables . Instance variables are usually private to an object. Unless explicitly made public or made available to other "friendly" classes, an object's instance variables are inaccessible from outside the object.

An object's behavior is defined by its methods. Methods manipulate the instance variables to create new state; an object's methods can also create new objects.

An object's instance variables (data) are packaged, or encapsulated, within an object. Instance variables are manipulated by an object's methods . With certain well-defined exceptions, the object's methods are the only means by which other objects can access or alter its instance variables.

2. Classes

A class is a software construct that defines the instance variables and methods of an object. A class in and of itself is not an object. A class is a pattern that defines how an object will look and behave when the object is created or instantiated from the specification declared by the class. You can instantiate many objects from one class definition, just as you can construct many houses that area all the same from a single architect's drawing.

B. Java is Designed to be Robust and Secure

Java is intended for developing software that must be robust, highly reliable, and secure, in a variety of ways. There's strong emphasis on early checking for possible problems, as well as later dynamic (run-time) checking, to eliminate error-prone situations.

The Java compiler employs extensive and strong compile-time type checking so that more errors can be detected early, before a program is deployed. Java requires explicit declarations and does not support C-style implicit declarations.

Many of the stringent compile-time checks at the Java compiler level are carried over to the run time, both to check consistency at run time, and to provide greater flexibility. The linker understands the type system and repeats many of the type checks done by the compiler, to guard against version mismatch problems.

The single biggest difference between Java and C or C++ is that Java's memory model eliminates the possibility of overwriting memory and corrupting data. Instead of pointer arithmetic, Java has true, first-class arrays and strings, which means that the interpreter can check array and string indexes.

One of the Java compiler's primary lines of defense is its memory allocation and reference model. First of all, memory layout decisions are not made by the Java language compiler, as they are in C and C++. Rather, memory layout is deferred to run time, and will potentially differ depending on the characteristics of the hardware and software platforms on which the Java system executes.

Secondly, Java does not have pointers in the traditional C and C++ sense of memory cells that contain the addresses of other memory cells..The Java compiled code references memory via symbolic "handles" that are resolved to real memory addresses at run time by the Java interpreter.

In addition, Java removes the memory management load from the programmer. Automatic garbage collection is an integral part of Java and its run-time system. While Java has a new operator to allocate memory for objects, there is no explicit free function. Once you have allocated an object, the run-time system keeps track of the object's status and automatically reclaims memory when objects are no longer in use, freeing memory for future use.

Java applets have even stronger security constraints. The Java "sandbox" (the runtime interpreter in a browser) examines the applet for any untoward instructions as the applet is being loaded. For example, the applet cannot access files on a client’s disk, protecting the client’s data and preventing any viruses from being written.

Contrast Java with the Microsoft ActiveX approach to security. Though originally developed for Windows, ActiveXcomponents can be run by a browser that supports ActiveX (of course Iexplorer does so and Netscape can do so with a plug-in). You can create ActiveX components in various languages, such as C++, Visual Basic or Delphi. But the only provision for security is that a browser can check for a "digital signature", whereby code is verified to show who the author is. The idea is that a virus works because its creator can be anonymous, so if you remove anonymity, code suppliers will be forced to be resposible for their actions. Though this scheme may cut down on some malicious mischief, it can still only as trustworthy as the author’s accountability. It doesn’t rule out mischief, violations of privacy, much less plain old bugs that could interfere with the proper behavior of your browser or operating system.

C. Java is Designed to be Architecturally Neutral and Portable

The solution that the Java system adopts to solve the platform-specific binary-code distribution problem is a bytecode format that is independent of hardware architectures, operating system interfaces, and window systems

The Java compiler doesn't generate "machine code" in the sense of native hardware instructions--rather, it generates bytecodes for a high-level, machine-independent virtual machine that is implemented by Java interpreter and run-time system.

C and C++ both suffer from the defect of designating many fundamental data types as "implementation dependent." Java eliminates this issue by defining standard behavior that will apply to the data types across all platforms.

D. Java is Interpreted and Dynamic

The Java language's portable and interpreted nature produces a highly dynamic and dynamically-extensible system. The Java language was designed to adapt to evolving environments. Classes are linked in as required and can be downloaded from across networks. Incoming code is verified before being passed to the interpreter for execution.

  • The interpreted environment enables fast prototyping without waiting for the traditional compile and link cycle,
  • The environment is dynamically extensible, whereby classes are loaded on the fly as required,
  • The fragile superclass problem that plagues C++ developers is eliminated because of deferral of memory layout decisions to run time.

E. Java is Designed to Support Multithreaded Applications

1. Threads at the Java Language Level

Threads are an essential keystone of Java. The Java library provides a Thread class that supports a rich collection of methods to start a thread, run a thread, stop a thread, and check on a thread's status. You do’t need to worrty about whether there are many processor or just one; the same model works.

Java's threads are pre-emptive , and depending on platform on which the Java interpreter executes, threads can also be time-sliced .

2. Integrated Thread Synchronization

Java supports multithreading at the language (syntactic) level and via support from its run-time system and thread objects. At the language level, methods within a class that are declared synchronized do not run concurrently. Such methods run under control of monitors to ensure that variables remain in a consistent state. Every class and instantiated object has its own monitor that comes into play if required. This model is quite useful when accessing shared resources, such as a printer, or a server-side file that several clients may need to modify.
 

III. Writing Java Programs

A. Getting a Java compiler and a Java editor

Sun has made the Java Development Kit (JDK), which includes a Java bytecode translator (javac.exe), run-time interpreter (java.exe), libraries and associated documentation and other tools available for free via the web at http://java.sun.com/j2se/. As of spring 2005, there are two current version of the Java 2 platform, Standard Edition (J2SE) on this site: J2SE 1.4.2, an incremental improvement of previous releases, and J2SE 5.0 (or 1.5), which contains many significant new features (which we'll discuss). J2SE 1.4.2 is available on campus. Install. Version 1.1 and may greater require more recent recent versions of browsers (e.g., Netscape 4.07 or greater, and Microsoft support is dicey). (Note: recents releases require at least 20MB before installation!)

Many vendors, including Sun (originally HotJava, later Forte, lately Sun Java Studio), Symantec (Cafe), Borland (Jbuilder)and Microsoft (J++), sell integrated programming environments for Java. Student pricing is available for many of these products. Lehigh still has a campus license for Borland JBuilder. But nothing is cheaper than free, for which you can get a relatively simple programming environment suitable for our purposes. Eclipse (www.eclipse.org) is an important open-source project for development in Java, with plug-ins for other languages and UML, and is available via campus Install.  Sally H. Moritz has created a version of Eclipse with two useful plug-ins--Omondo's UML editor and the DrJava interpreter, available at www.lehigh.edu/~sgh2, or on campus LANs, enter eclips. Javaedit is an older, much simpler and smaller program--it lacks the syntax coloring, compile-time error messages, debugger, etc., of eclipse. You can find it is at http://ww.cse.lehigh.edu/~glennb/oose/javaedit.zip; the README.TXT file to include some additional documentation that should help you get it set up and going; I've also included some sample programs to get you started. Be sure to follow the installation instructions in the README.TXT file; and I’ve modified the source code to make it a bit easier to use.   JavaEdit is also available on campus: just run “javaedit” (a file name is an optional parameter).

B. Applications v. Applets

Java programs come in two flavors:
(1) standalone applications (which can run in either a command line or windowing environment; and
(2) web browser applets (which run inside of a web browser).
Java applications and applets differ slightly in their structure (and significantly in their implementation). The main structural difference is that applications have a main() method comparable to main() in C++. (An interesting difference is that a C++ linker will not tolerate finding more than one main(), but Java permits a main() in the scope of each class. It is even possible to invoke any class’s main() from the command line—a useful feature for testing individual Java classes.)
Applets, on the other hand, have no main() function but incorporate other features required to start and run the applet in a web browser. In addition, as part of Java's security model, applets cannot make changes to the machine on which they are running (e.g., writing to disk). As a practical matter, this limits the functionality of applets to small interactive programs.

C. Hello World Examples: Stand-Alone and Applet

1. Application

   //Filename: HelloWorld.java
   class HelloWorld
   {
     public static void main(String[] args)
     {
       System.out.println("Hello World!");
     }//main()
   }//class HelloWorld

2. Applet

   //Filename: HelloWorldApplet.java
   import java.applet.Applet;
   import java.awt.Graphics;
 
   public class HelloWorldApplet extends Applet
   {
     public void paint(Graphics g)
     {
       g.drawString("Hello World!", 50, 25);
     }//paint()
   }//class HelloWorldApplet

2.1 HTML code to invoke a Java Applet

   //Filename: HelloWorldApplet.html
   <titleHello World Applet</title
   </head
   <body
   <hr
 
   <applet code="HelloWorldApplet.class" align="baseline" 
           width="200" height="80"</applet
   <hr
   </body
   </html

Note: The only thing a Java applet really has to do is define a paint() method. The paint() method is called automatically when the applet is displayed or resized. The argument to paint() is a Graphics object. Graphics objects store the current color, font and other information used when performing graphics operations.

D. More complex examples

Automated Teller Machine example  (in www.cse.lehigh.edu/~glennb/oose/java/atmjava.zip)

Illustrates classes, standard input/output, exceptions, inheritance, Strings, Vectors, and dynamic binding, in a partial simulation of common machine.

Ticker Applet Example  (www.cse.lehigh.edu/~glennb/oose/java/ticker.java)

Illustrates Strings, StringTokenizer, Applets and graphics, threads and interfaces, in a marquee which scrolls across a browser's screen.
 

IV. Main Features of the Java Language

Java follows C++ syntax to some degree, so C++ programmers are off to a good start.

A. Program Structure

The source code for an Java program consists of one or more compilation units. Each compilation unit is a file with a ".java" suffix. Each compilation unit can contain only the following (in addition to white space and comments):

  • a package statement
  • import statements
  • class declarations
  • interface declarations

Although each Java compilation unit can contain multiple classes or interfaces, at most one class or interface per compilation unit can be public.

When Java source code is compiled, the result is Java bytecode, stored in files with extension ".class". Java bytecode consists of machine-independent instructions that can be interpreted efficiently by the Java runtime system. The Java runtime system operates like a virtual machine.

B. Lexical Issues

1. Comments

The Java language has three styles of comments:

  // text

All characters from // to the end of the line are ignored.

  /* text */

All characters from /* to */ are ignored.

  /** text */

These comments are treated specially when they occur immediately before any declaration. They should not appear any other place in the code. These comments indicate that the enclosed text should be included in automatically generated documentation for the declared item. The javadoc program, which comes with the JDK, extracts these special comments, as well as the class or method name that adjoins each such comment. The output of javadoc is an HTML file. The documentation for the Java API (http://java.sun.com/j2se/1.4.2/docs/api/index.html) is generated using javadoc.  You can insert embedded class documentation tags for javadoc. @see tags are hyperlinked to other documentation, for example, @see classname links to the documentation for classname. Some other tags include @author, @version, @param, @return, @exception, and @deprecated.

2. Identifiers

Identifiers must start with a letter, underscore ("_"), or dollar sign ("$"); subsequent characters can also contain digits (0-9). Rather than 8-bit ASCII, Java uses the 16-bit Unicode character set.

3. Keywords

The following identifiers are reserved for use as keywords. They cannot be used in any other way.

abstract boolean break byte byvalue case catch char class const continue default do double else extends false final finally float for goto null  if implements import instanceof int interface long native new package private protected public return short static super switch

Those in italics are reserved, though currently unused.

4. Literals

Integers literals

Integers can be expressed in decimal (base 10), hexadecimal (base 16), or octal (base 8) format. Integer literals are of type int unless they are larger than 32-bits, in which case they are of type long.   (While in C/C++, the size of an int is platform-specific, in Java it is platform-independent.)

Floating point literals

A floating point literal can have the following parts: a decimal integer, a decimal point ("."), a fraction (another decimal number), an exponent, and a type suffix.

Boolean literals

The boolean type has two literal values: true and false.

Character literals

A character literal is a character (or group of characters representing a single character) enclosed in single quotes.     Characters have type char and are drawn from the international Unicode character set, each represented by two bytes (unlike the ASCII character set of C/C++, each represented by one byte).

String literals

A string literal is zero or more characters enclosed in double quotes. Each string literal is implemented as a String object (not as an array of characters). For example, "abc" creates an new instance of class String.

C. Types

Every variable and every expression has a type. Variables are declared with types as in C++. Type determines the allowable range of values a variable can hold, allowable operations on those values, and the meanings of the operations. Built-in types are provided by the Java language. Programmers can compose new types using the class and interface mechanisms.

The Java language has two kinds of types: simple and composite. Simple types are those that cannot be broken down; they are atomic. The integer, floating point, boolean, and character types are all simple types. Composite types are built on simple types. The language has three kinds of composite types: arrays, classes, and interfaces.

1. Numeric types

Integers are similar to those in C and C++. Here are some declaration of integer variables of different sizes:

   int i,k; float x; double y; long z; byte b;

Unlike C/C++, in Java all integer types are machine independent, and some of the traditional definitions have been changed to reflect changes in the world since C was introduced. The four integer types have widths of 8 (byte), 16 short), 32 (int), and 64 (long) bits and are signed. Java does not have a sizeof operator as in C++; it is not needed.

2. Floating point types

The float keyword denotes single precision (32 bit); double denotes double precision (64 bit). The result of a binary operator on two float operands is a float. If either operand is a double, the result is a double.

3. Character types

Java uses the 16-bit Unicode character set for characters throughout, whereas C/C++ encodes characters using the 8-bit ASCII standard.

4. Boolean types

The boolean type is used for variables that can be either true or false, and for methods that return true and false values. It's also the type that is returned by the relational operators (e.g., "", "==").

Boolean values are not numbers and cannot be converted into numbers by casting. Hence:

   if (evenAmount % 20)

is meaningful in C/C++, since a numeric value can also serve as a boolean value, where zero is false and non-zero is true. But it's not meaningful in Java, which insists that numerics and booleans are two different types, so you should rewrite the above as something like:

   if ((evenAmount % 20) ==  0)

5. Arrays

Arrays in Java are first-class objects, rather than pointers to blocks of memory as in C/C++. Arrays are created using the new operator:

   char s[] = new char[30];

Note that there is no static allocation of arrays as in C/C++; like other objects in Java, arrays are created on the heap at run-time. Every array must be explicitly allocated--use new every time.

The first element of an array is at index 0 (zero). Specifying bounds dimensions in a declaration is not allowed. Like C/C++, Java does not directly support multi-dimensional arrays. Instead, programmers can create arrays of arrays:

   int i[][] = new int[3][4];

At least one dimension must be specified but other dimensions can be explicitly allocated by a program at a later time. For example:

   int i[][] = new int[3][];

is a legal declaration.

Subscripts are checked to make sure they're valid:

   int a[] = new int[10];
   a[5] = 1;
 
   a[1] = a[0] + a[2];
 
   a[-1] = 4; //Throws an ArrayIndexOutOfBoundsException at runtime
 
   a[10] = 2; //Throws an ArrayIndexOutOfBoundsException at runtime

Array dimensions must be integer expressions:

   int n;
   //...
   float arr[] = new float[n + 1];

The length of any array is available via the attribute length and indicates how many elements can be placed in the array:

   int a[][] = new int[10][3]; 
   System.out.println(a.length);    //prints 10 (regardless of how many are non-null)

Note that in addition to built-in arrays, Java has a rich set of collection classes, including Vector, LinkedList, ArrayList, Set, HashSet, as well as String and StringBuffer classes to manipulate strings (using String to access and StringBuffer to modify individual characters).  See the API documentation for more details, starting with Collection, and of course consult a good textbook such as Deitel.


D. Statements

1. Declarations

Declarations can appear anywhere that a statement is allowed. The scope of the declaration ends at the end of the enclosing block.

In addition, declarations are allowed at the head of for statements, as shown below:

   for (int i = 0; i <; 10; i++)
   { //...
   }//for

Items declared in this way are valid only within the scope of the for statement.

2. Expressions

Expressions are statements:

   a = 3;
   print(23); 
   foo.bar();


3. Control Flow

The following is a summary of control flow:

   if (boolean-expression) statement; //not int (as in C), but boolean-expression 
   else statement
 
   switch (expr1) //expr1, expr2, etc., evaluate to enumerable types
   { case expr2: statements
     default: statements
   }
 
   for ([initialize-expr]; [exit-expr]; [increment-expr]) statement
  
   while (boolean-expression) statement;
 
   do statement while (boolean-expression);
 
   label:              //label must appear before an iteration statement
 
   break [label];      //jump to bottom of loop or out of labeled outer loop
 
   continue [label];   //jump to top of loop or labeled outer loop and continue
 
   return expression;  //exits function with value of expression

Unlike C/C++, there  is no goto statement at all (let alone a setjmp!).  On the other hand, the language supports labeled loops, e.g.,

   MyLabel: //statement following label must be a loop statement
   for ( ; true ; ) //an infinite loop, which gets broken below
   { while ( true )
     { if (i == 1) continue; 
       if (i == 2) break;
       if (i == 3) continue MyLabel;
       if (i == 4) break MyLabel;
   }

that is, in addition to implicitly jumping out of a loop (break) or up to the top of a loop (continue), one may jump to a label preceding an outer loop in a nested loop. The first, unlabeled continue jumps to the top of the inner while loop; The second, unlabeled break jumps out of the inner while loop; the third, labeled continue jumps to the top of outer for loop and re-enters it; and the fourth, labeled break jumps all the way out of the outer for loop. Since Java’s labels are constrained in their placement (immediately preceding a loop statement), they don’t obscure the static structure of programs. (Actually, goto is a reserved word in Java, apparently just in case the designers of the language should ever change their minds!)

When constructing boolean expressions in Java, bear in mind that objects are handles rather than expanded structures. E.g.,

   ( double x=3, y=sqrt(9); String s1="Sam",s2="Sam";
     boolean b1 = (x == y); boolean b2 = (s1 == s2);
   } 

The value of b1 is true, since the value of the two doubles are the same, but the value of b2 is false, since the two String objects are different handles in memory. If you want to compare the values of two Strings (or other comparable objects), use the equals method:

   ( String s1="Sam",s2="Sam";
     boolean b2 = (s1 == s2); boolean b3 = s1.equals(s2);
   }

  While b2 is false, as explained above, b3 is true, since the value of the two Strings are equal.


E. Classes

Classes represent the classical object-oriented programming model. They support data abstraction and implementations tied to data. In Java, each new class creates a new type.

A class can inherit instance variables and methods from another class, using the keyword extends:

   class Deposit extends Transaction

In this example, Deposit is called the subclass and Transaction is the superclass. Each instance of class Deposit physically includes all the instance variables of class Transaction as well as any new instance variables introduced by Deposit itself. Clients of Deposit can also invoke any of Transaction's public methods. To make a new class, the programmer must base it on an existing class. The new class is said to inherit from (or in C++ parlance, "derives from" or in Java jargon, "extends") the existing class. The "extends" keyword makes explicit the idea that inheritance promotes extendibility of software—the "open-closed" principle of Bertrand Meyer. A superclass can be both closed to change yet open to subclass extensions.

For example:

   /** 2 dimensional point */
   public class Point
   { float x, y;
     //...
   }//class Point
 
   /** Printable point */
   class PrintablePoint extends Points implements Printable
   { //...
     public void print()
     { //...
     }//print
   }//class PrintablePoint

The subclass PrintablePoint adds a new feature not found in its superclass. Class inheritance is transitive: if B is a subclass of A, and C is a subclass of B, then C is a subclass of A.

All classes are derived from a single root class: Object. Every class except Object has exactly one immediate superclass. If a class is declared without specifying an immediate superclass, Object is assumed. For example, the following:

   class Point 
   { float x, y;
     //...
   }//class Point

is the same as:

   class Point extends Object
   { float x, y;
     //...
   }//class Point

Java supports only single inheritance; i.e., a class extends at most one class. On the other hand, a Java class implements any number of interfaces.  We’ll learn more about interfaces and how they support a limited form of multiple inheritance in a subsequent section.

1. Instance Variables

Instance variables are to Java (as well as Smalltalk and Objective-C) what data members are to C++ and attributes to Eiffel: data defined in a scope of a class (except for static variables). Since every object is an instance of some class, every object maintains its own copy of instance variables as its state. (Variables declared inside the scope of a method are considered local variables.) Instance variables can have modifiers (see Modifiers).

Instance variables can be of any type and can have initializers. An example of an initializer for an instance variable named j is:

   class A
   { int j = 23;
     //...
   }//class A

If an instance variable of any numeric type does not have an initializer, it is initialized to zero; boolean variables are initialized to false; and references to objects are initialized to null.

2. Methods

Methods are the operations or functions that an object of some class type can perform. They can be declared in either classes or interfaces, but they can be implemented only in classes. (All user-defined operations in the language are implemented with methods.)

Variables declared in methods (local variables) can't hide other local variables or parameters in the same method. For example, if a method is implemented with a parameter named i, it's a compile-time error for the method to declare a local variable named i. In the following example:

   class Rectangle
   {
     void vertex(int i, int j) //vertex is a method of Rectangle
     {
       for (int i = 0; i <= 100; i++) { //ERROR
       //...
       }//for
     }//vertex()
   }//class Rectangle

the declaration of "i" in the for loop of the method body of "vertex" is a compile-time error.

Methods are rigorously checked to be sure that all local variables (variables declared inside a method) are set before they are referenced. Using a local variable before it is initialized is a compile-time error.


3. Constructors

Constructors are special methods provided for initialization of objects. As in C++, they have the same name as their class and do not have any return type. Constructors are automatically called upon the creation of an object. They cannot be called explicitly through an object.

Like methods, constructors can be overloaded by varying the number and types of parameters. The compiler requires that each constructor (or method) be distinguishable by a unique list of argument types.

If a class declares no constructors, the compiler automatically generates one, which initializes all of an object's instance variables to the default values, based on their types.

A subclass constructor always invokes its base class constructor (which in turn invokes its base class constructor), before running its own code. For example:

   class Drawing {
     Drawing() { System.out.println("Drawing constructor"); }
   }
   class Cartoon extends Drawing {
     Cartoon() { System.out.println("Cartoon constructor"); }
     public static void main(String[] args) { Cartoon d = new Cartoon(); }
   }

The output for this program shows the order of constructor calls:

   Drawing constructor
   Cartoon constructor

Construction proceeds from the base class (in this case, Drawing) down to the derived class. If you don’t create a constructor for a subclass, the compiler will synthesize a default constructor for you that calls the base class constructor.

The above example deals with default constructors; that is, they don’t have any arguments. If a subclass doesn’t have a default constructor or if you want to call superclass constructor that has an argument, then you must explicitly call a superclass constructor using the super keyword. For example:

   class Game {
     Game(int i) { System.out.println("Game constructor"); }
   }
   class BoardGame extends Game {
     BoardGame(int i) 
     { super(i); //call superclass constructor with the right argument list
       System.out.println("BoardGame constructor"); 
     }
     public static void main(String[] args) { BoardGame b = new BoardGame(); }
   }

If we hadn’t called the superclass constructor, the compiler would complain that it couldn’t find a constructor with the form Game(), since the constructor we supplied prevents the compiler from generating a default constructor.

4. Object Creation - The new Operator

A class is a pattern used to define the states and behaviors of an object. An object is an instance of a class. All instances of classes are allocated in a garbage collected heap. Declaring a reference to an object does not allocate any storage for that object. The programmer must explicitly allocate the storage for objects, but no explicit deallocation is required; the garbage collector automatically reclaims the memory when it is no longer needed.

To allocate storage for an object, use the new operator. In addition to allocating storage, new initializes the instance variables and then calls the instance's constructor. The constructor is a method that initializes an object. The following syntax allocates and initializes a new instance of a class named ClassA:

   a = new ClassA();

5. Order of Declarations

The order of declaration of classes and the methods and instance variables within them is irrelevant. Methods are free to make forward references to other methods and instance variables.

6. Access Specifiers

Access specifiers are modifiers that allow programmers to control access to methods and variables. The keywords used to control access are public, private, and protected. For example, method toString() described above was marked as public. Methods marked as public can be accessed from anywhere by anyone. Methods marked as private can be accessed only from within the class in which they are declared. Since private methods are not visible outside the class, they are effectively final and cannot be overridden. The protected access specifier makes a variable or method accessible to subclasses, but not to any other classes.

7. Static Methods, Variables, and Initializers

Variables and methods declared in a class can be declared static, which makes them apply to the class itself, rather than to an instance of the class. (Similarly, Smalltalk and Objective-C have class variables and methods, which belong to a class as a whole.) In addition, a block of code within a class definition can be declared static. Such a block of code is called a static initializer. E.g.:

   private static Checking checking = new Checking(2468,200);

A static variable exists only once per class, no matter how many instances of the class exist. Both static variables and static methods are accessed using the class name. For convenience, they can also be accessed using an instance of the class. A static function can be called directly through its class. For example, suppose the following appears in class foo:

   static void bar() { System.out.println("Hello, World!"); }

You may then invoke foo() invoke class bar (rather than instance of this class), e.g.,

   foo.bar();

By the way, a main() method is declared as a static, so that the Java interpreter can start execution by invoking a main() without first having to create an instance of a class.

8. Final Classes, Methods and Variables

The final keyword is a modifier that generally means "This cannot be changed." Constraining change facilitates maintenance by letting programmers focus on what can change. The final keyword marks a variable as having a constant value, an argument handle as immutable within a method, a method as never being overridden, and class as never having subclasses. It is a compile-time error to attempt to change the value of a final variable, assign a value to a final argument, override a final method, or subclass a final class.

Variables marked as final behave like constants. For example:

  static final double startingCheckingBalance=200;

The combination of final and static marks this variable as being constant in value and shared by all instances of the class in which it is declared.

Arguments marked as final behave like const parameters in C++. For example:

  void foo(final int i) { i=7; }  //illegal assignment, i is final

The final keyword lets the compiler perform a variety of optimizations. One such optimization is inline expansion of method bodies, which may be done for small, final methods (where the meaning of small is implementation dependent). By the way, any private methods are implicitly final; they cannot be overriden in subclasses, since they cannot even be accessed. Since final classes rule out inheritance, they can be compiled to avoid the overhead of dynamic binding. The downside of making methods or classes final is that it rules out extendibility.

9. Polymorphism through method overloading and overriding

Polymorphism--letting the same method mean different things depending on the type of its parameters or objects--is a major feature of object-oriented languages. Java supports two kinds of polymorphism: method overloading and overriding. Unlike C++, Java does not support operator overloading, but it does support method overloading. Overloading means declaring a method with the same name as another method, but distinguished by different parameter types. The Java compiler can distinguish different uses of the same name based on the different parameter types. (Note that methods cannot be distinguished by return type, since it is possible to invoke a method without usings its return type, e.g.:

foo();

The compiler cannot tell if this is a void foo() if one that returns an value of some type that isn’t being used here.)

In the context of a class hierarchy via inheritance, a subclass can override a method defined in a superclass. For example, the Object class, implicitly the root of all other classes, provides many methods, including toString, which produces a String representing information about a particular object. It may be useful to override this method in a subclass, as in class Deposit:

   public String toString()
   { return "Deposit to " + accountType + " of $" + amount 
                  + " giving balance of $" + balance; 
   }

Instead of the default behavior, an instance of class Deposit will invoke this behavior. An interesting situation happens if we store objects of different types in a collection. Then if we attempt to invoke the toString() method on each object in the collection, the meaning of this method will vary, at run-time, depending on the type of each element in the collection. E.g.:

    Enumeration actions = currentTransactions.elements();
    while (actions.hasMoreElements())
    {
       Transaction trans = (Transaction)actions.nextElement();        
       System.out.println(trans.toString());
    }

This example begins by creating an Enumeration from a Vector called currentTransactions, which holds different subclasses of class Transaction, each of which override toString() in a different way. The while loop then prints out the result of each Transaction's toString() method.

This ability to vary the behavior of a method, depending on its type at run-time, is called run-time polymorphism, and depends on a facility of the Java run-time system called dynamic binding. The advantage of dynamic binding is that it avoids the need for lots of explicit if-else or switch statements testing for the type of the object. Dynamic binding makes it much easier to extend code without breaking existing code. For example, we could add a new subclass of Transaction, such as TransferFunds, to the system without affecting the existing code for Transaction or any of its other subclasses. Just override toString() again in the new subclass. Thus, the combination of inheritance and dynamic binding supports what Bertrand Meyer calls the "open-closed" principle: existing code is open to extension (via inheritance) yet closed to modification itself.

10. Abstract Classes and Methods

Abstract methods provide the means for a superclass or interface to define a protocol that subclasses must implement. For example:

   public abstract int action();

Marking method action() is marked as abstract tells programmers (and the Java compiler) that any subclasses of this class must override and implement this method. Then dynamic binding can kick in. Note that an abstract method does not have a method body; instead the declaration is terminated with a semi-colon.

A class containing any abstract methods is an abstract class, and should be marked as such:

   public abstract class property 
   { //... 
     public abstract int action();
   }

The following rules apply to the use of the abstract keyword:

  • Constructors cannot be marked as abstract.
  • Static methods cannot be abstract.
  • Private methods cannot be abstract.
  • Abstract methods must be defined in some subclass of the class in which they are declared.
  • A method that overrides a superclass method cannot be abstract.
  • Classes that contains abstract methods and classes that inherit abstract methods without overriding them are considered abstract classes.
  • It is a compile-time error to instantiate an abstract class or attempt to call an abstract method directly.

11. Inner classes

Java 1.1 makes in possible to embed a class definition within another class definition. The inner class lets you group classes that logically belong together and control the visibility of one within the other. One good use of inner classes is to hide the implementation of an abstract class or an interface (see below). For example:

   public class Parcel
   { private class Pcontents extends Contents { //inner class implements abstract class
         private int i= 11; 
         public int value() { return i; }
     }
   }

Because Pcontents is nested within Parcel, it is inaccessible outside Parcel, thus its implementation details are completely hidden to outsiders. A Java compiler could also exploit this information hiding to generate more efficient code.

Furthermore, inner classes can be anonymous. For example:

  public class Parcel2
  { public Contents cont() { //a method that returns Contents…
        return new Contents() { //define an anonymous class
           private int i=11;   //instance variable of anonymous class
           public int value() { return i; } //method of anonymous class
           };   //semicolon required to close anonymous class
        } //cont() returns an object whose type is an anonymous class

The cont() method combines the creation of a return value with the definition of the anonymous class that represents the return value. The new expression produces a handle to an object from the anonymous class, which is immediately "upcast" to a Contents handle.

A Java compiler produces a distinct .class file for each different class in the source code, including inner classes and anonymous inner classes. The names of these classes have a formula: the name of the enclosing class, followed by ‘$’, followed by the name of the inner class. If the inner class is anonymous, the compiler simply generating numbers as inner class identifiers.

JDK 1.1 makes extensive use of inner classes in its re-design of the Abstract Windows Toolkit (AWT).

12. Finalization

Java includes the concept of object finalization. Like C++ destructors, a finalize() method runs when an object goes out of scope (at the end of the block in which it is declared), usually in order to free resources. Unlike C++, however, Java relies on automatic garbage collection to retrieve inaccessible memory. So finalization does not serve the same major purpose as destructors. Java finalization is generalization of garbage collection that allows a program to free arbitrary resources (e.g., file descriptors or graphics contexts) owned by objects that cannot be accessed by any Java program. Reclaiming an object's memory by garbage collection does not guarantee that these resources will be reclaimed. Here's an example that closes files (opened by other methods of this class):

   protected void finalize()    //"destructor" closes files
   { try
     { input.close();
       out.close();
     }
     catch(IOException e)
     { System.err.println(e);
       return;
     }
   }

(The try...catch block will be explained in the section on exception-handling below.)


F. Interfaces

An interface specifies a collection of methods without implementing their bodies. Interfaces provide encapsulation of method protocols without restricting the implementation to one inheritance tree. When a class implements an interface, it generally must implement the bodies of all the methods described in the interface. (If the implementing class is itself abstract, it can leave the implementation of some or all of the interface methods to its subclasses.)

Interfaces solve some of the same problems that multiple inheritance does without as much overhead at runtime. A class may implement any number of interfaces. For example, class Cat extends Mammal implements Pet implements Mouser; Cat inherits any methods of Mammal that it doesn’t override, but must implement the methods of interfaces Pet and Mouser. The interfaces tell us what Cat has in common with Dog and Weasel. In a sense, an interface is a "more pure" abstract class, since all of its methods are abstract, i.e., must be implemented by the derived class.

Using interfaces allows several classes to share a programming interface without having to be fully aware of each other's implementation. The following example shows an interface declaration (with the interface keyword) and a class that implements the interface:

   public interface Storing
   { void freezeDry(Stream s);
     void reconstitute(Stream s);
   }//class Storing
 
   public class Image implements Storing, Painting
   {
     //...
     void freezeDry(Stream s)
     { //JPEG compress image before storing
     }//freezeDry()
     void reconstitute(Stream s)
     {//JPEG decompress image before reading
     }//reconsititute()
   }//class Image

Like classes, interfaces are either private (the default) or public. The scope of public and private interfaces is the same as that of public and private classes, respectively. Methods in an interface are always public. Variables are public, static, and final.
 

G. Packages

Packages provide a mechanism to manage "name spaces." A flaw of many programming languages is that management of names because increasingly difficult as the number of names increase. The names of all your class members are insulated from each other. A method f() inside class A will not clash with an f() hat has the same signature (argument list) in class B. But what about class names? What if you implement your own stack class, but someone else has installed a stack class on your machine? (It might even have been downloaded via the Internet.) Packages allow you to divide up the global namespace so you won’t have clashing names.

A package statement must be the first non-comment, non-white space line in the compilation unit. It has the following format:

   package packageName;


The packageName refers to the class files in a directory reachable via the CLASSPATH. When a compilation unit has no package statement, the unit is placed in an anonymous default package, in the current directory. The language provides a mechanism for making the definitions and implementations of classes and interfaces available across packages. The import keyword is used to mark classes as being imported into the current package. A compilation unit automatically imports every class and interface in its own package.

Code in one package can specify classes or interfaces from another package in one of two ways:

  • By prefacing each reference to the class or interface name with the name of its package:
    
     //Prefacing with a package
     acme.project.FooBar obj = new acme.project.FooBar();
  • By importing the class or interface or the package that contains it, using an import statement. Importing a class or interface makes the name of the class or interface available in the current namespace. Importing a package makes the names of all of its public classes and interfaces available. The construct:
   
    //import all classes from acme.project 
    import acme.project.*;

     means that every public class from acme.project is imported.

It is illegal to specify an ambiguous class name and doing so always generates a compile-time error. Class names may be disambiguated through the use of a fully qualified class name, i.e., one that includes the name of the class's package.

All the classes and packages referenced in a program must be accessible via the CLASSPATH in the system environment. All names in a single package are accessible to code in the same package (i.e., access within a package is "friendly".)


H.
Exceptions

When an error occurs in a Java program--for example, when an argument has an invalid value--the code that detects the error can throw an exception. By default, exceptions result in the thread terminating after printing an error message. However, programs can have exception handlers that catch the exception and recover from the error.

Java, unlike C++, is quite strict about catching exceptions. If a method can trigger an exception, it must either catch it or explicitly re-throw it with an exception specification. By enforcing exception specifications from top to bottom, Java guarantees exception correctness at compile time. Here’s a method that ducks out of catching an exception by explicitly re-throwing it:

   void f() throws tooBig, tooSmall, divZero { //…

The caller of this method now must either catch these exceptions or rethrow them in its specification.

Some exceptions are thrown by the Java runtime system. However, any class can define its own exceptions and cause them to occur using throw statements. A throw statement consists of the throw keyword followed by an object. By convention, the object should be an instance of Exception or one of its subclasses. The throw statement causes execution to switch to the appropriate exception handler. When a throw statement is executed, any code following it is not executed, and no value is returned by its enclosing method. The following example shows how to create a subclass of Exception and throw an exception.

   class MyException extends Exception {
   }//class MyException
 
   class MyClass
   {
     void oops()
     {
       if (/* no error occurred */)
       { //...
       }
       else { /* error occurred */
         throw new MyException();
       }
     }//oops
   }//class MyClass

To define an exception handler, the program must first surround the code that can cause the exception with a try statement. After the try statement comes one or more catch statements--one per exception class that the program can handle at that point. In each catch statement is exception handling code. For example:

   try
   { p.a = 10;
   }
   catch (NullPointerException e)
   { println("p was null"); }
   catch (Exception e)
   { println("other error occurred"); }
   catch (Object obj)
   { println("Who threw that object?"); }

A catch statement is like a method definition with exactly one parameter and no return type. The parameter can be either a class or an interface. When an exception occurs, the nested try/catch statements are searched for a parameter that matches the exception class. The parameter is said to match the exception if it:

  • is the same class as the exception; or
  • is a superclass of the exception; or
  • if the parameter is an interface, the exception class implements the interface.

The first try/catch statement that has a parameter that matches the exception has its catch statement executed. After the catch statement executes, execution resumes after the try/catch statement. It is not possible for an exception handler to resume execution at the point that the exception occurred. For example, this code fragment:

   print("now");
   try
   { print("is "); 
     throw new MyException();
     print("a ");
   }
   catch(MyException e)
   { print("the ");
   }
   print("time\n");

prints "now is the time". As this example shows, exceptions don't have to be used only for error handling, but any other use is likely to result in code that's hard to understand.

Exception handlers can be nested, allowing exception handling to happen in more than one place. Nested exception handling is often used when the first handler can't recover completely from the error, yet needs to execute some cleanup code (as shown in the following code example). To pass exception handling up to the next higher handler, use the throw keyword using the same object that was caught. Note that the method that rethrows the exception stops executing after the throw statement; it never returns.

N.B. - Exception handling is required for almost all read operations, and for numerous other system provided class methods. If you use one of Java's built in class methods and it throws an exception, you must catch it (i.e., surround it in a try/catch block or you will get a compile time error. JDK 1.5 provides a Scanner class that lets you avoid exception handling for simple input operations.

I. The Java Base System

The complete Java system includes a number of libraries of utility classes and methods of use to developers in creating applications. Very briefly, these libraries are:

java.lang --the collection of base types (language types) that are always imported into any given compilation unit. This where you'll find the declarations of Object (the root of the class hierarchy) and Class , plus threads, exceptions, wrappers for the primitive data types, and a variety of other fundamental classes.

java.io --streams and random-access files. This is where you find the rough equivalent of the Standard I/O Library you're familiar with on most UNIX systems. A further library is called java.net , and provides support for sockets, telnet interfaces, and URLs.

java.util--container and utility classes. Here you'll find classes such as Dictionary, HashTable, and Stack , among others, plus encoder and decoder techniques, and Date and Time classes.

java.awt --an Abstract Windowing Toolkit that provides an abstract layer enabling you to port Java applications easily from one window system to another. This library contains classes for basic interface components such as events, colors, fonts, and controls such as buttons and scrollbars. JDK 1.1 made significant changes to the AWT and the Swing classes build on the new AWT design.

J. Java I/O

Input and output in Java follow the C and C++ model in which I/O support is provided by a library, not by the language itself In Java , the I/O library is a class library -- the java.io package.

Java uses I/O streams similar to C++. In Java, I/O streams are designed in a layered fashion. At the base, there are basic Input Stream and Output Stream classes -- with limited methods. Added facilities such as buffering, connecting to files, printing data types other than bytes and so on are provided by "wrapper" classes.

Examples

   InputStream in;         //declare an InputStream
   OutputStream out;       //declare an OutputStream
   DataInputStream dataIn; //declare a DataInputStream
   PrintStream printOut;   //declare a PrintStream
   in = new InputStream(); //create an InputStream
 
   out = new OutputStream(); //create OutputStream
   dataIn = new DataInputStream(in); //attach in to a DataInputStream
   printOut = new PrintStream(out);  //attach out to a PrintStream
 
   //***************************************************************
   InputStream in = null;
   in = new BufferedInputStream(new FileInputStream("input.txt"));
   //creates a buffered stream connected to a file called input.txt
 
Standard Streams  
   System.in (input stream)
   System.out (print stream)
   System.err (print stream)

* comparable to cin, cout and cerr in C++

Printing Text Output

The normal way of displaying output in Java is to use the print() and println() methods of the PrintStream class, e.g.:

   System.out.print("print and");
   System.out.println("println example");

would output:

   print and println example

Prompts

Note that a PrintStream object may not flush an output buffer (i.e., print the output) until a newline is sent. Therefore, to display a prompt you may need to make an explicit call to PrintStream.flush() to make sure the prompt is displayed, e.g.,

   //p is a Print Stream
   p.print ("Please enter an integer:")
   p.flush():
   //the prompt is not displayed until after p.flush() is executed

Reading Input

Each different InputStream provides its own read() methods. In fact, the stream that you choose to use for a given application will often be determined by the available read() methods. For example, the InputStream class has only three read methods:

  • read() - reads a byte
  • read(byte[]) - reads an array of bytes
  • read(byte[], int, int) - reads an array of bytes

The DataInputStream class includes additional read() methods such as: readInt(), readFloat(), readChar, readLine(), etc. You will need to consult the API reference for the details of the read() methods.

N.B. - Virtually all, if not all, of the read() methods throw exceptions (IOException). Accordingly, all read statements should be wrapped in a try/catch block, e.g.:

   char ch;
   //...
   try
   { 
     ch = (char) System.in.read();
   }
   catch(IOException e)
   {
     System.err.println(e);
     return;
   }

J. Collections

The most efficient way to hold a group of objects is an Array (described in section C.5 above), because Arrays are declared with information about their size and the type of their elements, which Java can exploit to optimize memory allocation and indexing. Indeed, if you want a collection of elements of a primitive type (such as int), then you must use an Array; the alternative is to "wrap" these elements in an object wrapper type, such as Integer. (Note, however, that wrapper types are much more memory intensive that the primitive types.)

On the other hand, there are times when you cannot easily predict the number of objects you’re going to need to collect, or you may want more sophisticated algorithms for accessing elements than just indexing. Enter the collection classes of JDK 1.1 or higher. These classes store and manipulate elements of type Object, the root of the Java class hierarchy. The good news is that this means that these classes can store handles to any object types (but not primitive types). The bad news is that you lose type information for elements. (You must resort to down-casting to refer to methods of your actual objects. Casting is dangerous in C++ because it breaks the type system; in Java, the run-time system checks to make sure your downcasting is legal and if it isn’t, triggers an exception.) See the API documentation for more details, starting with Collection, and of course consult a good textbook such as Deitel.