Chapter 7

Advanced Java Programming

by Bryan Morgan


CONTENTS

Being object oriented is not the only thing that makes Java a powerful language. The Java language also provides native support for a variety of advanced programming constructs that can be used to build virtually any type of application, including the following:

This chapter introduces and discusses all of these concepts in order to give you the broadest possible knowledge of Java before delving into such concepts as graphical user interface design, ActiveX/COM, scripting, database access, and multimedia. All these programming topics build on the concepts introduced in this chapter. For instance, many classes in the java package contain member methods that are capable of throwing exceptions. Likewise, to do animation or perform simultaneous threads of animations, the developer should possess a basic knowledge of multithreading in Java. The following section discusses exceptions and how they can be handled in Java.

Exception Handling

Exception handling refers to a capability in Java that allows you to plan ahead for potential problems and respond to these problems when they happen. Imagine a situation where you have built a screen asking the user for some type of input. Exceptions can be used to trap invalid user input and send, or throw, an exception. The Java programmer can trap, or catch, an exception by executing a set of statements within a handler. If any one of these statements causes, or throws, some type of problem, your code can catch the problem and handle it accordingly. The syntax provided by the Java language to perform this task is the following:


try

{

  //Execute one or more statements

}

catch (SomeException1 error1)

{

  //Handle the error in some way

}

catch (SomeExceptionN errorn)

{

  //Handle the error in some way

}

finally

{

  //Perform mandatory cleanup operations

}

Note the use of the try...catch...finally mechanism here. This is the basic format used to trap exceptions in code. The try...catch portion of this operation is required syntax in Java. The finally keyword is used to introduce a set of statements that will always be executed before the method executes. Because exceptions are simply classes derived from the java.lang.Throwable class, you are free to define your own set of exception classes complete with member variables and methods. The next section illustrates how exceptions can be used to build elegant applications in Java.

Handling Errors in Java

Java implements exception handling using the base class java.lang.Throwable. All classes that derive from this class are objects that can be thrown at runtime whenever an error is encountered. All thrown errors must be accompanied by a catch at some level in the program (in either the current class or the parent classes of the current class). If an exception is not caught, the program will crash.

The java.lang.Exception and java.lang.Error classes are derived directly from java.lang.Throwable. Classes derived from the Exception class define normal programming errors that can be caught in an application. Classes derived from the Error class define abnormal errors that occur in the Java system. Examples of java.lang.Error errors are the ThreadDeath, VirtualMachineError, LinkageError, and the ClassFormatError. Chances are good that you will probably never try to catch an Error-derived class, because they are designed to signal low-level errors that, as the Java API documentation says, should be handled "only if you know what you're doing." The Exception-derived classes represent much more common programming problems such as using a null pointer, dividing by zero, or indexing beyond the end of an array.

NOTE
A class derived from java.lang.Exception is generally referred to as an exception. A class derived from java.lang.Error is generally referred to as an error.

Examples of exceptions are ArithmeticException, ArrayIndexOutOfBoundsException, ClassNotFoundException, and NullPointerException. Java allows you to define new errors or exceptions and to try to catch existing exceptions and errors. When an error occurs within a method, that method can throw an exception. The Java runtime environment will attempt to determine which method in the call chain handles an exception that most closely matches the thrown exception. If one is found, its exception handler (the catch block) is called.

Catching Exceptions Using try...catch

To retrieve any error that may occur in a block of code, catch the Exception class exception, as in the following example:


try

{

  callSomeMethod();

}

catch (Exception e)

{

  System.out.println("An error occurred.");

}

System.out.println("Everything looks OK!");

In this block of code, the callSomeMethod() method is called. If the code throws an exception anywhere in that method (either through the use of the throw keyword or by an invalid operation), the error message is printed and the method is exited.

Performing Default Operations Using the finally Keyword

