Study guide: Classes within classes

Brush up on Java terms, learn tips and cautions, review homework assignments, and read Jeff's answers to student questions

Glossary of terms

anonymous inner class
An unnamed inner class located within a method block or a statement block in an object creation expression.
block
A region of source code sandwiched between open brace ({) and close brace (}) characters.
inner classes
Nested classes that you declare without the static keyword.
instance inner class
An inner class that you declare within a class block. In other words, you do not declare that class within a method block or a statement block such as an if statement block.
iterator
An object whose methods return other objects in succession from a containing object and determine if there are additional objects in the containing object to return.
local inner class
A named inner class located within a method block or a statement block.
nest
To appear within. For example, when a class nests within another class, the nested class appears within an enclosing class.
nested top-level class
A class that you declare with the static keyword in a top-level class or another nested top-level class.
synthetic field
A compiler-created field that links a local inner class to a block's local variable or reference type parameter.
top-level class
A class that has no enclosing class. In other words, the class is not a nested top-level class or an inner class.

Tips and cautions

These tips and cautions will help you write better programs and save you from agonizing over why the compiler produces error messages.

Tips

  • You can declare either a nested top-level class or an instance inner class with the private, protected, or public keywords to determine that class's level of accessibility to code outside the enclosing class.
  • Learn more about the compiler's manipulation of local inner classes into top-level classes by studying the Inner Classes Specification document and by using the javap program to disassemble class files.

Cautions

  • A nested top-level class cannot access any enclosing class's instance members (fields and methods).
  • Attempts to declare constructors in an anonymous inner class fail because constructors must have the names of the classes in which they appear, and anonymous inner classes have no names.

Miscellaneous notes and thoughts

There is some confusion regarding nested classes and static members. That confusion results from compiler error messages that appear when the compiler discovers static field, method, or class declarations within inner classes. It turns out that only nested top-level classes can declare such members. To the best of my knowledge, the reason has something to do with keeping Java more object-oriented. To see these errors for yourself, compile the following InnerStatic source code:

// InnerStatic.java
class InnerStatic
{
   // A is a nested top-level class.
   static class A
   {
      // static field, method, and class declarations are permitted in a
      // nested top-level class.
      static int x = 1;
      static void methodA ()
      {
      }
      static class FooA
      {
      }
   }
   // B is an instance inner class.
   class B extends A
   {
      // static field, method, and class declarations are not permitted
      // in an instance inner class.
      static int y = 2; // Compiler error.
      static void methodB () // Compiler error.
      {
         System.out.println (x); // This statement is okay.
      }
      static class FooB // Compiler error.
      {
      }
      // final static field declarations are permitted in an instance
      // inner class.
      final static int Z = 3;
   }
}

InnerStatic declares nested top-level class A and instance inner class B. Each class contains three static member declarations: A declares x, static void methodA(), and FooA; whereas B declares y, static void methodB(), and FooB. Although A's declarations are legal, B's declarations are not. However, a close look at B's source code reveals two legal uses of static fields:

  • A static field (such as x) may be inherited from a nested top-level class and accessed from an inner class
  • Final static fields (that is, constants) may be declared in an inner class

Homework

Please answer the following questions and exercises. You will have to do some research:

  • How do you access a shadowed variable from within a nested top-level class?
  • Create an anonymous inner class from nothing more than an interface.
  • Can interfaces nest within classes?

Answers to last month's homework

Last month, I asked you to rewrite WeakReferenceDemo to use the WeakHashMap class and describe how the resulting program differs from what appeared in the article.

That exercise is challenging because it involves the use of a class from the Collections API portion of Java's standard class library. Specifically, that class is known as WeakHashMap, located in the java.util package (I have more to say about Collections and packages in future articles.) The code below displays source code to WeakHashMapDemo, which uses WeakHashMap:

WeakReferenceDemo.java

// WeakHashMapDemo.java
import java.util.*;
class WeakHashMapDemo
{
   public static void main (String [] args)
   {
      // Create a String object that is strongly reachable from key.
      String key = new String ("key");
      /*
         Note: For this program, you cannot say String key = "key";. You
               cannot do that because (by itself), "key" is strongly 
               referenced from an internal constant pool data structure
               (which I will discuss in a future article). There is no
               way for the program to nullify that strong reference. As
               a result, that object will never be garbage collected,
               and the polling loop will be infinite.
      */
      // Create an Object object that is strongly reachable from value.
      Object value = new Object ();
      // Create a WeakHashMap object that is strongly reachable from whm.
      WeakHashMap whm = new WeakHashMap ();
      // Place the String and Object objects in the WeakHashMap.
      whm.put (key, value);
      // Confirm number of entries in WeakHashMap equals 1.
      System.out.println ("Number of entries = " + whm.size ());
      // Remove the only strong reference to the String object.
      key = null;
      // As long as the entry remains in the WeakHashMap, suggest that
      // the garbage collector should run.
      while (whm.size () != 0)
         System.gc ();
      // Confirm number of entries in WeakHashMap equals 0. This means
      // the entry and key objects have been removed.
      System.out.println ("Number of entries = " + whm.size ());
      // Remove the strong reference to the Object object, so that object
      // is eligible for garbage collection. Although not necessary in this
      // program, because we are about to exit, imagine a continuously 
      // running program and that this code is in some kind of long-lasting
      // loop.
      value = null;
   }
}

In some ways, WeakHashMapDemo resembles WeakReferenceDemo. For example, WeakHashMapDemo uses the same String key and Object value objects. However, the two programs differ significantly. To begin, in WeakReferenceDemo, a WeakReference object doesn't need to wrap itself around the String key object because the WeakHashMap class considers its key weak. Once the last strong reference to the String key object disappears (through the key = null; assignment) and once the garbage collector runs, WeakHashMap automatically removes the String key and key/value entry objects.

Another difference involves WeakReferenceDemo's lack of a ReferenceQueue object. WeakHashMapDemo does not need a ReferenceQueue object nor does it need to continually poll that object for a WeakReference to arrive. Instead, it continually calls whm.size() to determine if the number of entries in the WeakHashMap drops to zero and calls System.gc() to ask the garbage collector to run -- so WeakHashMap's number of entries will eventually drop to zero.

Related: