Java核心技术卷I:基础知识(原书第8版):6.4 内部类

时间:2022-12-07 00:45:22

  铁文整理

6.4内部类

   内部类是定义在另一个类中的类。为什么需要使用内部类呢?其主要原因有以下三点:

  • 内部类方法可以访问该类定义所在作用域中的数据,包括私有的数据。

  • 内部类可以对同一个包中的其他类隐藏起来。

  • 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。

   我们将这个比较复杂的内容分几部分介绍。

  • 6.4.1节中,给出一个简单的内部类,它将访问外围类的实例域。

  • 6.4.2节中,给出内部类的特殊语法规则。

  • 6.4.3节中,领略一下内部类的内部,探讨一下如何将其转换成常规类。读者可以跳过这一节。

  • 6.4.4节中,讨论局部内部类,它可以访问作用域中的局部变量。

  • 6.4.5节中,介绍匿名内部类,说明用于实现回调的基本方法。

  • 最后在6.4.6节中,介绍如何将静态内部类嵌套在辅助类中。

   C++注释:C++有嵌套类。一个被嵌套的类包含在外围类的作用域内。下面是一个典型的例子,一个链表类定义了一个存储结点的类和一个定义迭代器位置的类。

dass LinkedList

class LinkedList

{

public:

   class Iterator// a nested class

   {

   };

private:

   class Link// a nested class

   {

   };

};

   嵌套是一种类之间的关系,而不是对象之间的关系。一个LinkedList对象并不包含Iterator类型或Link类型的子对象。

   嵌套类有两个好处:命名控制和访问控制。由于名字Iterator嵌套在LinkedList类的内部,所以在外部被命名为LinkedList:: Iterator,这样就不会与其他名为Iterator的类发生冲突。在Java中这个并不重要,因为Java包已经提供了相同的命名控制。需要注意的是,Link类位于LinkedList类的私有部分,因此,Link对其他的代码均不可见。鉴于此情况,可以将Link的数据域设计为公有的,它仍然是安全的。这些数据域只能被LinkedList类(具有访问这些数据域的合理需要)中的方法访问,而不会暴露给其他的代码。在Java中,只有内部类能够实现这样的控制。

   然而,Java内部类还有另外一个功能,这使得它比C++的嵌套类更加丰富,用途更加广泛。内部类的对象有一个隐式引用,它引用了实例化该内部对象的外围类对象。通过这个指针,可以访问外围类对象的全部状态。在本章后续内客中,我们将会看到有关这个Java机制的详细介绍。

   Java中,static内部类没有这种附加指针,这样的内部类与C++中的嵌套类很相似。

6.4.1使用内部类访问对象状态

   内部类的语法比较复杂。鉴于此情况,我们选择一个简单但不太实用的例子说明内部类的使用方式,下面将进一步分析TimerTest示例,并抽象出一个TalkingClock类。构造一个语音时钟时需要提供两个参数:发布通告的间隔和开关铃声的标志。

class TalkingClock {

   public TalkingClock(int interval, boolean beep) {

   }

 

   privateintinterval;

   privatebooleanbeep;

 