As mentioned in the "Exception Handling" section, you can use the finally keyword to execute a block of statements regardless of whether an error was caught. The method shown in Listing 7.1 uses a try...catch...finally block to open a database using a fictional Database object, perform some operation, and then make sure that the database connection is closed.


Listing 7.1. Catching exceptions using a try...catch...finally block.

void PerformQuery(String SQL, Database DB)

{

  String results;

  try

  {

    DB.Open();

    results = DB.PerformQuery(SQL);

  }

  catch (DBException e)

  {

    System.out.println("Your query failed, buddy.");

  }

  finally

  {

    DB.Close();

  }

  System.out.println("Query results = " + results);

}


In this example, the DBException exception is caught by the PerformQuery() method. Regardless of what happens, the database is closed using the DB.Close() method call in the finally clause.

Catching Multiple Exceptions

In Listing 7.1, two statements are executed in the try clause:


DB.Open();

results = DB.PerformQuery(SQL);

Most database class libraries define two different exceptions for these two operations; let's call them DBOpenException and ExecuteQueryException. With Java, your program can catch and handle multiple exceptions. This allows you to fine-tune error handling so that a variety of problems can be handled effectively. Listing 7.2 illustrates how this is done.


Listing 7.2. Catching multiple exceptions in Java.

void PerformQuery(String SQL, Database DB)

{

  String results;

  try

  {

    DB.Open();

    results = DB.PerformQuery(SQL);

  }

  catch (DBOpenException e)

  {

    System.out.println("Database open failed.");

  }

  catch (ExecuteQueryException e)

  {

    System.out.prinln("Query execution failed.");

  }

  finally

  {

    DB.Close();

  }

  System.out.println("Query results = " + results);

}


You can handle as many exceptions as you like, but no one wants to write error handlers for all the possible errors that could ever occur. Suppose that the source code for a method was not available to you and you had no idea which exceptions were thrown by that class, if any. The best that you could hope to do is catch the base exception, Exception, and perform some type of generic error handling.

Using the throws Keyword to Identify Possible Exceptions

Fortunately, Java provides a way for the creator of a method to define which exceptions are being thrown by a method: by using the throws keyword in the method definition, as demonstrated in Listing 7.3.


Listing 7.3. Catching and throwing exceptions in Java.

void PerformQuery(String SQL, Database DB) throws EmptyResultException

{

  String results;

  try

  {

    DB.Open();

    results = DB.PerformQuery(SQL);

  }

  catch (DBOpenException e)

  {

    System.out.println("Database open failed.");

  }

  catch (ExecuteQueryException e)

  {

    System.out.prinln("Query execution failed.");

  }

  finally

  {

    DB.Close();

  }

  if (results.length > 0) then

    System.out.println("Query results = " + results);

  else

  {

    throw new EmptyResultException();

  }

}


In Listing 7.3, the fictional EmptyResultException is thrown if the length of the result string is less than or equal to 0. The caller of this method could then catch this exception in the PerformQuery() method and have some idea of what error occurred, as in the following example:


void QueryButtonClick(void)

{

  String SQL = "SELECT * FROM EMPLOYEES";

  try

  {

    PerformQuery(SQL);

  }

  catch (EmptyResultException e)

  {

     System.out.println("No results returned.");

  }

  catch (Exception e)

  {

     System.out.println("An unknown error occurred.");

  }

}

Note that if you use the throws keyword in the definition of the PerformQuery() method, any method that calls PerformQuery() is required to try to catch an exception. This error can be caught at compile time. For instance, assume that you compiled the following method:


void QueryButtonClick(void)

{

  String SQL = "SELECT * FROM EMPLOYEES";

  PerformQuery(SQL);

}

Using Visual J++, the following error would occur:


error J0122: Exception 'EmptyResultException' not caught or declared by 

  'void DataDlg.QueryButtonClick(void)'

When calling any of the methods in the Java API, always be sure to implement an exception handler if the method definition in the online help shows that the method throws an exception.

Creating New Exception Classes

