corejava11 (6.3 internal)

6.3 Internal class

Internal class category is defined in another class. Why would you do that? There are two reasons:

  • Inner classes can be hidden to other classes in the same package.
  • Method within the class can access data from the definition of the scope thereof, including those originally private data.

Inner classes in the past is very important to implement a callback succinctly, but now lambda expressions better. However, inner classes are useful for building code. The following sections will guide you through all the details.

C ++ Note

Nested C ++ classes. Nested classes included within the scope of the enclosing class. Here is a typical example: objectlist defines a class used to store a link, and a position to define the iterator class.

class LinkedList
{ 
public:
    class Iterator // a nested class
    { 
    public:
        void insert(int x);
        int erase();
        . . .
    private:
        Link* current;
        LinkedList* owner;
    };
    . . .
private:
    Link* head;
    Link* tail;
};

Similar to the nested class in Java within the class. However, Java inner classes have an additional feature to make them richer and more useful than nested classes in C ++. Objects from within the classes have implicit references to instances of it external object. With this pointer, it can access the total status of the external object. For example, in Java, the Iteratorclass does not point to it points LinkedListexplicit pointers.

In Java, statican internal pointer to the class does not have this addition. They are C ++, Java analogy nested class.

6.3.1 Access class internal object state

The syntax of inner classes is quite complicated. For this reason, we offer a simple but somewhat contrived example to demonstrate the use of inner classes. We reconstructed Timertestthe example and extract the TalkingClockclass. A sound clock consists of two parameters: a gap opening and closing flag or notification between beeps.

public class TalkingClock
{
    private int interval;
    private boolean beep;
    public TalkingClock(int interval, boolean beep) { . . . }
    public void start() { . . . }
    public class TimePrinter implements ActionListener
    // an inner class
    {
    	. . .
    }
}

Please note that TimePrinterclass is now in TalkingClockclass. This does not mean that each TalkingClockhas an TimePrinterinstance field. As you will see, TimePrinterthe object is the TalkingClockconstructor of the class.

The following is a more detailed TimePrinterclass. Please note that the actionPerformedmethod checks before beeps beepflag.

public class TimePrinter implements ActionListener
{
    public void actionPerformed(ActionEvent event)
    {
        System.out.println("At the tone, the time is "
        	+ Instant.ofEpochMilli(event.getWhen()));
        if (beep) Toolkit.getDefaultToolkit().beep();
    }
}

Amazing thing happened. TimePrinterNo class named beepinstance variables or fields. In contrast, beepit is referring to the creation TimePrinterof TalkingClockthe object field. As you can see, the internal class methods can access their own data fields and create data fields its external objects.

To achieve this, the object of an inner class always get to create its implicit reference object (see Figure 6.3).

Figure 6.3 having an internal class object reference to an external class object.

This reference is not visible in the definition of internal class. However, in order to illustrate this concept, let's call it an external object. Then, the actionPerformedmethod is equivalent to the following:

public void actionPerformed(ActionEvent event)
{
    System.out.println("At the tone, the time is "
    	+ Instant.ofEpochMilli(event.getWhen()));
    if (outer.beep) Toolkit.getDefaultToolkit().beep();
}

External reference type provided constructor. The compiler modifies all internal class constructor, a parameter is added as an external reference type. TimePrinterClass does not define a constructor; therefore, no compiler synthesized a constructor parameter, generate the following code:

public TimePrinter(TalkingClock clock) // automatically generated code
{
	outer = clock;
}

Please note that outernot a Java keyword. We only use it to illustrate the internal mechanism of a class involved.

When the startconstructor TimePrinterobject, the compiler will thisreference clock transmitted to the current session to the constructor:

var listener = new TimePrinter(this); // parameter automatically added

Listing 6.7 shows the complete test procedure within the class. Look at access control. If the TimePrinterclass is a regular class, it will need TalkingClockpublic access to the class beepflag. Inner classes is an improvement. It is only required to provide access to another class of interest.

note

We can TimePrinterdeclare that class private. Then, the only TalkingClockmethod to construct TimePrinterobjects. Only inner classes can be private. General class always has a bag or public access.