   publicclass TimePrinterimplements ActionListener {

   }

}

   需要注意,这里的TimePrinter类位于TalkingClock类内部。这并不意味着每个TalkingClock都有一个TimePrinter实例域。如前所示,TimePrinter对象是由TalkingClock类的方法构造。

   下面是TimePrinter类的详细内容。需要注意一点,actionPerformed方法在发出铃声之前检査了beep标志。

   privateclass TimePrinterimplements ActionListener {

       publicvoid actionPerformed(ActionEvent event) {

           Date now = new Date();

           System.out.println("At the tone, the time is " + now);

           if (beep)

               Toolkit.getDefaultToolkit().beep();

       }

   }

   令人惊讶的事情发生了。TimePrinter类没有实例域或者名为beep的变量,取而代之的是beep引用了创建TimePrinterTalkingClock对象的域。这是一种创新的想法。从传统意义上讲,一个方法可以引用调用这个方法的对象数据域。内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。

   为了能够运行这个程序,内部类的对象总有一个隐式引用,它指向了创建它的外部类对象。如图6-3所示。

   这个引用在内部类的定义中是不可见的。然而,为了说明这个概念,我们将外围类对象的引用称为outer。于是actionPerformed方法将等价于下列形式:

       publicvoid actionPerformed(ActionEvent event) {

           Date now = new Date();

           System.out.println("At the tone, the time is " + now);

           if (outer.beep)

               Toolkit.getDefaultToolkit().beep();

       }

   外围类的引用在构造器中设置。编译器修改了所有的内部类的构造器,添加一个外围类引用的参数。因为TimePrinter类没有定义构造器,所以编译器为这个类生成了一个默认的构造器,其代码如下所示:

       public TimePrinter(TalkingClock clock)// automatically generated code

       {

           outer = clock;

       }

   请再注意一下,outer不是Java的关键字,我们只是用它说明内部类中的机制。

   当在start中创建了TimePrinter对象后,编译器就会将this引用传递给当前的语音时钟的构造器:

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

   6-4给出了一个测试内部类的完整程序。下面我们再看一下访问控制。如果有一个TimePrinter类是一个常规类,它就需要通过TalkingClock类的公有方法访问beep标志,而使用内部类可以给予改进,即不必提供仅用于访问其他类的访问器。

   注释:TimePrinter类被声明为私有的,这样一来,只有TalkingClock的方法才能够生成TimePrinter对象。只有内部类可以是私有类,而常规类只可以具有包可见性,或公有可见性。

6-4 InnerClassTest.java

import java.awt.*;

import java.awt.event.*;

import java.util.*;

import javax.swing.*;

import javax.swing.Timer;

 

/**

 * This program demonstrates the use of inner classes.

 *

 *@version 1.10 2004-02-27

 *@author Cay Horstmann

 */

publicclass InnerClassTest {

   publicstaticvoid main(String[] args) {

       TalkingClock clock = new TalkingClock(1000,true);

       clock.start();

 

       // keep program running until user selects "Ok"

       JOptionPane.showMessageDialog(null,"Quit program?");

       System.exit(0);

   }

}

 

/**

 * A clock that prints the time in regular intervals.

 */

class TalkingClock {

   /**

    * 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.

    */

   publicvoid start() {

       ActionListener listener = new TimePrinter();

       Timer t = new Timer(interval, listener);

       t.start();

   }

 

   privateintinterval;

   privatebooleanbeep;

 

   publicclass TimePrinterimplements ActionListener {

       publicvoid actionPerformed(ActionEvent event) {

           Date now = new Date();

           System.out.println("At the tone, the time is " + now);

           if (beep)

               Toolkit.getDefaultToolkit().beep();

       }

   }

}

6.4.2内部类的特殊语法规则

   在上一节中,已经讲述了内部类有一个外围类的引用outer。事实上,使用外围类引用的正规语法还要复杂一些。表达式OuterClass.this表示外围类引用。例如,可以像下面这样编写TimePrinter内部类的actionPerformed方法:

       publicvoid actionPerformed(ActionEvent event) {

           if (TalkingClock.this.beep)

               Toolkit.getDefaultToolkit().beep();

       }

   反过来,可以采用下列语法格式更加明确地编写内部对象的构造器:

   outerObject.new InnerClass(construction parameters)

   例如,

       ActionListener listener = this.new TimePrinter();

   在这里,最新构造的TimePrinter对象的外围类引用被设置为创建内部类对象的方法中的this引用,这是一种很常见的情况。通常,this限定词是多余的。不过,可以通过显式地命名将外围类引用设置为其他的对象。例如,如果TimePrinter是一个公有内部类,对于任意的语音时钟都可以构造一个TimePrinter:

       TalkingClock jabberer = new TalkingClock(1000,true);

       TalkingClock.TimePrinter listener = jabberer.new TimePrinter();

   需要注意,在外围类的怍用域之外,可以这样引用内部类:

   OuterClass.InnerClass

6.4.3内部类是否有用、必要和安全

   当在Java 1.1Java语言中增加内部类时,很多程序员都认为这是一项很主要的新特性,但这却违背了Java要比C++更加简单的设计理念。内部类的语法很复杂(可以看到,稍后介绍的匿名内部类更加复杂)。它与访问控制和安全性等其他的语言特性的没有明显的关联。

   由于增加了一些看似优美有趣,实属没必要的特性,似乎Java也开始走上了许多语言饱受折磨的毁灭性道路上。

   我们并不打算就这个问题给予一个完整的答案。内部类是一种编译器现象,与虚拟机无关。编译器将会把内部类翻译成用$(美元符号)分隔外部类名与内部类名的常规类文件,而虚拟机则对此一无所知。

   例如,在TalkingClock类内部的TimePrinter类将被翻译成类文件TalkingClock$TimePrinter.class。为了能够看到执行的效果,可以做一下这个实验:运行第5章中的程序ReflectionTest,并将类TalkingClock$TimePrinter传递给它进行反射,也可以选择简单的使用javap,如下所示:

   javap -private ClassName

   注释:如果使用UNIX,并以命令行的方式提供类名,就需要记住将$字符进行转义。也就是说,应读按照下面这种格式或javap程序运行ReflectionTest程序:

   java ReflectionTest TalkingClock\$TimePrinter

   

   javap -private TalkingClock\$TimePrinter

   这时会看到下面的输出结果:

publicclass TalkingClock$TimePrirter

{

   public TalkingClock$TimePrirter(TalkingClock);

   publicvoid actioflPerformed(java.awt.event.ActiorEvent);

   final TalkingClock this$0;

}

   可以清楚地看到,编译器为了引用外围类,生成了一个附加的实例域this$0(名字this$0是由编译器合成的,在自己编写的代码中不能够引用它)。另外,还可以看到构造器的TalkingClock参数。

   如果编译器能够自动地进行转换,那么能不能自己编写程序实现这种机制呢?让我们试试看。将TimePrinter定义成一个常规类,并把它置于TalkingClock类的外部。在构造TimePrinter对象的时候,将创建该对象的this指针传递给它。

class TalkingClock {

   publicvoid start() {

       ActionListener listener = new TimePrinter(this);

       Timer t = new Timer(interval, listener);

       t.start();

   }

}

class TimePrinterimplements ActionListener {

   public TimePrinter(TalkingClock clock){

       outer = clock;

   }

 

   private TalkingClockouter;

}

   现在,看一下actionPerformed方法,它需要访问outer.beep

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

   这就遇到了一个问题,内部类可以访问外围类的私有数据,但这里的TimePrinter类则不行。

   可见,由于内部类拥有访问特权,所以与常规类比较起来功能更加强大。

   可能有人会好奇,既然内部类可以被翻译成名字很古怪的常规类(而虚拟机对此—点也不了解),内部类如何管理那些额外的访问特权呢?为了掲开这个谜团,让我们再次利用ReflectTest程序査看一下TalkingClock类:

class TalkingClock

{

   public TalkingClock(int,boolean);

   

   staticboolean access$0(TalkingClock);

   publicvoid start();

 

   privateintinterval;

   privatebooleanbeep;

}

   请注意编译器在外围类添加静态方法access$0。它将返回作为参数传递给它的对象域beep

   内部类方法将调用那个方法。在TimePrinter类的actionPerformed方法中编写语句:

           if(beep) ...

   将会提高下列调用的效率:

           if(access$0(outer)) ...

   这样做不是存在安全风险吗?这种担心是很有道理的。任何人都可以通过调用access$0方法很容易地读取到私有域beep。当然,access$0不是Java的合法方法名。但熟悉类文件结构的黑客可以使用十六进制编辑器轻松地创建一个用虚拟机指令调用那个方法的类文件。由于隐秘地访问方法需要拥有包可见性,所以攻击代码需要与被攻击类放在同一个包中。

   总而言之,如果内部类访问了私有数据域,就有可能通过附加在外围类所在包中的其他类访问它们,但做这些事情需要高超的技巧和极大的决心。程序员不可能无意之中就获得对类的访问权限,而必须刻意地构建或修改类文件才有可能达到这个目的。

   注释:合成构造器和方法是复杂令人费解的(如果过于注重细节,可以跳过这个注释)。假设将TimePrinter转换为一个内部类。在虚拟机中不存在私有类,因此编译器将会利用私有构造器生成一个包可见的类:

   private TalkingClock$TimePrinter(TalkingClock);

   当然,没有人可以调用这个构造器,因此,存在弟二个包可见构造器

   TalkingClock$TimePrinter(TalkingClock, TalkingClock$1);

   它将调用第一个构造器.

   编译器将TalkingClockstart方法中的构造器调用翻译为:

       new TalkingClock$TimePrinter(this,null);