The previous examples demonstrate throwing and catching an exception known as EmptyResultException; however, the exception was never actually implemented. As discussed in the section "Handling Errors in Java," exceptions are simply special-case classes derived from the java.lang.Throwable class. The java.lang.Exception and java.lang.Error classes derive directly from this class and are used to implement errors and exceptions. Therefore, to build your own exception named EmptyResultException, you must simply build a class that derives from java.lang.Exception. The following example creates this exception:


class EmptyResultException extends Exception

{

  EmptyResultException() { super(); }

  EmptyResultException(String s) { super(s); }

}

This exception simply defines two constructors that are used to call the Exception class's constructors.

Exception handling is used extensively throughout the classes in the java package to trap common errors. This is much simpler and more reliable than defining a huge set of error codes that are used as return values from method calls. If an exception is thrown and no handler exists to catch it, the program will crash. If the program is actually a Java applet running within a browser, the browser will not crash because most browsers are designed to catch runtime errors to prevent this from happening. Nonetheless, always attempt to catch errors, particularly when performing calculations or operations based on user input or values obtained from a file. Because these types of data are unpredictable, always be sure to check for errors at runtime by implementing exception handlers.

Performing Simultaneous Operations Through Multithreading

A multithreading program can perform two or more different threads of operation simultaneously. Examples of multithreaded applications are the 32-bit Windows operating systems, Java applications that perform animation, and applications that allow user actions while some other operation is being performed. The underlying operating system actually controls the extent to which two different threads are allowed to operate. Different tasks can be assigned different priorities to control their operation in the queue.

You may be wondering why threading is necessary. Suppose you wanted to write an application that would allow the user to update a database and continue with the application. At first glance, this may not seem like that big of a deal. However, suppose the database update involves some complex trigger action on the database server that can take more than a minute. This operation can also cause an error about which the user must be aware. In an unthreaded Java application, the only way to do this is to call the method and wait until it finishes execution (see Listing 7.4).


Listing 7.4. An unthreaded database update.

void UpdateButtonClick()

{

  try

  {

    Database.Update();

  }

  catch (DBError e)

  {

    System.out.println("An error occurred!");

  }

  System.out.println("Update completed!");

}


As mentioned previously, the Database.Update() method call could take up to a minute to execute. Unfortunately, without a separate thread of execution, you have no way of continuing with the program while the Update() method goes off and does its work. The following section explains how to create a multithreaded application using Java so that there is no interruption in the program.

Creating a Threaded Application

The java.lang.Runnable interface and the java.lang.Thread class are used to create and run a separate thread of execution. Any object that is to execute within a thread must implement the Runnable interface. This interface defines a single method: run(). It is the responsibility of the developer of the threaded class to provide the run() method. The operations to be performed by the thread should be done in this method.

The Thread class is used to control a thread in a running program. Some of the important methods it defines are listed in Table 7.1.

Table 7.1. Public methods of the java.lang.Thread class.

MethodDescription
destroy()Destroys the thread without cleanup
getName()Gets the thread's name
getPriority()Gets the thread's priority (MAX_PRIORITY, MIN_PRIORITY, or NORM_PRIORITY)
getThreadGroup()Retrieves the thread's threadgroup
interrupt()Interrupts the thread
interrupted()true if the current thread has been interrupted
isAlive()true if the thread is alive
isDaemon()true if the thread is a daemon
isInterrupted()true if this thread has been interrupted
join()Waits for this thread to die
join(long millis)Waits millis milliseconds for this thread to die
join(long millis, int nanos)Waits millis milliseconds and nanos nanoseconds for this thread to die
resume()Resumes a suspended thread
run()Calls the Runnable object's run() method
setDaemon(boolean on)Marks the thread as a daemon thread or a normal thread
setName(String name)Sets the thread's name
setPriority(int newPriority)Sets the thread's priority
sleep(long millis)Forces the thread to sleep for millis milliseconds
sleep(long millis, int nanos) Forces the thread to sleep for millis milliseconds and nanos nanoseconds
start()Starts the thread's execution
stop()Stops the thread's execution
stop(Throwable obj)Stops the thread's execution and throws the throwable object as an exception
suspend()Suspends the operation of the thread
toString()Converts the thread's contents to a string
yield()Yields execution of the thread so others can execute