清单6.7 innerClass/InnerClassTest.java

package innerClass;

import java.awt.*;
import java.awt.event.*;
import java.time.*;

import javax.swing.*;

/**
 * This program demonstrates the use of inner classes.
 * @version 1.11 2017-12-14
 * @author Cay Horstmann
 */
public class InnerClassTest
{
   public static void main(String[] args)
   {
      var clock = new TalkingClock(1000, true);
      clock.start();

      // keep program running until the user selects "OK"
      JOptionPane.showMessageDialog(null, "Quit program?");
      System.exit(0);
   }
}

/**
 * A clock that prints the time in regular intervals.
 */
class TalkingClock
{
   private int interval;
   private boolean beep;

   /**
    * Constructs a talking clock
    * @param interval the interval between messages (in milliseconds)
    * @param beep true if the clock should beep
    */
   public TalkingClock(int interval, boolean beep)
   {
      this.interval = interval;
      this.beep = beep;
   }

   /**
    * Starts the clock.
    */
   public void start()
   {
      var listener = new TimePrinter();
      var timer = new Timer(interval, listener);
      timer.start();
   }

   public class TimePrinter implements ActionListener
   {
      public void actionPerformed(ActionEvent event)
      {
         System.out.println("At the tone, the time is " 
            + Instant.ofEpochMilli(event.getWhen()));
         if (beep) Toolkit.getDefaultToolkit().beep();
      }
   }
}

Special inner class syntax rules 6.3.2

In the previous section, we have an in-house by calling the class outerto explain its outer class reference. In fact, the external reference the correct syntax is more complicated. expression

OuterClass.this

It represents an external class references. For example, the TimePrinterinternal class actionPerformedmethod written as

public void actionPerformed(ActionEvent event)
{
    . . .
    if (TalkingClock.this.beep) Toolkit.getDefaultToolkit().beep();
}

Instead, you can use the syntax to write more explicit internal object constructor.

outerObject.new InnerClass(construction parameters)
ActionListener listener = this.new TimePrinter();

Here, new structure of TimePrinterexternal object references are set to create a method of the class object internal thisreference. This is the most common situation. As thisusual, . Qualifier is redundant. However, by explicitly naming the external reference type to another object. For example, since TimePrintera common internal class, it can be any sound a clock configured TimePrinter:

var jabberer = new TalkingClock(1000, true);
TalkingClock.TimePrinter listener = jabberer.new TimePrinter();

Please note that you will be called an inner class

OuterClass.InnerClass

When it occurs outside the range of the external type.

note

Any internal static fields declared in the class must be final field, and initialized with compile-time constant. If the field is not constant, it may not be unique.

Inner classes can not have statica method. Java language specification gives no reason for this restriction. Static methods may be allowed to access only the static fields and methods of the enclosing class. Obviously, the complexity of the language designers believe that outweigh the benefits.

6.3.3 inner classes useful? Really necessary? Safety?

When the Java language in the inner class is added to Java 1.1, many programmers believe they are one of the main new features of the Java language embodies simpler than C ++. Admittedly, inner class syntax is very complex. (As we study anonymous inner classes later in this chapter, it becomes more complicated.) How do inner classes and other language features (such as access control and security) interaction is not obvious.

By adding features an elegant and interesting rather than needed, Java began a path of destruction to destroy so much of it in other languages?

Although we will not try to fully answer this question, but it is worth noting that class is an internal compiler phenomenon, rather than a virtual machine. Inner classes are converted to regular class files, separated by external and internal class name with a $ (dollar sign), and a virtual machine without any special knowledge of them.

For example, TalkingClockthe class of TimePrinterthe class is converted into TalkingClock$TimePrinter.classclass files. To see this at work, try the following experiment: Run Chapter 5 of the ReflectionTestprogram and give it a class TalkingClock$TimePrinterto reflect. Or, simply use the javaputility:

javap -private ClassName

note

If you use UNIX, please keep in mind when providing an escape $ character class names on the command line. In other words, run ReflectionTestor javapuse the program

java reflection.ReflectionTest innerClass.TalkingClock\$TimePrinter

or

javap -private innerClass.TalkingClock\$TimePrinter

You will get the following printout:

public class innerClass.TalkingClock$TimePrinter
	implements java.awt.event.ActionListener
{
    final innerClass.TalkingClock this$0;
    public innerClass.TalkingClock$TimePrinter(innerClass.TalkingCloc
    public void actionPerformed(java.awt.event.ActionEvent);
}

You can clearly see the compiler to generate an additional instance field, this$0used to refer to the outer class. ( this$0The name is synthesized by the compiler, you can not refer to it in code.) You can also see the constructor TalkingClockparameters.

If the compiler can automatically perform this conversion, you can not simply manually programming the same mechanism it? Let's give it a try. We will TimePrinterbecome a regular class, but not in the TalkingClockclass inside. In the constructor TimePrinterwhen the object, we will create its reference to the object passed to it.

class TalkingClock
{
    . . .
    public void start()
    {
        var listener = new TimePrinter(this);
        var timer = new Timer(interval, listener);
        timer.start();
    }
}
class TimePrinter implements ActionListener
{
    private TalkingClock outer;
    . . .
    public TimePrinter(TalkingClock clock)
    {
    	outer = clock;
    }
}

Now let's look at actionPerformedthe method. It needs to access outer.beep.

if (outer.beep) . . . // ERROR

Here we encounter a problem. Inner class can access private data outside class, but outside of TimePrinterclass can not.

Therefore, inner classes really more powerful than regular classes because they have more access.

If inner classes are converted to regular classes with funny names, then you might want to know how to gain access to the interior of these classes are added - the virtual machine they know nothing about. To solve this mystery, let us re-use ReflectionTestprogram monitors TalkingClockcategories:

class TalkingClock
{
    private int interval;
    private boolean beep;
    public TalkingClock(int, boolean);
    static boolean access$0(TalkingClock);
    public void start();
}

Note that the compiler added to the outer class static access$0method. It returns as an object passed as a parameter of the beepfield. (Method name may be slightly different, such as access$000, depending on the compiler.)

Internal class method calls this method. statement

if (beep)

In the TimePrinterclass actionPerformedmethods, effectively following call:

if (TalkingClock.access$0(outer))

This is a security risk? of course. For others, call access$0methods to read private beepfields is very easy to do. Of course, access$0not a legal name of the Java method. But hackers are familiar with the structure of the class file can be easily generated using a virtual machine instruction class file to invoke the method, for example, using a hex editor. Since the private packet access method, it is necessary to attack the same code in the package by the attack class.

All in all, if a private inner class to access a data field, you can add to the outside class package to access the data fields other classes, but to do this requires skill and determination. Programmer can not accidentally get access but must intentionally build or modify the class file for this purpose.

note

Synthesis and methods of construction will be very complicated. (If you are squeamish, skip this note.) Suppose we would TimePrinterbe converted to a private inner class. The virtual machine is not a private class, so the compiler will generate the next best thing, a package of classes and access to a private constructor has:

private TalkingClock$TimePrinter(TalkingClock);

Of course, no one can call the constructor, it is the second constructor has package access:

TalkingClock$TimePrinter(TalkingClock, TalkingClock$1);

This is the first one. TalkingClock$1Synthetic only to distinguish this constructor and other constructors.

The compiler TalkingClockclass startconstructor method call is converted

new TalkingClock$TimePrinter(this, null)

6.3.4 partial inner class

If you look closely TalkingClockthe code example, you will find only need to type TimePrinterthe name of the time: When you startcreate objects of that type of approach.

In this case, the class may be locally defined in a single process.

public void start()
{
    class TimePrinter implements ActionListener
    {
        public void actionPerformed(ActionEvent event)
        {
            System.out.println("At the tone, the time is "
            	+ Instant.ofEpochMilli(event.getWhen()));
            if (beep) Toolkit.getDefaultToolkit().beep();
        }
    } 
    var listener = new TimePrinter();
    var timer = new Timer(interval, listener);
    timer.start();
}

Never use local class access specifiers (i.e., public or private) statement. Their scope is always limited to declare their blocks.

Local class has a big advantage: they are completely hidden in the outside world, even TalkingClockother code in the class can not access them. In addition to startaddition, there is no way to understand TimePrinterclass.

6.3.5 Access variable from outside the method

Local analogy other internal class has another advantage. They can not only access to the outer class field, even access local variables! However, these local variables must be valid finalvariables. In other words, once assigned, they may never change.

This is a typical example. Let us intervaland beepparameters from the TalkingClockmove constructor startmethod.

public void start(int interval, boolean beep)
{
    class TimePrinter implements ActionListener
    {
        public void actionPerformed(ActionEvent event)
        {
            System.out.println("At the tone, the time is "
            	+ Instant.ofEpochMilli(event.getWhen()));
            if (beep) Toolkit.getDefaultToolkit().beep();
        }
    }
    var listener = new TimePrinter();
    var timer = new Timer(interval, listener);
    timer.start();
}

Note that the TalkingClockclass is no longer a need to store beepinstance field. It only reference startmethod beepparameter variables.

Perhaps this is not surprising. This line

if (beep) ...

After all, it is in the final startprocess, so why it can not access the beepvalue of the variable it?

To understand why there is a delicate question, let us consider more carefully control the flow.

  1. startMethod is called
  2. Object variable listenerby calling an internal class TimePrinterconstructor initialization.
  3. listenerReference passed to Timerthe constructor, the timer starts, startthe method exits. In this case, startthe method of beepparameter variables no longer exist.
  4. A second later, actionPerformeda method to perform `if (beep) ...

For actionPerformedthe code working method, TimePrinterthe class must beepbe the value of the parameter before the disappearance of beepreplication field startlocal variable method. This is indeed what happened. In our example, the compiler synthesis named local inner class TalkingClock$1TimePrinter. If you re-use ReflectionTestprogram monitors TalkingClock$1TimePrintercategory, you get the following output:

class TalkingClock$1TimePrinter
{
    TalkingClock$1TimePrinter(TalkingClock, boolean);
    public void actionPerformed(java.awt.event.ActionEvent);
    final boolean val$beep;
    final TalkingClock this$0;
}

Please note that the constructor booleanparameters and val$beepinstance variables. When creating an object, the value beepis passed to the constructor, and stored in the val$beepfield. The compiler detect access to local variables, instance field generated matches for each variable, and local variable to the copy constructor, to initialize instance fields.

6.3.6 anonymous inner classes

When using local inner classes, you can usually go further. If you want to generate a single object class, the class name need not even. Such a class is called an anonymous inner classes.

public void start(int interval, boolean beep)
{
    var listener = new ActionListener()
        {
            public void actionPerformed(ActionEvent event)
            {
                System.out.println("At the tone, the time is "
                    + Instant.ofEpochMilli(event.getWhen()));
                if (beep) Toolkit.getDefaultToolkit().beep();
            }
        };
    var timer = new Timer(interval, listener);
    timer.start();
}

This syntax is indeed very mysterious. This means: create an implementation ActionListenernew object interface of the class, where the desired actionPerformedmethod is a method defined within the braces.

In general, the syntax is

new SuperType(construction parameters)
    {
    	inner class methods and data
    }

Here, an interface may be a parent type, for example ActionListener; then, the internal class that implements the interface. Supertype may also be a category; then, the internal class extension class.

Anonymous inner classes can not have a constructor, because the name of the constructor must be the same name of the class, and the class has no name. In contrast, the configuration parameters are provided to the constructor of the superclass. In particular, whenever an internal class that implements the interface, it can not have any configuration parameters. However, you must provide a set of parentheses, such as

new InterfaceType()
    {
    	methods and data
    }

The difference between the structure of the object's constructor and anonymous inner class, you must carefully review the new object class extensions of the class.

var queen = new Person("Mary");
	// a Person object
var count = new Person("Dracula") { . . . };
	// an object of an inner class extending Person

Right parenthesis followed by a left brace If the constructor parameter list, an anonymous inner class will be defined.

note

Even if not anonymous class constructors, object initialization block may be provided:

var count = new Person("Dracula")
    {
        { initialization }
        . . .
    };

Listing 6.8 contains the complete source code for the program Talking Clock with anonymous inner classes of. If you leave this program with a list of 6.7 to compare, you will see in this case, the anonymous inner class solutions is much shorter, and hope through some practice, it can be easily understood.

Over the years, Java programmers usually implement event listeners and other callbacks use anonymous inner classes. Now, it is best to use a lambda expression. For example, a lambda expression can be used more simply as the preparation method of start beginning of this section:

public void start(int interval, boolean beep)
{
    var timer = new Timer(interval, event -> {
        System.out.println("At the tone, the time is "
        	+ Instant.ofEpochMilli(event.getWhen()));
        if (beep) Toolkit.getDefaultToolkit().beep();
    });
    timer.start();
}

note

The following initialization technique called double brackets, which uses an internal class syntax. Suppose you want to list an array configuration and passed to a method:

var friends = new ArrayList<String>();
friends.add("Harry");
friends.add("Tony");
invite(friends);

If you no longer need the array list, the best anonymous. But how do you add elements? Methods as below:

invite(new ArrayList<String>() {
	{ 
		add("Harry"); 
		add("Tony"); 
	}
});

Note that curly brackets. External braces constitute an anonymous subclass of arraylist. Internal braces is an object initialization block (see chapter 4).

In practice, this technique rarely useful. More likely, invite willing to accept any method List <String>, you can simply pass List.of("harry", "tony").

Be careful

Make an anonymous subclass is often convenient, it and its superclass is almost, but not exactly the same. But you need to be careful using the equals method. In Chapter 5, we recommend that you use the equals method test

if (getClass() != other.getClass()) return false;

Anonymous subclass will not pass this test.

prompt

When you generate a log or debug messages, you usually want to include the name of the current class, for example,

System.err.println("Something awful happened in " + getClass());

But in a static method failed. After all, getClasscalls, calls this.getClass(), and no static methods this. Please use the following expression:

new Object(){}.getClass().getEnclosingClass() // gets class of static method

Here, new Object() {}generating the anonymous anonymous object subclass of Object, getEnclosingClassacquiring its closed type, i.e. comprising a class static method.

清单6.8 anonymousInnerClass/AnonymousInnerClassTest.java

package anonymousInnerClass;

import java.awt.*;
import java.awt.event.*;
import java.time.*;

import javax.swing.*;

/**
 * This program demonstrates anonymous inner classes.
 * @version 1.12 2017-12-14
 * @author Cay Horstmann
 */
public class AnonymousInnerClassTest
{
   public static void main(String[] args)
   {
      var clock = new TalkingClock();
      clock.start(1000, true);

      // keep program running until the user selects "OK"
      JOptionPane.showMessageDialog(null, "Quit program?");
      System.exit(0);
   }
}

/**
 * A clock that prints the time in regular intervals.
 */
class TalkingClock
{
   /**
    * Starts the clock.
    * @param interval the interval between messages (in milliseconds)
    * @param beep true if the clock should beep
    */
   public void start(int interval, boolean beep)
   {
      var listener = new ActionListener()
         {
            public void actionPerformed(ActionEvent event)
            {
               System.out.println("At the tone, the time is " 
                  + Instant.ofEpochMilli(event.getWhen()));
               if (beep) Toolkit.getDefaultToolkit().beep();
            }
         };
      var timer = new Timer(interval, listener);
      timer.start();
   }
}

6.3.7 static inner classes

Sometimes, you may want to use an inner class to hide one class inside another class, but do not need the inner class has a reference to the outer class object. It can suppress the generation of the declaration referenced by the inner class static.

Here is a typical example of how you want to do this where it is. Consider computing the minimum and maximum array of tasks. Of course, you can write a method to calculate the minimum value, another method to calculate the maximum. When you call these two methods, the array will be traversed twice. Traversing the array once only be more efficient, while calculating the minimum and maximum.

double min = Double.POSITIVE_INFINITY;
double max = Double.NEGATIVE_INFINITY;
for (double v : values)
{
    if (min > v) min = v;
    if (max < v) max = v;
}

However, this method must return two numbers. We can kind of two values by defining a contained Pairto accomplish this:

class Pair
{
    private double first;
    private double second;
    public Pair(double f, double s)
    {
        first = f;
        second = s;
    }
    public double getFirst() { return first; }
    public double getSecond() { return second; }
}

Then the minmaxmethod may return type of Pairthe object.

class ArrayAlg
{
    public static Pair minmax(double[] values)
    {
        . . .
        return new Pair(min, max);
    }
}

The caller using the method getFirstand the getSecondmethod to receive the answer:

Pair p = ArrayAlg.minmax(d);
System.out.println("min = " + p.getFirst());
System.out.println("max = " + p.getSecond());

Of course, the name Pairis a very common name, in a large-scale project, it is likely that other programmers have the same good idea, but it creates a Pair class contains a pair of strings. We can by ArrayAlgmanipulation Pairto solve this potential name conflicts become public within the class. Then this class will be exposed as ArrayAlg.Pair:

ArrayAlg.Pair p = ArrayAlg.minmax(d);

However, different internal class used in the previous example, we do not want Pairany other object in the object referenced. Internal class can be declared staticto suppress the reference:

class ArrayAlg
{
    public static class Pair
    {
    	. . .
    }
    . . .
}

Of course, only the inner class can be declared static. Internal static type and any other internal classes are identical, but the object is not static inner class reference to its external generated class object. In our example, you must use a static inner classes, because the internal structure of the class object is inside a static method:

public static Pair minmax(double[] d)
{
    . . .
    return new Pair(min, max);
}

If the Pairclass is not declared static, the compiler will complain that there is no ArrayAlgtype of implicit objects that can be used to initialize the internal class object.

note

As long as the internal class does not need to access external object, use the static inner classes. Some programmers use the term to describe a nested class static inner classes.

note

Conventional internal class, the class may have a static internal static fields and methods.

note

In the inner class declared inside the interface are automatically static and the public.

6.9 Listing contains ArrayAlgclasses and nested Paircomplete source code for the class.

清单6.9 staticInnerClass/StaticInnerClassTest.java

package staticInnerClass;

/**
 * This program demonstrates the use of static inner classes.
 * @version 1.02 2015-05-12
 * @author Cay Horstmann
 */
public class StaticInnerClassTest
{
   public static void main(String[] args)
   {
      var values = new double[20];
      for (int i = 0; i < values.length; i++)
         values[i] = 100 * Math.random();
      ArrayAlg.Pair p = ArrayAlg.minmax(values);
      System.out.println("min = " + p.getFirst());
      System.out.println("max = " + p.getSecond());
   }
}

class ArrayAlg
{
   /**
    * A pair of floating-point numbers
    */
   public static class Pair
   {
      private double first;
      private double second;

      /**
       * Constructs a pair from two floating-point numbers
       * @param f the first number
       * @param s the second number
       */
      public Pair(double f, double s)
      {
         first = f;
         second = s;
      }

      /**
       * Returns the first number of the pair
       * @return the first number
       */
      public double getFirst()
      {
         return first;
      }

      /**
       * Returns the second number of the pair
       * @return the second number
       */
      public double getSecond()
      {
         return second;
      }
   }

   /**
    * Computes both the minimum and the maximum of an array
    * @param values an array of floating-point numbers
    * @return a pair whose first element is the minimum and whose second element
    * is the maximum
    */
   public static Pair minmax(double[] values)
   {
      double min = Double.POSITIVE_INFINITY;
      double max = Double.NEGATIVE_INFINITY;
      for (double v : values)
      {
         if (min > v) min = v;
         if (max < v) max = v;
      }
      return new Pair(min, max);
   }
}

Guess you like

Origin blog.csdn.net/nbda1121440/article/details/91314722