Thursday 9 March 2017

Packages, Access Protection & Importing Packages - Java Tutorials

Packages

In the preceding chapters, the name of each example class was taken from the same name space. This means that a unique name had to be used for each class to avoid name collisions. After a while, without some way to manage the name space, you could run out of convenient, descriptive names for individual classes. You also need some way to be assured that the name you choose for a class will be reasonably unique and not collide with class names chosen by other programmers. (Imagine a small group of programmers fighting over who gets to use the name “Foobar” as a class name. Or, imagine the entire Internet community arguing over who first named a class “Espresso.”) Thankfully, Java provides a mechanism for partitioning the class name space into more manageable chunks. This mechanism is the package. The package is both a naming and a visibility control mechanism. You can define classes inside a package that are not accessible by code outside that package. You can also define class members that are only exposed to other members of the same package. This allows your classes to have intimate knowledge of each other, but not expose that knowledge to the rest of the world.


Defining a Package

To create a package is quite easy: simply include a package command as the first statement in a Java source file. Any classes declared within that file will belong to the specified package. The package statement defines a name space in which classes are stored. If you omit the package statement, the class names are put into the default package, which has no name. (This is why you haven’t had to worry about packages before now.) While the default package is fine for short, sample programs, it is inadequate for real applications. Most of the time, you will define a package for your code.

This is the general form of the package statement:

  package pkg;

Here, pkg is the name of the package. For example, the following statement creates a package called MyPackage.

  package MyPackage;

Java uses file system directories to store packages. For example, the .class files for any classes you declare to be part of MyPackage must be stored in a directory called MyPackage. Remember that case is significant, and the directory name must match the package name exactly.

More than one file can include the same package statement. The package statement simply specifies to which package the classes defined in a file belong. It does not exclude other classes in other files from being part of that same package. Most real-world packages are spread across many files.

You can create a hierarchy of packages. To do so, simply separate each package name from the one above it by use of a period. The general form of a multileveled package statement is shown here:

  package pkg1[.pkg2[.pkg3]];

A package hierarchy must be reflected in the file system of your Java development system. For example, a package declared as

  package java.awt.image;

needs to be stored in java/awt/image, java\awt\image, or java:awt:image on your UNIX, Windows, or Macintosh file system, respectively. Be sure to choose your package names carefully. You cannot rename a package without renaming the directory in which the classes are stored.


Finding Packages and CLASSPATH

As just explained, packages are mirrored by directories. This raises an important question: How does the Java run-time system know where to look for packages that you create? The answer has two parts. First, by default, the Java run-time system uses the current working directory as its starting point. Thus, if your package is in the current directory, or a subdirectory of the current directory, it will be found. Second, you can specify a directory path or paths by setting the CLASSPATH environmental variable.

For example, consider the following package specification.

  package MyPack;

In order for a program to find MyPack, one of two things must be true. Either the program is executed from a directory immediately above MyPack, or CLASSPATH must be set to include the path to MyPack. The first alternative is the easiest (and doesn’t require a change to CLASSPATH), but the second alternative lets your program find MyPack no matter what directory the program is in. Ultimately, the choice is yours.

The easiest way to try the examples shown in this book is to simply create the package directories below your current development directory, put the .class files into the appropriate directories and then execute the programs from the development directory. This is the approach assumed by the examples.


A Short Package Example

Keeping the preceding discussion in mind, you can try this simple package:

  // A simple package
  package MyPack;

  class Balance {
    String name;
    double bal;

    Balance(String n, double b) {
      name = n;
      bal = b;
    }

    void show() {
      if(bal<0)
        System.out.print("--> ");
        System.out.println(name + ": $" + bal);
    }
  }

  class AccountBalance {
    public static void main(String args[]) {
      Balance current[] = new Balance[3];

      current[0] = new Balance("K. J. Fielding", 123.23);
      current[1] = new Balance("Will Tell", 157.02);
      current[2] = new Balance("Tom Jackson", -12.33);

      for(int i=0; i<3; i++) current[i].show();
    }
  }