All these methods can be used to control a thread when it has been instantiated. Threads are commonly created using one of the following constructors (this is a partial listing; see the Visual J++ documentation to learn about the other Thread class constructors):

Listing 7.5 shows how the Thread class and the Runnable interface can be used to implement the same operation performed in Listing 7.4. The difference in Listing 7.5 is that the database update can occur in a separate thread, which allows the application to get on with other work.


Listing 7.5. A threaded database update.

public class DBUpdate implements Runnable

{

  Thread dbThread;

  DatabaseUpdate Database;



  public DBUpdate(String SQL)

  {

    Database =  new DatabaseClass(SQL);  //pass in query

    dbThread = new Thread(this);

    dbThread.start();

  }



  public void run()

  {

    try

    {

      Database.Update();

    }

    catch

    {

      System.out.println("An error occurred!");

    }

    System.out.println("Update completed!");

  }



  public static void main(String args[])

  {

    String name = "Bryan Morgan";

    new DBUpdate("UPDATE EMPLOYEE SET SALARY=1000000 WHERE NAME =" + name);

    /*

      Continue on with other work

    */

  }

}


The code in Listing 7.5 will not actually compile because the DatabaseUpdate class used in it is purely fictional; however, it serves to illustrate how a thread is created and used. The DBUpdate class implements the Runnable interface and therefore the run() method that performs the Update() method call in Listing 7.5. The difference is that it occurs within a thread. The thread object, dbThread, is created by calling the Thread(Runnable obj) constructor. When the dbThread.Start() method is called, the DatabaseUpdate class's run() method is called. While all of this going on, the main() method can continue with its execution. Listing 7.6 can be compiled and run within Visual J++. It uses separate threads of execution to print different outputs to the screen.


Listing 7.6. A multithreaded output example.

/*

  This example prints out some of the different baseball teams in the

  National League and the American League.  The printing occurs in separate

  threads to illustrate how two operations can occur simultaneously.

*/



public class BaseballTeams

{

  public static void main(String args[])

  {

    AmericanLeagueTeams AL = new AmericanLeagueTeams();

    NationalLeagueTeams NL = new NationalLeagueTeams();

    NL.start();

    AL.start();

  }

}



class AmericanLeagueTeams extends Thread

{

  int counter = 0;



  public void run()

  {

    while (counter++ < 5)

    {

      switch (counter)

      {

        case 0:

          System.out.println("AL: Cleveland Indians");

          break;

        case 1:

          System.out.println("AL: New York Yankees");

          break;

        case 2:

          System.out.println("AL: Texas Rangers");

          break;

        case 3:

          System.out.println("AL: Baltimore Orioles");

          break;

        case 4:

          System.out.println("AL: Seattle Mariners");

          break;

      }

      try

      {

        Thread.sleep(500);

      }

      catch (InterruptedException e)

      {

        System.out.println("AL Operation was interrupted");

      }

    }

  }

}



class NationalLeagueTeams extends Thread

{

  int counter = 0;



  public void run()

  {

    while (counter++ < 5)

    {

      switch (counter)

      {

        case 0:

          System.out.println("NL: Atlanta Braves");

          break;

        case 1:

          System.out.println("NL: St. Louis Cardinals");

          break;

        case 2:

          System.out.println("NL: San Diego Padres");

          break;

        case 3:

          System.out.println("NL: Los Angeles Dodgers");

          break;

        case 4:

          System.out.println("NL: Montreal Expos");

          break;

      }

      try

      {

        Thread.sleep(500);

      }

      catch (InterruptedException e)

      {

        System.out.println("NL Operation was interrupted");

      }

    }

  }

}