6.4.4局部内部类

   如果仔细地阅读一下TaIkingClock示例的代码就会发现,TimePrinter这个类名字只在start方法中创建这个类型的对象时使用了一次。

   当遇到这类情况时,可以在方法中定义局部类。

   publicvoid start() {

 

       class TimePrinterimplements ActionListener {

           publicvoid actionPerformed(ActionEvent event) {

               Date now = new Date();

               System.out.println("At the tone, the time is " + now);

               if (beep)

                   Toolkit.getDefaultToolkit().beep();

           }

       }

 

       ActionListener listener = new TimePrinter();

       Timer t = new Timer(interval, listener);

       t.start();

   }

   局部类不能用publicprivate访问说明符进行声明。它的作用域被限定在声明这个局部类的块中。

   局部类有一个优势,即对外部世界可以完全地隐藏起来。即使TalkingClock类中的其他代码也不能访问它。除start方法之外,没有任何方法知道TimePrinter类的存在。

6.4.5由外部方法访问final变量

   与其他内部类相比较,局部类还有一个优点。它们不仅能够访问包含它们的外部类,还可以访问局部变量。不过,那些局部变量必须被声明为final下面是一个典型的示例。这里将TalkjingClock构造器的参数intervalbeep移至start方法中。

   publicvoid start(int interval, finalboolean beep) {

       class TimePrinterimplements ActionListener {

           publicvoid actionPerformed(ActionEvent event) {

               Date now = new Date();

               System.out.println("At the tone, the time is " + now);

               if (beep)

                   Toolkit.getDefaultToolkit().beep();

           }

       }

 

       ActionListener listener = new TimePrinter();

       Timer t = new Timer(interval, listener);

       t.start();

   }

   请注意,TalkingClock类不再需要存储实例变量beep了,它只是引用start方法中的beep参数变量。

   这看起来好像没什么值得大惊小怪的。程序行

               if (beep) ...

   毕竟在start方法内部,为什么不能访问beep变量的值呢?

   为了能够清楚地看到内部的问题,让我们仔细地考査一下控制流程。

  1. 调用start方法。

  2. 调用内部类TimePrinter的构造器,以便初始化对象变量listener

  3. listener引用传递给Timer构造器,定时器开始计时,start方法结束。此时,start方法的beep参数变量不复存在。

  4. 然后,actionPerformed方法执行if(beep)...

   为了能够让actionPerformed方法工作,TimePrinter类在beep域释放之前将beep域用start方法的局部变量进行备份。实际上也是这样做的。在我们列举的例子中,编译器为局部内部类构造了名字TalkingClock$TimerPrinter。如果再次运行ReflectionTest程序,査看TalkingClock$TimerPrinter类,就会看到下列结果:

class TalkingClock$TimePrinter {

   TalkingClock$TimePrinter(TalkingClock, boolean);

 

   publicvoid actionPerformed(java.awt.event.ActionEvent);

 

   finalboolean val$beep;

   final TalkingClock this$0;

}

   请注意构造器的boolean参数和val$beep实例变量。当创建一个对象的时候,beep就会被传递给构造器,并存储在val$beep域中。编译器必须检测对局部变量的访问,为每一个变量建立相应的数据域,并将局部变量拷贝到构造器中,以便将这些数据域初始化为局部变量的副本。

   从程序员的角度看,局部变量的访问非常容易。它减少了需要显式编写的实例域,从而使得内部类更加简单。

   前面曾经提到,局部类的方法只可以引用定义为final的局部变量。鉴于此情况,在列举的示例中,将beep参数声明为final,对它进行初始化后不能够再进行修改。因此,就使得局部变量与在局部类内建立的拷贝保持一致。

   注释:前面曾经将final变量作为常量使用,例如:

   publicstaticfinaldoubleSPEED_LIMIT = 55;

   final关键字可以应用于局部变量,实例变量和静态变量。在所有这些情况下,它们的含义都是:在创建这个变量之后,只能够为之赋值一次。此后,再也不能修改它的值了,这就是final

   不过,在定义final变量的时候,不必进行初始化。例如,当调用start方法时,final参数变量beep只能够在创建之后被初始化一次(如果这个方法被调用多次,那么每次调用都有一个新创建的beep参数)。可以看到在Talking$1TimePrinter内部类中的val$beep实例变量仅在内部类的构造器中被设置一次。定义时没有如始化的final变量通常被称为空final变量。

   有时,final限制显得并不太方便。例如,假设想更新在一个封闭作用域内的计数器。这里想要统计一下在排序过程中调用compareTo方法的次数。

       int counter = 0;

       Date[] dates = new Date[100];

       for (int i = 0; i < dates.length; i++)

           dates[i] = new Date() {

               publicint campareTo(Date other) {

                   counter++; // ERROR

                   returnsuper.campareTo(other);

               }

           };

 

       Arrays.sort(dates);

       System.out.pnntlr(counter +" comparison.");

   由于清楚地知道counter需要更新,所以不能将counter声明为final。由于Integer对象是不可变的,所以也不能用Integer代替它。补救的方法是使用一个长度为1的数组:

       finalint[] counter =newint[1];

       for (int i = 0; i < dates.length; i++)

           dates[i] = new Date() {

               publicint campareTo(Date other) {

                   counter[0]++; // ERROR

                   returnsuper.campareTo(other);

               }

           };

   (数组变量仍然被声明为final,但是这仅仅表示不可以让它引用另外一个数组。数组中的数据元素可以*地更改。)

   在内部类被首次提出时,原型编译器对内部类中修改的局部变量自动的进行转换。然而,有些程序员对于编译器在背后生成堆对象感到十分惧怕,而采用final限制代替。在未来Java语言版本中有可能会修改这种策略。

6.4.6匿名内部类

   将局部内部类的使用再深入一步。假如只创建这个类的一个对象,就不必命名了。这种类被称为匿名内部类。

   publicvoid start(int interval, finalboolean beep) {

       ActionListener listener = new ActionListener() {

           publicvoid actionPerformed(ActionEvent event) {

               Date now = new Date();

               System.out.println("At the tone, the time is " + now);

               if (beep)

                   Toolkit.getDefaultToolkit().beep();

           }

       };

       Timer t = new Timer(interval, listener);

       t.start();

   }

   这种语法确实有些难以理解。它的含义是:创建一个实现ActionListener接口的类的新对象,需要实现的方法actionPerformed定义在括号{}内。

   用于构造对象的任何参数都要被放在超类名后面的括号()内。通常的语法格式为:

   new SuperType(construction parameters)

   {

       inner class methods and data

   }

   其中,SuperType可以是ActionListener这样的接口,于是内部类就要实现这个接口。SuperType也可以是一个类,于是内部类就要扩展它。

   由于构造器的名字必须与类名相同,而匿名类没有类名,所以,匿名类不能有构造器。取而代之的是,将构造器参数传递给超类构造器。尤其是在内部类实现接口的时候,不能有任何构造参数。不仅如此,还要像下面这样提供一组括号:

   new InterfaceType()

   {

       methods and data

   }

   请仔细研究一下,看看构造一个类的新对象与构造一个扩展了那个类的匿名内部类的对象之间有什么差别。

       Person queen = new Person("Mary");// a Person object

       Person count = new Person("Dracular") {

       }; // an object of an inner class extending Person

   如果构造参数的闭圆括号跟一个开花括号,正在定义的就是匿名内部类。

   匿名内部类是一种好想法呢?还是一种让人迷惑不解的想法呢?也许两者兼有。如果内部类的代码比较短,例如,只有几行简单的代码,匿名内部类就可以节省一些录入的时间。但是节省这点时间却会让人陷入“混乱的Java代码竞赛”。

   6-5包含了用匿名内部类实现语音时钟程序的全部源代码。将这个程序与例6-4相比较就会发现使用匿名内部类的解决方案比较简短、更切实际、更易于理解。

6-5 AnonymousInnerClassTest.java

import java.awt.*;

import java.awt.event.*;

import java.util.*;

import javax.swing.*;

import javax.swing.Timer;

 

/**

 * This program demonstrates anonymous inner classes.

 *

 *@version 1.10 2004-02-27

 *@author Cay Horstmann

 */

publicclass AnonymousInnerClassTest {

   publicstaticvoid main(String[] args) {

       TalkingClock clock = new TalkingClock();

       clock.start(1000, true);

 

       // keep program running until 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

    */

   publicvoid start(int interval, finalboolean beep) {

       ActionListener listener = new ActionListener() {

           publicvoid actionPerformed(ActionEvent event) {

               Date now = new Date();

               System.out.println("At the tone, the time is " + now);

               if (beep)

                   Toolkit.getDefaultToolkit().beep();

           }

       };

       Timer t = new Timer(interval, listener);

       t.start();

   }

}

6.4.7静态内部类

   有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为static,以便取消产生的引用。

   下面是一个使用静态内部类的典型例子。考虑一下计算数组中最小值和最大值的问题。当然,可以编写两个方法,一个方法用于计算最小值,另一个方法用于计算最大值,在调用这两个方法的时候,数组被遍历两次。如果只遍历数组一次,并能够同时计算出最小值和最大值,那么就可以大大地提高效率了。

       double min = Double.MAX_VALUE;

       double max = Double.MIN_VALUE;

       for (double v : values) {

           if (min > v)

               min = v;

           if (max < v)

               max = v;

       }

   然而,这个方法必须返回两个数值,为此,可以定义一个包含两个值的类Pair

   class Pair {

       public Pair(double f, double s) {

           first = f;

           second = s;

       }

 

       publicdouble getFirst() {

           returnfirst;

       }

 

       publicdouble getSecond() {

           returnsecond;

       }

 

       privatedoublefirst;

       privatedoublesecond;

   }

   minmax方法可以返回一个pair类型的对象。

class ArrayAlg {

   publicstatic Pair minmax(double[] values) {

       returnnew Pair(min, max);

   }

}

   这个方法的调用者可以使用getFirstgetSecond方法获得答案:

       Pair p = ArrayAlg.minmax(d);

       System.out.println("min = " + p.getFirst());

       System.out.println("max = " + p.getSecond());

   Pair是一个十分大众化的名字。在大型项目中,除了定义包含一对字符串的Pair类之外,其他程序员也很可能使用这个名字。这样就会产生名字冲突。解决这个问题的办法是将Pair定义为ArrayAlg的内部公有类,此后,通过ArrayAlg.Pair访问它:

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

   与前面例子中所使用的内部类不同,在Pair对象中不需要引用任何其他的对象,为此,可以将这个内部类声明为static

class ArrayAlg {

   publicstaticclass Pair {

   }

}

   当然,只有内部类可以声明为static。静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有内部类完全一样。在我们列举的示例中,必须使用静态内部类,这是由于内部类对象是在静态方法中构造的:

   publicstatic Pair minmax(double[] values) {

       returnnew Pair(min, max);

   }

   如果没有将Pair类声明为static,那么编译器将会给出错误报告:没有可用的隐式ArrayAlg类型对象初始化内部类对象。

   注释:在内部类不需要访问外围类对象的时候,应该使用静态内部类。有些程序员用嵌套类(nested class)表示静态内部类

   r注释:声明在接口中的内部类自动成为staticpublic

   6-6包含ArrayAlg类和嵌套的Pair类的全部源代码。

6-6 StaticInnerClassTest.java

/**

 * This program demonstrates the use of static inner classes.

 *

 *@version 1.01 2004-02-27

 *@author Cay Horstmann

 */

publicclass StaticInnerClassTest {

   publicstaticvoid main(String[] args) {

       double[] d =newdouble[20];

       for (int i = 0; i < d.length; i++)

           d[i] = 100 * Math.random();

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

       System.out.println("min = " + p.getFirst());

       System.out.println("max = " + p.getSecond());

   }

}

 

class ArrayAlg {

   /**

    * A pair of floating-point numbers

    */

   publicstaticclass Pair {

       /**

        * 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

        */

       publicdouble getFirst() {

           returnfirst;

       }

 

       /**

        * Returns the second number of the pair

        *

        * @return the second number

        */

       publicdouble getSecond() {

           returnsecond;

       }

 

       privatedoublefirst;

       privatedoublesecond;

   }

 

   /**

    * 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

    */

   publicstatic Pair minmax(double[] values) {

       double min = Double.MAX_VALUE;

       double max = Double.MIN_VALUE;

       for (double v : values) {

           if (min > v)

               min = v;

           if (max < v)

               max = v;

       }

       returnnew Pair(min, max);

   }

}