博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
(四) 内部类
阅读量:4049 次
发布时间:2019-05-25

本文共 7758 字,大约阅读时间需要 25 分钟。

内部类(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
 

 

转载地址:http://woyci.baihongyu.com/

你可能感兴趣的文章
新建动态web工程项目红叉报错,以及Could not publish server configuration for Tomcat v9.0 Server at localhost.
查看>>
机器学习SVM的车牌识别系统—计算机专业课程设计(毕业设计)
查看>>
leetcode 80. 删除有序数组中的重复项 II
查看>>
课程设计(毕业设计)—学生宿舍管理系统—计算机类专业
查看>>
毕业设计(课程设计)—SpringBoot网上订餐系统的设计与实现—计算机类专业课程设计(毕业设计)
查看>>
毕业设计(课程设计)—个人博客系统(微博)的设计与实现—计算机类专业课程设计(毕业设计)
查看>>
牛客(中兴捧月)—B-切绳子
查看>>
剑指Offer 13.机器人的运动范围——DFS和BFS
查看>>
Java中GUI编程总结—AWT中的Frame容器、panel面板、布局管理
查看>>
剑指offer12.矩阵中的路径—DFS+剪枝
查看>>
Java中GUI编程总结—事件监听、TextField监听、画笔、(鼠标、窗口、键盘)监听
查看>>
Java中GUI编程总结—Swing(窗口、面板、弹窗、标签、按钮、列表、文本框)
查看>>
Java中map容器分别根据键key和值value进行排序的总结
查看>>
剑指offer面试题16. 数值的整数次方——快速幂
查看>>
剑指 Offer 39. 数组中出现次数超过一半的数字——摩尔投票法
查看>>
python中SQLite3 数据库语句使用总结——增删改查
查看>>
Java网络编程总结
查看>>
leetcode 477. 汉明距离总和——超出时间限制
查看>>
基于SSM校园二手交易市场系统——课程设计(毕业设计)
查看>>
leetcode 1882.使用服务器处理任务——优先队列
查看>>