Call this file AccountBalance.java, and put it in a directory called MyPack. Next, compile the file. Make sure that the resulting .class file is also in the MyPack directory. Then try executing the AccountBalance class, using the following command line:

  java MyPack.AccountBalance

Remember, you will need to be in the directory above MyPack when you execute this command, or to have your CLASSPATH environmental variable set appropriately.

As explained, AccountBalance is now part of the package MyPack. This means that it cannot be executed by itself. That is, you cannot use this command line:

  java AccountBalance

AccountBalance must be qualified with its package name.




Access Protection

In the preceding chapters, you learned about various aspects of Java’s access control mechanism and its access specifiers. For example, you already know that access to a private member of a class is granted only to other members of that class. Packages add another dimension to access control. As you will see, Java provides many levels of protection to allow fine-grained control over the visibility of variables and methods within classes, subclasses, and packages.

Classes and packages are both means of encapsulating and containing the name space and scope of variables and methods. Packages act as containers for classes and other subordinate packages. Classes act as containers for data and code. The class is Java’s smallest unit of abstraction. Because of the interplay between classes and packages, Java addresses four categories of visibility for class members:
  • Subclasses in the same package
  • Non-subclasses in the same package
  • Subclasses in different packages
  • Classes that are neither in the same package nor subclasses

The three access specifiers, private, public, and protected, provide a variety of ways to produce the many levels of access required by these categories. Table 9-1 sums up the interactions.

While Java’s access control mechanism may seem complicated, we can simplify it as follows. Anything declared public can be accessed from anywhere. Anything declared private cannot be seen outside of its class. When a member does not have an explicit access specification, it is visible to subclasses as well as to other classes in the same package. This is the default access. If you want to allow an element to be seen outside your current package, but only to classes that subclass your class directly, then declare that element protected.

Table 9-1 applies only to members of classes. A class has only two possible access levels: default and public. When a class is declared as public, it is accessible by any other code. If a class has default access, then it can only be accessed by other code within its same package.

Class Member Access

                                        Private             No modifier              Protected               Public

Same class                         Yes                    Yes                             Yes                          Yes

Same package                    No                    Yes                              Yes                          Yes
subclass                        

Same package                   No                    Yes                              Yes                          Yes
non-subclass              

Different                           No                     No                              Yes                          Yes
package
subclass 

Different                           No                     No                               No                          Yes
package
non-subclass


An Access Example

The following example shows all combinations of the access control modifiers. This example has two packages and five classes. Remember that the classes for the two different packages need to be stored in directories named after their respective packages—in this case, p1 and p2.

The source for the first package defines three classes: Protection, Derived, and SamePackage. The first class defines four int variables in each of the legal protection modes. The variable n is declared with the default protection, n_pri is private, n_pro is protected, and n_pub is public.

Each subsequent class in this example will try to access the variables in an instance of this class. The lines that will not compile due to access restrictions are commented out by use of the single-line comment //. Before each of these lines is a comment listing the places from which this level of protection would allow access.

The second class, Derived, is a subclass of Protection in the same package, p1. This grants Derived access to every variable in Protection except for n_pri, the private one. The third class, SamePackage, is not a subclass of Protection, but is in the same package and also has access to all but n_pri.

This is file Protection.java:

  package p1;

  public class Protection {
    int n = 1;
    private int n_pri = 2;
    protected int n_pro = 3;
    public int n_pub = 4;

    public Protection() {
      System.out.println("base constructor");
      System.out.println("n = " + n);
      System.out.println("n_pri = " + n_pri);
      System.out.println("n_pro = " + n_pro);
      System.out.println("n_pub = " + n_pub);
    }
  }

This is file Derived.java:

  package p1;
  
  class Derived extends Protection {
    Derived() {
      System.out.println("derived constructor");
      System.out.println("n = " + n);
  //  class only
  //  System.out.println("n_pri = " + n_pri);

      System.out.println("n_pro = " + n_pro);
      System.out.println("n_pub = " + n_pub);
    }
  }

