`

(四) 内部类

 
阅读更多

内部类(inner class)是定义在另一个类中的类。

使用内部类的主要原因:
(1)内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
(2)内部类可以对同一个包中的其它类隐藏起来。
(3)当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous)内部类比较便捷。


1.使用内部类访问对象状态
内部类既可以访问自身的属性,也可以访问创建它的外围对象的属性。
内部类的对象总有一个隐式引用,它指向了创建它的外部类对象。这个引用在内部类的定义中是不可见的。
只有内部类可以声明为似有的,这样就只有它的外部类对象才能够生成内部类对象。常规类只可以具有包可见性或共有可见性。

import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;

import javax.swing.JOptionPane;
import javax.swing.Timer;

public class InnerClassTest {
   
    public static void main(String[] args) {
        TalkingClock clock = new TalkingClock(1000, true);
        clock.start();
       
        JOptionPane.showMessageDialog(null, "Quit program?");
        System.exit(0);
    }
}

class TalkingClock{
   
    private int interval;
    private boolean beep;
   
    public TalkingClock(int interval, boolean beep){
        this.interval = interval;
        this.beep = beep;
    }
   
    public void start(){
        ActionListener listener = new TimePrinter();
        Timer t = new Timer(interval, listener);
        t.start();
    }
   
    private class TimePrinter implements ActionListener{
        @Override
        public void actionPerformed(ActionEvent e) {
            Date now = new Date();
            System.out.println("At the tone, the time is " + now);
            if(beep){
                Toolkit.getDefaultToolkit().beep();
            }
        }
    }
}
 

2.内部类的特殊语法规则

(1)内部类对外围类的引用表达式: OuterClass.this

    public void actionPerformed(ActionEvent e) {
        Date now = new Date();
        System.out.println("At the tone, the time is " + now);
        if(TalkingClock.this.beep){
            Toolkit.getDefaultToolkit().beep();
        }
    }

 
(3)反过来,可以采用下列语法格式更加明确的编写内部对象的构造器:
    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();

 或

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

 


(4)在外围类的作用域之外,可以这样引用内部类: OutClass.InnerClass(外部类名.内部类名)


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

(1)内部类是一种编译器现象,与虚拟机无关
编译器将会把内部类编译成用$(美元符号)分割外部类名与内部类名的常规类文件,而虚拟机则对此一无所知。
例如,在TalkingClock类的内部的TimePrinter类将被翻译成类文件TalkingClock$TimePrinter.class

通过反射,或通过命令(javap -private ClassName)可以看到编译器为了引用外围类,生成了一个附件的属性this$0(名字this$0是由编译器合成的,在自己编写的代码中不能够引用它),并在构造器中传入了外围类的参数。
例:通过javap命令查看内部类
\>javap -private TalkingClock$TimePrinter
Compiled from "InnerClassTest.java"
class TalkingClock$TimePrinter extends java.lang.Object implements java.awt.event.ActionListener{
    final TalkingClock this$0;
    TalkingClock$TimePrinter(TalkingClock);
    public void actionPerformed(java.awt.event.ActionEvent);
}


(2)因为内部类可以访问外围类的私有数据,比如私有属性,私有方法等访问特权,所以与常规类比较起来功能更加强大。

(3)内部类如何管理那些额外的访问特权
通过反射或javap来查看TalkingClock类
\>javap -private TalkingClock
Compiled from "InnerClassTest.java"
class TalkingClock extends java.lang.Object{
    private int interval;
    private boolean beep;
    public com.shaogq.inclass.innter.TalkingClock(int, boolean);
    public void start() throws java.lang.Exception;
    static boolean access$0(TalkingClock);
}
发现编译器在外围类添加静态方法access$0(如果内部类调用另一个属性,将再生成静态方法access$1,以此类推)将返回座位参数传递给它的属性beep。
内部类方法将会调用生成的这个静态方法
if(beep) 将会提高下列类调用的效率 if(access$0(outer));

(4)是否存在风险,因为任何人都可以通过调用access$0方法很容易地读取到私有属性beep,虽然access$0不是Java的合法方法名,但熟悉类文件结果的情况下,使用十六进制表一起可以创建一个虚拟机指令调用那个方法的类文件。由于隐藏地访问方法需要有用包可见性,所以攻击代码需要与被攻击类放到同一个包中。
总而言之,如果内部类访问了私有数据域,就有可能通过附加在外围类所在包中的其他类访问他们。但必须可以的构建或修改类文件才可能达到这个目的。


4. 局部内部类
在上例中,TimePrinter这个类名字只在start方法中构建这个类型的对象时使用了一次,当遇到这类情况时,可以在一个方法中定义局部类。

    public void start() throws Exception{
        class TimePrinter implements ActionListener{
            @Override
            public void actionPerformed(ActionEvent e) {
                Date now = new Date();
                System.out.println("At the tone, the time is " + now);
                if(TalkingClock.this.beep){
                    Toolkit.getDefaultToolkit().beep();
                }
            }
        }
        ActionListener listener = new TimePrinter();
        Timer t = new Timer(interval, listener);
        t.start();
    }

 局部类不能用 public 或 private 访问说明符进行声明。它的作用域被限定在声明这个局部类的快中。
局部类有一个优势,即对外部世界可以完全隐藏起来。即使TalkingClock类中的其他代码也不能访问它。除了start方法外,没有任何方法指导TimePrinter类的存在。


5. 由内部方法方位final变量
与其他内部类相比较,局部内部类还有一个优点。它们不仅能够访问包含它们的外部类,还可以访问局部变量。不过,那些局部变量必须被声明为final。
例:

    public void start() throws Exception{
        class TimePrinter implements ActionListener{
            @Override
            public void actionPerformed(ActionEvent e) {
                Date now = new Date();
                System.out.println("At the tone, the time is " + now);
                if(TalkingClock.this.beep){
                    Toolkit.getDefaultToolkit().beep();
                }
            }
        }
        ActionListener listener = new TimePrinter();
        Timer t = new Timer(interval, listener);
        t.start();
    }

 

上例中的控制流程
1)调用start方法。
2)调用内部类TimePrinter的构造器,以便初始化对象变量listener。
3)将listener引用传递给Timer构造器,定时器开始计时,start方法结束,此时,start方法的beep参数变量不复存在。
4) 然后,actionPerformed方法执行if(beep)...
为了能够让actionPerformed方法工作,TimePrinter类在beep域释放之前将beep域用start方法的局部变量进行备份。

在例子中,编译器为局部边内部类构造了名字TilkingClock$TimePrinter(也可能类似于TalkingClock$1TimePrinter这种名字)。查看TilkingClock$TimePrinter类,结果如下
:\>javap -private TalkingClock$1TimePrinter
Compiled from "TalkingClock.java"
class TalkingClock$1TimePrinter extends java.lang.Object implements java.awt.event.ActionListener{
    final innterclass.TalkingClock this$0;
    private final boolean val$beep;
    innterclass.TalkingClock$1TimePrinter(innterclass.TalkingClock, boolean);
    public void actionPerformed(java.awt.event.ActionEvent);
}
构造器的boolean参数和val$beep实例变量,当创建一个对象的时候,beep就会被传递给构造器,并存储在val$beep域中。编译器必须检测对局部变量的访问,为每一个变量建立相应的属性,并将局部变量拷贝到构造器中,以便将这些属性初始化为局部变量的副本。
从程序员的角度看,局部变量的访问非常容易。它减少了需要显示编写的属性,从而使内部类更加简单。

注意,局部类的方法只可以引用定义为final的局部变量,比如在上例中beep参数声明为final,对它进行初始化后不能够在进行修改。因此,就使得局部变量与在局部类内建立的拷贝一致。
在定义final变量的时候,不必进行初始化。定义时没有初始化的final变量通常称为空final(blank final)变量。

如果需要修改局部变量的值,可以通过使用一个长度为1的数组,数组变量被声明为final,但是这仅仅表示不可以让它引用另外一个数组,数组中的数据元素可以自由地变更。


6. 匿名内部类
(1)将局部内部类的使用再深入一步,只创建这个类的一个对象,而且不需要命名。这种类被称为匿名内部类(anonymous inner class)。

    public void start(int interval, final boolean beep){
        ActionListener listener = new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
                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定义在括号{}内。

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

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

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

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

    new InterFaceType(){
        methods and data
    }

 
(4)构造一个类的新对象与构造一个扩展了那个类的匿名内部类的对象之间的差别

    Person queen = new Person("Mary");
    Person count = new Person("Dracula"){...};

 如果构造参数的小括号跟一个大括号,正在定义的就是匿名内部类。
如果内部类的代码比较简单,只有几行简单的代码,匿名内部类就可以节省一些录入的时间。


7. 静态内部类
有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类的对象。为此,可以将内部类声明为static,以便取消产生的引用。
只有内部类可以声明为static,静态内部类对象中不需要引用任何其他的对象,静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有内部类完全一样。

声明在接口中的内部类自动成为static和public的。
DEMO:在下面的例子中,必须使用静态内部类,是由于内部类对象是在静态方法中构造的。如果没有将Pair类声明为static,那么编译器将会给出错误报告:没有可用的隐式ArrayAlg类型对象初始化内部类对象。

public class StaticInnerClassTest {
    public static void main(String[] args) {
        double[] d = new double[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.getMinValue());
        System.out.println("max = " + p.getMaxValue());
   
    }
}

class ArrayAlg{
    public static class Pair{
       
        private double minValue;
        private double maxValue;
       
        public Pair(double minValue, double maxValue){
            this.minValue = minValue;
            this.maxValue = maxValue;
        }
       
        public double getMinValue(){
            return minValue;
        }
       
        public double getMaxValue(){
            return maxValue;
        }
    }
   
    public static Pair minmax(double[] values){
        double min = Double.MAX_VALUE;
        double max = Double.MIN_NORMAL;
        for(double value : values){
            if(min > value){
                min = value;
            }
            if(max < value){
                max = value;
            }
        }
        return new Pair(min, max);
    }
}
 

 

分享到:
评论

相关推荐

    内部类分类及应用

    内部类可以分为四种:成员内部类、静态嵌套类、方法内部类和匿名内部类。每种内部类都有其特点和应用场景。 一、成员内部类 成员内部类是指定义在外部类中的内部类。它们可以访问外部类的所有成员变量和方法,无论...

    内部类 匿名内部类 内部接口 对比说明

    内部类有四种主要类型: 1. **成员内部类(实例内部类)**:它作为外部类的成员存在,与字段和方法并列。成员内部类可以访问外部类的实例变量,但不能有静态属性和方法(final的除外)。创建成员内部类的实例时,...

    Java的内部类讲解案例代码(成员内部类、局部内部类、匿名内部类、静态内部类、外部类访问四种内部类、其他类访问四种内部类...)

    - 不同类型的内部类(静态内部类、成员内部类、局部内部类和匿名内部类)的区别和用法 - 内部类的优缺点以及使用场景 这些目标将帮助你理解和掌握内部类的概念,并能够在适当的时候使用内部类来解决问题...

    java 内部类 局部内部类 匿名类 实例代码

    本篇文章将深入探讨Java中的四种内部类:实例内部类、局部内部类、匿名类和静态内部类,并通过实例代码进行详细解析。 1. **实例内部类**:这是最常见的内部类形式,它在外部类的实例方法或成员位置定义。实例内部...

    Java语法总结 - 内部类

    内部类可以分为四种:成员内部类、静态嵌套类、方法内部类和匿名内部类。 成员内部类 成员内部类是定义在外部类的成员变量中的一种内部类。它可以访问外部类的所有成员变量和方法,包括私有的变量和方法。成员内部...

    java内部类详解

    内部类主要分为四种类型:静态内部类、成员内部类、局部内部类和匿名内部类。 1. 静态内部类(Static Inner Class) 静态内部类与普通的类类似,只是它们定义在外部类中,并且前面带有 `static` 关键字。它们不会...

    java代码笔记2010-06-01:Java内部类 静态内部类 局部内部类 明明内部类;StringBuffer reverse的使用;

    内部类可以分为四种类型:静态内部类、成员内部类(非静态内部类)、局部内部类和匿名内部类。 1. **静态内部类**: 静态内部类与普通的成员内部类不同,它不持有对外部类的引用。因此,可以像其他静态成员一样,...

    Java 深入理解嵌套类和内部类

    四、在外部类中定义内部类 在外部类中定义内部类可以使得内部类访问外部类的所有成员变量和方法。例如,在上面的代码中,内部类 Inner 可以访问外部类的成员变量 outer_x。 五、内部类的使用注意事项 在使用内部...

    内部类的使用

    首先,内部类分为四种类型:成员内部类、局部内部类、匿名内部类和静态内部类。成员内部类就像其他成员变量一样,可以直接访问外部类的所有成员,包括私有成员。局部内部类只存在于某个方法内,它的作用范围更小,...

    Java内部类总结

    #### 四、内部类的构造器 内部类的构造器可以访问外部类的构造器,但如果内部类不是静态的,则需要调用`super()`来完成对父类构造器的调用。例如: ```java class a0 { class b0 { } } class vvv { class ff ...

    JAVA内部类总结

    接下来将逐一介绍这四种类型的内部类。 #### 二、非静态成员内部类 非静态成员内部类是最常见的一种内部类形式,它具有以下特点: 1. **访问权限**:非静态成员内部类可以访问外部类的所有成员,包括私有成员。 2...

    浅谈Java内部类的四个应用场景

    ### 浅谈Java内部类的四个应用场景 #### 一、引言 在Java语言中,内部类(Inner Class)作为一种特殊的存在,为开发者提供了更为灵活的面向对象编程方式。通过本文,我们将深入探讨Java内部类的四个典型应用场景,...

    静态内部类

    #### 四、静态内部类的应用 静态内部类主要用于以下情况: 1. **减少内存消耗**:当内部类不需要访问外部类的实例变量时,使用静态内部类可以避免为外部类创建不必要的实例,从而节省内存。 2. **提高代码可读性**...

    从零开始学JAVA第12章_内部类.ppt

    内部类可以分为非静态内部类、局部内部类、静态内部类和匿名内部类四种。 非静态内部类是指定义在另一个类的非静态成员中的类。非静态内部类可以访问外部类的成员变量和成员方法,同时也可以被外部类访问。在外部类...

    C++内部类详细分析

    ### C++内部类详细分析 #### 一、引言 面向对象编程(OOP)自诞生以来,便成为了软件工程领域的重要组成部分。C++作为一种广泛使用的编程语言,支持面向对象编程特性,包括多重继承。多重继承是指一个类可以从多个...

    控制器(内部类)

    内部类分为四种类型:成员内部类、局部内部类、匿名内部类和静态内部类。控制器通常会用到的是成员内部类或静态内部类,因为它们可以访问外部类的属性和方法,同时又具有一定的封装性。 1. **成员内部类**:这种...

    内部类代码

    内部类是Java编程语言中一个独特且强大的特性,它允许我们在一个类的内部定义另一个类。这样的设计使得内部类能够访问外部类的所有成员,包括私有成员,这为实现复杂的设计模式提供了便利。本篇文章将深入探讨内部类...

    内部类的相关知识-静态内部类,匿名内部类……

    #### 四、非静态内部类不能有静态声明 非静态内部类不能包含任何静态成员(如静态变量、静态方法等),因为它们依赖于外部类的实例。如果尝试在非静态内部类中声明静态成员,将会导致编译错误。 ```java public ...

Global site tag (gtag.js) - Google Analytics