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

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

以下简单举例:

一个时钟需要两个参数:发布通告的时间间隔以及开关铃声的标志。

public 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 timer = new Timer(interval, listener);
timer.start();
} public class TimePrinter implements ActionListener{ @Override
public void actionPerformed(ActionEvent e) {
System.out.println("At the tone, the time is " + new Date());
if (beep) {
Toolkit.getDefaultToolkit().beep();
}
}
} public static void 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);
}
}

本代码主要想说明的是作为内部类的TimePrinter可以直接访问外部类TalkingClock的私有成员变量beep。

语法和用法大家很容易就学会了,那么内部类是如何实现直接访问其他类的私有成员变量呢?我们知道Java的私有成员变量只有类的实例本身能访问。

在最初学习Java语法的时候,是把内部类当做该类的一个属性去记忆和理解的,因此,自然可以访问私有变量。这个理解,现在看来是错误的。

内部类是一种编译器现象,与虚拟机无关。编译器将会把内部类翻译成用$分隔外部类名与内部类名的常规类文件,而虚拟机对此一无所知。

上述内部类被编译成TalkingClock$TimePrinter :

 javap -private TalkingClock\$TimePrinter.class
Compiled from "TalkingClock.java"
public class com.test.java.clazz.innerclass.TalkingClock$TimePrinter implements java.awt.event.ActionListener {
final com.test.java.clazz.innerclass.TalkingClock this$0;
public com.test.java.clazz.innerclass.TalkingClock$TimePrinter(com.test.java.clazz.innerclass.TalkingClock);
public void actionPerformed(java.awt.event.ActionEvent);
}

可以看到,多了一个成员变量 final com.test.java.clazz.innerclass.TalkingClock this$0;,以及构造函数。内部类将通过这种方式持有外部类。

内部类的一个特性是可以访问外部的私有成员变量,这又是如何做到的呢。

查看外部类:

javap -private TalkingClock.class
Compiled from "TalkingClock.java"
public class com.test.java.clazz.innerclass.TalkingClock {
private int interval;
private boolean beep;
public com.test.java.clazz.innerclass.TalkingClock(int, boolean);
public void start();
public static void main(java.lang.String[]);
static boolean access$000(com.test.java.clazz.innerclass.TalkingClock);
}

可以发现,编译器给添加了新的静态方法static boolean access$000(com.test.java.clazz.innerclass.TalkingClock);,显然,我们可以通过类名调用这个方法来获取私有变量,相当于给添加了getter方法暴露私有变量。

查看变异后的内部类的actionPerformed方法:

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

综上,内部类通过编译器修改代码,实现了对类的访问。如果黑客在同包名下调用外部类的access$000,则是可以访问到私有变量的,这算是一个危险的地方吧。

局部内部类

仔细观察示例代码,TimePrinter只在start方法里调用了,我们可以把这个类缩小为局部内部类,即在方法内部声明内部类。

局部内部类可以访问局部变量

我们将上述demo修改下:

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

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

上述demo,局部内部类TimePrinter会访问局部变量interval和beep。同理,依旧是将这两个变量转换程内部类的成员变量,实现值的传递。

匿名内部类

public class AnonymousInnerClassTest {

    @Data
static class Animal {
private String name; public void sayHi(){
System.out.println("Hi " + name);
}
} public static void main(String[] args) {
Animal animal = new Animal(){
@Override
public void sayHi() {
System.out.println("Wang");
}
};
animal.sayHi();
}
}

上述简单模拟一种Animal匿名类。编译后的结果为:

'AnonymousInnerClassTest$1.class'  'AnonymousInnerClassTest$Animal.class'   AnonymousInnerClassTest.class

编译器生成了3个编译类。

AnonymousInnerClassTest

public class AnonymousInnerClassTest {
public AnonymousInnerClassTest() {
} public static void main(String[] args) {
com.test.java.clazz.innerclass.AnonymousInnerClassTest.Animal animal = new 1();
animal.sayHi();
}
}

AnonymousInnerClassTest$1

import com.test.java.clazz.innerclass.AnonymousInnerClassTest.Animal;

final class AnonymousInnerClassTest$1 extends Animal {
AnonymousInnerClassTest$1() {
} public void sayHi() {
System.out.println("Wang");
}
}

AnonymousInnerClassTest$Animal

class AnonymousInnerClassTest$Animal {
private String name; public void sayHi() {
System.out.println("Hi " + this.name);
} public AnonymousInnerClassTest$Animal() {
} //getter setter equals toString
}

即编译器生成一个包访问权限的内部类AnonymousInnerClassTest$Animal, 对于其匿名子类,又生成了final的AnonymousInnerClassTest$1 ,如果还有第二个匿名类,则命名AnonymousInnerClassTest$2.

Java内部类是如何实现的的更多相关文章

  1. Java内部类final语义实现

    本文描述在java内部类中,经常会引用外部类的变量信息.但是这些变量信息是如何传递给内部类的,在表面上并没有相应的线索.本文从字节码层描述在内部类中是如何实现这些语义的. 本地临时变量 基本类型 fi ...

  2. Java内部类详解

    Java内部类详解 说起内部类这个词,想必很多人都不陌生,但是又会觉得不熟悉.原因是平时编写代码时可能用到的场景不多,用得最多的是在有事件监听的情况下,并且即使用到也很少去总结内部类的用法.今天我们就 ...

  3. 黑马----JAVA内部类

    黑马程序员:Java培训.Android培训.iOS培训..Net培训 黑马程序员--JAVA内部类 一.内部类分为显式内部类和匿名内部类. 二.显式内部类 1.即显式声明的内部类,它有类名. 2.显 ...

  4. java 内部类 *** 最爱那水货

    注: 转载于http://blog.csdn.net/jiangxinyu/article/details/8177326 Java语言允许在类中再定义类,这种在其它类内部定义的类就叫内部类.内部类又 ...

  5. java内部类和匿名内部类

    内部类即是包含在类里面的又一个类. java内部类分为: 成员内部类.静态嵌套类.方法内部类.匿名内部类 . 内部类的共性 (1).内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.clas ...

  6. Java内部类小程序(成员内部类,静态内部类,匿名内部类)

    /** * 测试java内部类(成员内部类,静态内部类,匿名内部类) * 局部内部类不常用,就不写了. * @package :java05 * @author shaobn * @Describe ...

  7. [转] Java内部类详解

    作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本博客中未标明转载的文章归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置 ...

  8. java内部类的作用分析

    提起Java内部类(Inner Class)可能很多人不太熟悉,实际上类似的概念在C++里也有,那就是嵌套类(Nested Class),关于这两者的区别与联系,在下文中会有对比.内部类从表面上看,就 ...

  9. 9)Java内部类(Inner Class)

      内部类:不可以有静态数据,静态方法或者又一个静态内部类      内部类的优点:隐藏类的细节,内部类可以声明为私有.内部类可以访问外部类的对象(包括private) 静态内部类:可以有静态数据,静 ...

  10. JAVA内部类(转)

    源出处:JAVA内部类 在java语言中,有一种类叫做内部类(inner class),也称为嵌入类(nested class),它是定义在其他类的内部.内部类作为其外部类的一个成员,与其他成员一样, ...

随机推荐

  1. LinuxShell——内嵌命令

    LinuxShell——内嵌命令 摘要:本文主要学习了Shell的常用内嵌命令. alias命令 alias命令可以为指定命令定义一个别名. 基本语法 查看所有别名: alias 设置别名: alia ...

  2. Python脚本1

    [轮子]P123. 求最大约数,并鉴别是否为素数

  3. PHP获得毫秒数

    因为前端需要写函数处理时间戳,比较麻烦,所以我们有的时候,需要接口传递毫秒数给前端. 下面可以通过这个函数来获得毫秒数 <?php function getMillisecond() { lis ...

  4. jQuery 的58种事件方法你都用过了吗

    jQuery 事件方法 事件方法触发或将函数附加到所选元素的事件处理程序. 下表列出了用于处理事件的所有jQuery方法. 方法 描述 bind() 在3.0版中已弃用. 请改用on()方法.将事件处 ...

  5. Delphi2007 在Win10 下运行报错 Assertion failure

    Delphi2007 原来安装在Win7 下 运行正常, 自从升级到Win10 ,新建工程运行然后关闭报错, 报错信息如下: ---------------------------bds.exe - ...

  6. FCC-学习笔记 Spinal Tap Case

    FCC-学习笔记   Spinal Tap Case 1>最近在学习和练习FCC的题目.这个真的比较的好,推荐给大家. 2>中文版的地址:https://www.freecodecamp. ...

  7. youtube视频在线下载

    youtube视频在线下载网站: https://www.clipconverter.cc/ youtube视频样例: https://www.youtube.com/watch?v=NMkgz0AR ...

  8. Hibernate基于注解实现自关联树形结构实现

    很久没用过Hibernate了,项目需求需要使用,并建立树形结构,在开发中遇到一些问题,在这里记录一下. 1.创建数据库表,主要是设置标志信息,不然插入数据库会报id不能插入null的错误. 2.创建 ...

  9. Linux终端执行shell脚本,提示权限不够

    在学习dubbo过程中,上传自己写的脚本,执行的时候提示“权限不够”,从网上了解到是因为没有为脚本赋权限 解决方法是使用chmod命令为shell脚本赋予权限 chmod 777 ./service- ...

  10. 201871010101-陈来弟《面向对象程序设计(Java)》第八周学习总结

    实验七 接口的定义与使用 第一部分:理论知识 一.接口.lambda和内部类:  Comparator与comparable接口: 1.comparable接口的方法是compareTo,只有一个参数 ...