This is file SamePackage.java:

  package p1;
  
  class SamePackage {
    SamePackage() {

      Protection p = new Protection();
      System.out.println("same package constructor");
      System.out.println("n = " + p.n);

  //  class only
  //  System.out.println("n_pri = " + p.n_pri);
      System.out.println("n_pro = " + p.n_pro);
      System.out.println("n_pub = " + p.n_pub);
    }
  }

Following is the source code for the other package, p2. The two classes defined in p2 cover the other two conditions which are affected by access control. The first class, Protection2, is a subclass of p1.Protection. This grants access to all of p1.Protection’s variables except for n_pri (because it is private) and n, the variable declared with the default protection. Remember, the default only allows access from within the class or the package, not extra-package subclasses. Finally, the class OtherPackage has access to only one variable, n_pub, which was declared public.

This is file Protection2.java:

  package p2;

  class Protection2 extends p1.Protection {
    Protection2() {
      System.out.println("derived other package constructor");
  
  //  class or package only
  //  System.out.println("n = " + n);

  //  class only
  //  System.out.println("n_pri = " + n_pri);

      System.out.println("n_pro = " + n_pro);
      System.out.println("n_pub = " + n_pub);
    }
  }

This is file OtherPackage.java:

  package p2;

  class OtherPackage {
    OtherPackage() {
      p1.Protection p = new p1.Protection();
      System.out.println("other package constructor");

  //  class or package only
  //  System.out.println("n = " + p.n);

  //  class only
  //  System.out.println("n_pri = " + p.n_pri);

  //  class, subclass or package only
  //  System.out.println("n_pro = " + p.n_pro);

      System.out.println("n_pub = " + p.n_pub);
    }
  }

If you wish to try these two packages, here are two test files you can use. The one for package p1 is shown here:

  // Demo package p1.
  package p1;
  
  // Instantiate the various classes in p1.
  public class Demo {
    public static void main(String args[]) {
      Protection ob1 = new Protection();
      Derived ob2 = new Derived();
      SamePackage ob3 = new SamePackage();
    }
  }

The test file for p2 is shown next:

  // Demo package p2.
  package p2;

  // Instantiate the various classes in p2.
  public class Demo {
    public static void main(String args[]) {
      Protection2 ob1 = new Protection2();
      OtherPackage ob2 = new OtherPackage();
    }
  }




Importing Packages

Given that packages exist and are a good mechanism for compartmentalizing diverse classes from each other, it is easy to see why all of the built-in Java classes are stored in packages. There are no core Java classes in the unnamed default package; all of the standard classes are stored in some named package. Since classes within packages must be fully qualified with their package name or names, it could become tedious to type in the long dot-separated package path name for every class you want to use. For this reason, Java includes the import statement to bring certain classes, or entire
packages, into visibility. Once imported, a class can be referred to directly, using only its name. The import statement is a convenience to the programmer and is not technically needed to write a complete Java program. If you are going to refer to a few dozen classes in your application, however, the import statement will save a lot of typing.

In a Java source file, import statements occur immediately following the package statement (if it exists) and before any class definitions. This is the general form of the import statement:


  import pkg1[.pkg2].(classname|*);

Here, pkg1 is the name of a top-level package, and pkg2 is the name of a subordinate package inside the outer package separated by a dot (.). There is no practical limit on the depth of a package hierarchy, except that imposed by the file system. Finally, you specify either an explicit classname or a star (*), which indicates that the Java compiler should import the entire package. This code fragment shows both forms in use:

  import java.util.Date;
  import java.io.*;

The star form may increase compilation time—especially if you import several large packages. For this reason it is a good idea to explicitly name the classes that you want to use rather than importing whole packages. However, the star form has absolutely no effect on the run-time performance or size of your classes.

All of the standard Java classes included with Java are stored in a package called java. The basic language functions are stored in a package inside of the java package called java.lang. Normally, you have to import every package or class that you want to use, but since Java is useless without much of the functionality in java.lang, it is implicitly imported by the compiler for all programs. This is equivalent to the following line being at the top of all of your programs:

  import java.lang.*;

If a class with the same name exists in two different packages that you import using the star form, the compiler will remain silent, unless you try to use one of the classes. In that case, you will get a compile-time error and have to explicitly name the class specifying its package.

Any place you use a class name, you can use its fully qualified name, which includes its full package hierarchy. For example, this fragment uses an import statement:

  import java.util.*;
  class MyDate extends Date {
  }

The same example without the import statement looks like this:

  class MyDate extends java.util.Date {

  }

As shown in Table 9-1, when a package is imported, only those items within the package declared as public will be available to non-subclasses in the importing code. For example, if you want the Balance class of the package MyPack shown earlier to be available as a stand-alone class for general use outside of MyPack, then you will need to declare it as public and put it into its own file, as shown here:

  package MyPack;
  
  /* Now, the Balance class, its constructor, and its
     show() method are public. This means that they can
     be used by non-subclass code outside their package.
  */
  public class Balance {
    String name;
    double bal;

    public Balance(String n, double b) {
      name = n;
      bal = b;
    }

    public void show() {
      if(bal<0)
        System.out.print("--> ");
      System.out.println(name + ": $" + bal);
    }
  }

As you can see, the Balance class is now public. Also, its constructor and its show( ) method are public, too. This means that they can be accessed by any type of code outside the MyPack package. For example, here TestBalance imports MyPack and is then able to make use of the Balance class:

  import MyPack.*;
  
  class TestBalance {
    public static void main(String args[]) {

      /* Because Balance is public, you may use Balance
         class and call its constructor. */

      Balance test = new Balance("J. J. Jaspers", 99.88);

      test.show(); // you may also call show()
    }
  }

As an experiment, remove the public specifier from the Balance class and then try compiling TestBalance. As explained, errors will result.

Dynamic Method Dispatch, Using Abstract Classes, Using final with Inheritance & The Object Class - Java Tutorials

Dynamic Method Dispatch

While the examples in the preceding section demonstrate the mechanics of method overriding, they do not show its power. Indeed, if there were nothing more to method overriding than a name space convention, then it would be, at best, an interesting curiosity, but of little real value. However, this is not the case. Method overriding forms the basis for one of Java’s most powerful concepts: dynamic method dispatch. Dynamic method dispatch is the mechanism by which a call to an overridden method is resolved at run time, rather than compile time. Dynamic method dispatch is important because this is how Java implements run-time polymorphism.

Let’s begin by restating an important principle: a superclass reference variable can refer to a subclass object. Java uses this fact to resolve calls to overridden methods at run time. Here is how. When an overridden method is called through a superclass reference, Java determines which version of that method to execute based upon the type of the object being referred to at the time the call occurs. Thus, this determination is made at run time. When different types of objects are referred to, different versions of an overridden method will be called. In other words, it is the type of the object being referred to (not the type of the reference variable) that determines which version of an overridden method will be executed. Therefore, if a superclass contains a method that is overridden by a subclass, then when different types of objects are referred to through a superclass reference variable, different versions of the method are executed.

Here is an example that illustrates dynamic method dispatch:

  // Dynamic Method Dispatch
  class A {
    void callme() {
      System.out.println("Inside A's callme method");
    }
  }

  class B extends A {
    // override callme()
    void callme() {
      System.out.println("Inside B's callme method");
    }
  }

  class C extends A {
    // override callme()
    void callme() {
      System.out.println("Inside C's callme method");
    }
  }

  class Dispatch {
    public static void main(String args[]) {
      A a = new A(); // object of type A
      B b = new B(); // object of type B
      C c = new C(); // object of type C
      A r; // obtain a reference of type A

      r = a; // r refers to an A object
      r.callme(); // calls A's version of callme

      r = b; // r refers to a B object
      r.callme(); // calls B's version of callme

      r = c; // r refers to a C object
      r.callme(); // calls C's version of callme
    }
  }