When Listing 7.6 is compiled and run, the following output is printed to the system console:


NL: Atlanta Braves

AL: Cleveland Indians

NL: St. Louis Cardinals

AL: New York Yankees

NL: San Diego Padres

AL: Texas Rangers

NL: Los Angeles Dodgers

AL: Baltimore Orioles

NL: Montreal Expos

AL: Seattle Mariners

The National League and American League teams are printed using separate threads. Each of these threads prints one team and then sleeps for 500 milliseconds. While one thread is sleeping, the system takes the opportunity to execute the other thread. The end result is that the threads give the impression of being run simultaneously.

In the previous example, two separate thread objects are implemented with separate run() methods. One printed out a list of hard-coded American League teams, while the other printed out a list of hard-coded National League teams. This worked well because both threads were working with completely different sets of data. However, stop to think what would happen if two threads were implemented within the same class. One of these threads could print data out to the screen, while the other thread could be used to modify data. When either of these threads is run separately, it should work fine. However, when both are run at the same time, data consistency errors will occur. (As one thread is printing data, the other thread is modifying it.) Listing 7.7 is an example of this problem.


Listing 7.7. Reading and writing data simultaneously.

/*

  This example prints out some of the different baseball teams in the

  National League.  The printing occurs in separate

  threads to illustrate how two operations can occur simultaneously.

*/



public class BaseballTeams

{

  String[] NLTeams = {"Atlanta Braves",

    "St. Louis Cardinals",

    "San Diego Padres",

    "Los Angeles Dodgers",

    "Montreal Expos"};



  Teams print1, print2;



  public BaseballTeams()

  {

    print1 = new Teams(this, "1");

    print1.start();

    print2 = new Teams(this, "2");

    print2.start();

  }



  public static void main(String args[])

  {

    new BaseballTeams();

  }



  public void printDataOut(String numPrint)

  {

    int counter = 0;

    int counter2 = 0;

    while (counter2 < 50)

    {

      while (counter < 5)

      {

        System.out.println(numPrint + ": " + NLTeams[counter]);

        counter++;

      }

      counter2++;

      counter = 0;

    }

  }

}



class Teams extends Thread

{



  BaseballTeams theTeams;

  String numPrint;



  public Teams(BaseballTeams teams, String number)

  {

    theTeams = teams;

    numPrint = number;

  }



  public void run()

  {

    theTeams.printDataOut(numPrint);

  }

}


This application creates two threads of the same type: Teams. These threads run simultaneously, and therefore the printouts switch back and forth between the Team "1" and the Team "2". A sample printout looks something like this:


1: Atlanta Braves

1: St. Louis Cardinals

2: Atlanta Braves

2: St. Louis Cardinals

2: San Diego Padres

1: San Diego Padres

2: Los Angeles Dodgers

2: Los Angeles Dodgers

...

This random printing happens because both threads were executing at the same time using the same data and method call. This is a problem (with the code written this way) and illustrates the difficulty involved in writing threaded applications.

Writing Thread-Safe Code Using Synchronized Methods

As you might have guessed, the Java designers were well aware of this potential problem and came up with a solution: It can be solved by declaring a method to be synchronized. When this is done, the method cannot be interrupted until it has finished execution. Take the following method declaration for printDataOut():


public void printDataOut(String numPrint)

Simply changing it to the following solves the problem:


synchronized void printDataOut(String numPrint)

Because it is now a synchronized method, the printDataOut() method must finish its execution before it can be reentered. Now when the code in Listing 7.7 is run, the output should look like the following:


1: Atlanta Braves

1: St. Louis Cardinals

1: San Diego Padres

1: Los Angeles Dodgers

1: Montreal Expos

.../*Repeat 100 times */

2: Atlanta Braves

2: St. Louis Cardinals

2: San Diego Padres

2: Los Angeles Dodgers

2: Montreal Expos

.../*Repeat 100 times */

Forcing a method to be synchronized solves the problem of multiple threads calling a single method simultaneously. However, it does not solve the problem that occurs when separate threads modify data directly within the same object. Another common problem occurs when two synchronized methods call each other. A condition known as deadlock occurs when this happens because both methods are waiting on the other to finish before they can continue. Multithreaded programming requires you to be extremely careful to prevent errors such as this from happening.

Streaming Input and Output

Like C++, the Java programming language supports the concept of streamable input and output. A stream is simply a byte-stream of data that is sent from a sender to a receiver. This data could be generated from a file, a socket connection sending data to a remote computer, or text printed out to the system console. All types of streams can be separated into two basic categories: input streams and output streams. Therefore, the java.io package includes two abstract classes that are used to provide this functionality, conveniently named InputStream and OutputStream. Table 7.2 lists the public methods found in the java.io.InputStream class. This class can be inherited from in order to open a stream for input.

Table 7.2. Public members of java.io.InputStream.

Method NameDescription
available()Returns the number of bytes that can be read without blocking
close()Closes the input stream
mark(int)Marks the current position in the input stream
markSupported()Used to determine whether the stream supports mark/reset
read()Reads a byte of data
read(byte[])Reads into an array of bytes
read(byte[], int, int)Reads into an array of bytes based on an offset and a read length
reset()Repositions the stream to the last marked position
skip(long)Skips a specified number of bytes of input

Table 7.3 lists the public methods in the java.io.OutputStream class, an abstract class that is inherited from by other classes opening a stream for output.

Table 7.3. Public members of java.io.OutputStream.

Method NameDescription
close()Closes the output stream
flush()Flushes the stream
write()Writes a byte of data
write(byte[])Writes a byte array of data
write(byte[], int, int)Writes a byte array of data based on an offset and a write length

There are many types of classes in the java package that are used to open either input or output streams. One of these classes has been used extensively thus far throughout this book: java.io.PrintStream is used to send output to the standard output device. (When using Visual J++ with JVIEW as the Java interpreter, this output device will be the MS-DOS command window.) The instance of the PrintStream class that has been used extensively thus far is the out member variable of the java.lang.System object. Each time that output has been sent to the screen in the examples, the out output stream class has been used. The following statement is an example of this:


System.out.println("Printing to the output stream...");

In addition to the PrintStream class, there are many other stream classes that are used often, such as FileInputStream/FileOutputStream and DataInputStream/DataOutputStream. The first two are file-streaming classes used to read and write files. (Keep in mind that file access is available only from a Java application. Most Web browsers disallow file access by a Java applet running in a Web browser.) The DataInputStream/DataOutputStream classes are handy because they allow you to read the Java primitive data types directly from the string. (The primitive data types are the basic Java data types such as int, char, long, float, and boolean.)

Listing 7.8 illustrates how streams can be utilized to read a file's contents and then output it to a separate file. This example uses the FileInputStream and FileOutputStream classes.


Listing 7.8. Copying the contents of a file using streams.

/*

 *

 * FileCopy

 *

 */

import java.io.*;



public class FileCopy

{

  public static void main(String args[])

  {

    try

    {

      FileInputStream in = new FileInputStream("Example10.txt");

      FileOutputStream out = new FileOutputStream("COPIED.TXT");

      int input;



      while ((input = in.read()) != -1)

      {

        out.write(input);

      }

      in.close();

      out.close();

    }

    catch (FileNotFoundException e)

    {

      System.out.println("Input file not found!");

    }

    catch (IOException e)

    {

      System.out.println("File could not be created!");

    }

  }

}


Running this simple program will verify that the contents of the Example10.txt file were indeed copied to Copied.txt. The FileInputStream.read() method is used to read through the file one byte at a time. The contents of the input file are then written out one byte at a time using the FileOutputStream.write() method.