The output from the program is shown here:

  Inside A’s callme method
  Inside B’s callme method
  Inside C’s callme method

This program creates one superclass called A and two subclasses of it, called B and C. Subclasses B and C override callme( ) declared in A. Inside the main( ) method, objects of type A, B, and C are declared. Also, a reference of type A, called r, is declared. The program then assigns a reference to each type of object to r and uses that reference to invoke callme( ). As the output shows, the version of callme( ) executed is determined by the type of object being referred to at the time of the call. Had it been determined by the type of the reference variable, r, you would see three calls to A’s callme( ) method.

Readers familiar with C++ or C# will recognize that overridden methods in Java are similar to virtual functions in those languages.


Why Overridden Methods?

As stated earlier, overridden methods allow Java to support run-time polymorphism. Polymorphism is essential to object-oriented programming for one reason: it allows a general class to specify methods that will be common to all of its derivatives, while allowing subclasses to define the specific implementation of some or all of those methods. Overridden methods are another way that Java implements the “one interface, multiple methods” aspect of polymorphism.

Part of the key to successfully applying polymorphism is understanding that the superclasses and subclasses form a hierarchy which moves from lesser to greater specialization. Used correctly, the superclass provides all elements that a subclass can use directly. It also defines those methods that the derived class must implement on its own. This allows the subclass the flexibility to define its own methods, yet still enforces a consistent interface. Thus, by combining inheritance with overridden methods, a superclass can define the general form of the methods that will be used by all of its subclasses.

Dynamic, run-time polymorphism is one of the most powerful mechanisms that object-oriented design brings to bear on code reuse and robustness. The ability of existing code libraries to call methods on instances of new classes without recompiling while maintaining a clean abstract interface is a profoundly powerful tool.


Applying Method Overriding

Let’s look at a more practical example that uses method overriding. The following program creates a superclass called Figure that stores the dimensions of various two-dimensional objects. It also defines a method called area( ) that computes the area of an object. The program derives two subclasses from Figure. The first is Rectangle and the second is Triangle. Each of these subclasses overrides area( ) so that it returns the area of a rectangle and a triangle, respectively.

  // Using run-time polymorphism.
  class Figure {
    double dim1;
    double dim2;

    Figure(double a, double b) {
      dim1 = a;
      dim2 = b;
    }

    double area() {
      System.out.println("Area for Figure is undefined.");
      return 0;
    }
  }

  class Rectangle extends Figure {
    Rectangle(double a, double b) {
      super(a, b);
    }

    // override area for rectangle
    double area() {
      System.out.println("Inside Area for Rectangle.");
      return dim1 * dim2;
    }
  }

  class Triangle extends Figure {
    Triangle(double a, double b) {
      super(a, b);
    }

    // override area for right triangle
    double area() {
      System.out.println("Inside Area for Triangle.");
      return dim1 * dim2 / 2;
    }
  }

  class FindAreas {
    public static void main(String args[]) {
      Figure f = new Figure(10, 10);
      Rectangle r = new Rectangle(9, 5);
      Triangle t = new Triangle(10, 8);

      Figure figref;

      figref = r;
      System.out.println("Area is " + figref.area());

      figref = t;
      System.out.println("Area is " + figref.area());

      figref = f;
      System.out.println("Area is " + figref.area());
    }
  }

The output from the program is shown here:

  Inside Area for Rectangle.
  Area is 45
  Inside Area for Triangle.
  Area is 40
  Area for Figure is undefined.
  Area is 0

Through the dual mechanisms of inheritance and run-time polymorphism, it is possible to define one consistent interface that is used by several different, yet related, types of objects. In this case, if an object is derived from Figure, then its area can be obtained by calling area( ). The interface to this operation is the same no matter what type of figure is being used.





Using Abstract Classes