Retrieving Input from the User

Examples in this book have repeatedly used calls to the PrintStream.println() method in order to print some text out to the screen. However, to this point, none of the examples have retrieved any input from the user using the standard input device. (On a personal computer, the standard input device would be the keyboard.) Listing 7.9 is accepting two things from a user: a filename and text to enter into that file. When the user is finished entering the text, pressing the Enter key will cause the text to be saved to the specified file.


Listing 7.9. CreateTextFile outputs user input to a text file.

/*

 *

 * CreateTextFile

 *

 */

import java.io.*;



public class CreateTextFile

{

  public static void main(String argv[])

  {

    try

    {

      DataInputStream in = new DataInputStream(System.in);



      System.out.println("Enter a filename: ");

      String filename = in.readLine();

      System.out.println("Enter text to write to the file: ");

      String contents = in.readLine();

      DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));

      out.writeBytes(contents);

    }

    catch (IOException e)

    {

      System.out.println("File could not be created!");

    }

  }

}


Note that in this example, the writeBytes() method is used to write a sequence of bytes to an output file. writeChars() is not used in this case because it writes a sequence of two-byte values to the file, which results in an empty space between each character in the output file.

Networking in Java

Because Java was designed from the outset to support sending bytecodes across a network and the capability of these bytecodes to communicate back to a server, its networking capabilities are extremely powerful. Operations that are extremely complicated in languages such as C or C++ have been greatly simplified using the classes and functionality available in the java.net package.

The first thing to realize about writing network software with Java is that it is completely platform independent. Standard TCP/IP networking concepts such as sockets, URLs, and IP addresses are used throughout the networking classes. Nowhere will you find any mention of Windows NT specifics or any references to UNIX kernels. Using the showDocument() method in the java.applet.Applet class, a single line of code can be used to retrieve files from a remote server for display on the local screen. You can use the java.net.URL class to create a connection to a remote URL.

NOTE
Keep in mind that URLs are not just HTTP addresses. They can be FTP, e-mail, Gopher, or any other URL format supported by your local Java runtime environment.

Sockets are used to open a "pipe" between a client and a port on a specified server. When the socket is opened for communication purposes, data can be streamed back and forth between the client and the server. Client-side sockets are implemented in the java.net.Socket class. Server-side sockets are used to communicate with a client socket and can be created using the java.net.ServerSocket class.

Summary

Exception handling is not a concept unique to Java. It is also supported in Ada, Object Pascal, and C++, to name a few. By anticipating a potential error before it happens and coding for it ahead of time, you can prevent unexplained crashes. In addition, errors can be thrown by a method with some assurance that the caller of the method will handle this error gracefully.

Multithreading applications have become more popular as hardware and operating systems have advanced. A thread represents a single path of execution within a program. A multithreaded program is an application that uses more than one thread simultaneously to accomplish some task. Java supports multithreading through the use of the java.lang.Thread class and the java.lang.Runnable interface.

Input/output streams can be used to send streams of data either to or from some source. The concept of a stream as a generic flow of data allows you to build entire classes of objects that are similar in nature. Native methods are Java's way of allowing code written in other languages to be called from within a Java program. Although this may be the only solution for Macintosh and UNIX programmers for the near future, Visual J++ also supports the creation and use of COM objects from a Java application. By encapsulating code in a COM object, you can call an object from a program written in any language.

Because the Java language is designed from the ground up as a distributed language, networking operations that are extremely complicated using other languages are greatly simplified. The vast majority of networking operations are contained in the java.net package. The Java programming language and its companion, the Java Base API, provide a variety of tools and features that can be used to build powerful platform-independent, distributed applications. The features presented in this chapter finish the discussions of the capabilities of the Java programming language. Chapter 8, "Developing Applets and Applications," examines the difference between Java applets and applications and is followed by a thorough examination of the capabilities of the Visual J++ development environment in Part II, "The Visual J++ Development Tools."