There are situations in which you will want to define a superclass that declares the structure of a given abstraction without providing a complete implementation of every method. That is, sometimes you will want to create a superclass that only defines a generalized form that will be shared by all of its subclasses, leaving it to each subclass to fill in the details. Such a class determines the nature of the methods that the subclasses must implement. One way this situation can occur is when a superclass is unable to create a meaningful implementation for a method. This is the case with the class Figure used in the preceding example. The definition of area( ) is simply a placeholder. It will not compute and display the area of any type of object.

As you will see as you create your own class libraries, it is not uncommon for a method to have no meaningful definition in the context of its superclass. You can handle this situation two ways. One way, as shown in the previous example, is to simply have it report a warning message. While this approach can be useful in certain situations—such as debugging—it is not usually appropriate. You may have methods which must be overridden by the subclass in order for the subclass to have any meaning. Consider the class Triangle. It has no meaning if area( ) is not defined. In this case, you want some way to ensure that a subclass does, indeed, override all necessary methods. Java’s solution to this problem is the abstract method.

You can require that certain methods be overridden by subclasses by specifying the abstract type modifier. These methods are sometimes referred to as subclasser responsibility because they have no implementation specified in the superclass. Thus, a subclass must override them—it cannot simply use the version defined in the superclass. To declare an abstract method, use this general form:

  abstract type name(parameter-list);

As you can see, no method body is present.

Any class that contains one or more abstract methods must also be declared abstract. To declare a class abstract, you simply use the abstract keyword in front of the class keyword at the beginning of the class declaration. There can be no objects of an abstract class. That is, an abstract class cannot be directly instantiated with the new operator. Such objects would be useless, because an abstract class is not fully defined. Also, you cannot declare abstract constructors, or abstract static methods. Any subclass of an abstract class must either implement all of the abstract methods in the superclass, or be itself declared abstract.

Here is a simple example of a class with an abstract method, followed by a class which implements that method:

  // A Simple demonstration of abstract.
  abstract class A {
    abstract void callme();

    // concrete methods are still allowed in abstract classes
    void callmetoo() {
      System.out.println("This is a concrete method.");
    }
  }

  class B extends A {
    void callme() {
      System.out.println("B's implementation of callme.");
    }
  }

  class AbstractDemo {
    public static void main(String args[]) {
      B b = new B();
      
      b.callme();
      b.callmetoo();
    }
  }

Notice that no objects of class A are declared in the program. As mentioned, it is not possible to instantiate an abstract class. One other point: class A implements a concrete method called callmetoo ( ). This is perfectly acceptable. Abstract classes can include as much implementation as they see fit.

Although abstract classes cannot be used to instantiate objects, they can be used to create object references, because Java’s approach to run-time polymorphism is implemented through the use of superclass references. Thus, it must be possible to create a reference to an abstract class so that it can be used to point to a subclass object. You will see this feature put to use in the next example.

Using an abstract class, you can improve the Figure class shown earlier. Since there is no meaningful concept of area for an undefined two-dimensional figure, the following version of the program declares area( ) as abstract inside Figure. This, of course, means that all classes derived from Figure must override area( ).

  // Using abstract methods and classes.
  abstract class Figure {
    double dim1;
    double dim2;

    Figure(double a, double b) {
      dim1 = a;
      dim2 = b;
    }

    // area is now an abstract method
    abstract double area();
  }

  class Rectangle extends Figure {
    Rectangle(double a, double b) {
      super(a, b);
    }

    // override area for rectangle
    double area() {
      System.out.println("Inside Area for Rectangle.");
      return dim1 * dim2;
    }
  }

  class Triangle extends Figure {
    Triangle(double a, double b) {
      super(a, b);
    }

    // override area for right triangle
    double area() {
      System.out.println("Inside Area for Triangle.");
      return dim1 * dim2 / 2;
    }
  }

  class AbstractAreas {
    public static void main(String args[]) {
    // Figure f = new Figure(10, 10); // illegal now
      Rectangle r = new Rectangle(9, 5);
      Triangle t = new Triangle(10, 8);
      Figure figref; // this is OK, no object is created

      figref = r;
      System.out.println("Area is " + figref.area());

      figref = t;
      System.out.println("Area is " + figref.area());
    }
  }

As the comment inside main( ) indicates, it is no longer possible to declare objects of type Figure, since it is now abstract. And, all subclasses of Figure must override area( ). To prove this to yourself, try creating a subclass that does not override area( ). You will receive a compile-time error.

Although it is not possible to create an object of type Figure, you can create a reference variable of type Figure. The variable figref is declared as a reference to Figure, which means that it can be used to refer to an object of any class derived from Figure. As explained, it is through superclass reference variables that overridden methods are resolved at run time.




Using final with Inheritance

The keyword final has three uses. First, it can be used to create the equivalent of a named constant. This use was described in the preceding chapter. The other two uses of final apply to inheritance. Both are examined here.


Using final to Prevent Overriding

While method overriding is one of Java’s most powerful features, there will be times when you will want to prevent it from occurring. To disallow a method from being overridden, specify final as a modifier at the start of its declaration. Methods declared as final cannot be overridden. The following fragment illustrates final:

  class A {
    final void meth() {
      System.out.println("This is a final method.");
    }
  }

  class B extends A {
    void meth() { // ERROR! Can't override.
      System.out.println("Illegal!");
    }
  }

Because meth( ) is declared as final, it cannot be overridden in B. If you attempt to do so, a compile-time error will result.

Methods declared as final can sometimes provide a performance enhancement: The compiler is free to inline calls to them because it “knows” they will not be overridden by a subclass. When a small final method is called, often the Java compiler can copy the bytecode for the subroutine directly inline with the compiled code of the calling method, thus eliminating the costly overhead associated with a method call. Inlining is only an option with final methods. Normally, Java resolves calls to methods dynamically, at run time. This is called late binding. However, since final methods cannot be overridden, a call to one can be resolved at compile time. This is called early binding.


Using final to Prevent Inheritance

Sometimes you will want to prevent a class from being inherited. To do this, precede the class declaration with final. Declaring a class as final implicitly declares all of its methods as final, too. As you might expect, it is illegal to declare a class as both abstract and final since an abstract class is incomplete by itself and relies upon its subclasses to provide complete implementations.

Here is an example of a final class:

  final class A {
    // ...
  }

  // The following class is illegal.
  class B extends A { // ERROR! Can't subclass A
    // ...
  }

As the comments imply, it is illegal for B to inherit A since A is declared as final.




The Object Class

There is one special class, Object, defined by Java. All other classes are subclasses of Object. That is, Object is a superclass of all other classes. This means that a reference variable of type Object can refer to an object of any other class. Also, since arrays are implemented as classes, a variable of type Object can also refer to any array.

Object defines the following methods, which means that they are available in every object.


Method                                         Purpose

Object clone( )                              Creates a new object that is the same as the object being cloned.

boolean equals(Object object)      Determines whether one object is equal to another.

void finalize( )                              Called before an unused object is recycled.

Class getClass( )                           Obtains the class of an object at run time.

int hashCode( )                              Returns the hash code associated with the invoking object.

void notify( )                                 Resumes execution of a thread waiting on the invoking object.

void notifyAll( )                            Resumes execution of all threads waiting on the invoking object.

String toString( )                           Returns a string that describes the object.

void wait( )                                   Waits on another thread of execution.
void wait(long milliseconds)
void wait(long milliseconds,
int nanoseconds)                           


The methods getClass( ), notify( ), notifyAll( ), and wait( ) are declared as final. You may override the others. These methods are described elsewhere in this book. However, notice two methods now: equals( ) and toString( ). The equals( ) method compares the contents of two objects. It returns true if the objects are equivalent, and false otherwise. The toString( ) method returns a string that contains a description of the object on which it is called. Also, this method is automatically called when an object is output using println( ). Many classes override this method. Doing so allows them to tailor a description specifically for the types of objects that they create. See Chapter 13 for more information on toString( ).