[java] 深入理解内部类: inner-classes
[java] 深入理解内部类: inner-classes
[java] 深入理解内部类: inner-classes
Table of Contents
1 简介
内部类就是定义在其他类中的类, 那么为什么要具有这样的特性呢?
- 内部类能够访问该类外部类的所有成员变量, 以及所有方法, 包括私有的成员.
- 内部类将它的可见性进行了一定的隐藏, 使得同一个package中的其他类不能直接的对其进行访问.
下面将通过一个个案例来对内部类进行深入的解释, 欢迎看客的各种建议.
2 案例
一个小学教室, 数学老师在教小朋友们数数字, 老师说一个数, 如n, 那么小朋友就从1开始以n为间隔进行数数, 如1, 1+n, 1+n+n, …, 老师说停, 那么小朋友便停止数数 (这里假设小朋友的数数速度是均匀的, 1 number/s).
2.1 不使用内部类的实现
import java.awt.event.*;
import javax.swing.*; public class Demo {
public static void main(String[] args) {
// 为了示例的简洁
// 这里直接默认学生的名字为: xiao ming
// 数数的步长为10
Student s = new Student("xiao ming");
s.startCount(10); JOptionPane.showMessageDialog(null, "停止数数.");
System.exit(0);
}
} /**
学生类, 仅包含学生的名字信息
*/
class Student {
public Student(String name) {
this.name = name;
} public void startCount(int step) {
// 为了能够知道谁在计数
// 这里需要将学生的名字信息传递给计数类
ActionListener count = new Counting(step, name);
Timer t = new Timer(1000, count);
t.start();
} private String name;
} /**
用来完成每隔一定时间实施数数功能
*/
class Counting implements ActionListener {
public Counting(int step, String name) {
this.step = step;
this.name = name;
num = 1;
} // 进行数数
public void actionPerformed(ActionEvent event) {
System.out.println(name + " count: " + num);
num = num + step;
} private int step;
private int num;
private String name;
}
从上面的示例中, 我们发现, 如果使用外部类进行实现该功能, 那么该计数类将能够被包中其他的类使用, 降低了该功能的封装性. 这正好可以通过使用内部类的方法进行解决.
2.2 内部类的实现
import java.awt.event.*;
import javax.swing.*; public class Demo {
public static void main(String[] args) {
// 为了示例的简洁
// 这里直接默认学生的名字为: xiao ming
// 数数的步长为10
Student s = new Student("xiao ming");
s.startCount(10); JOptionPane.showMessageDialog(null, "停止数数.");
System.exit(0);
}
} /**
学生类, 仅包含学生的名字信息
*/
class Student {
public Student(String name) {
this.name = name;
} public void startCount(int step) {
// 为了能够知道谁在计数
// 这里需要将学生的名字信息传递给计数类
ActionListener count = new Counting(step);
Timer t = new Timer(1000, count);
t.start();
} private String name; /**
用来完成每隔一定时间实施数数功能
内部类中可以直接使用外部类中的成员变量 - name
*/
private class Counting implements ActionListener {
public Counting(int step) {
this.step = step;
num = 1;
} // 进行数数
public void actionPerformed(ActionEvent event) {
System.out.println(name + " count: " + num);
num = num + step;
} private int step;
private int num;
}
}
这样包内的其他类就不能直接调用Counting类了.
3 有趣的事情开始发生了
用外部类实现的示例编译后产生的类文件有:
Demo.class, Student.class, Counting.class
而内部类示例编译后产生的类文件为:
Demo.class, Student.class, Student$Counting.class
单单从编译后的结果来看, java虚拟机在具体类的载入以及实现过程中, 应该和"外部类实现","内部类实现"没有关系. 那么"Student$Counting.class"这个神奇的类中到底包含了什么呢?
下面就来揭示这个秘密吧, 采用的工具为java decompiler.
反编译后的结果如下, Student$Counting.class:
#studentcounting
Student.class:
#student
从Student$Counting.class中发现, 它的构造函数的参数列表中多了一个变量:
// before
public Counting(int paramInt) {
this.step = paramInt;
this.num = 1;
} // after
public Student$Counting(Student paramStudent, int paramInt) {
// ...
}
但是仍然没有可视化的给出, 到底将这个变量赋值给了谁? 尝试的寻找更合适的java反向编译的软件, 花了近2个多小时的时间, 没有找到合适的, 哎, 算了, 反正不是为了得到反向的所有的代码, 只是专注于类内部的成员变量以及成员函数, 就采用java.lang.reflect中的类实现了从一个class文件中提取类名, 成员变量以及函数的.
3.1 从类文件中提取成员函数, 变量以及构造函数
在reflect库中包含了一下三个类Field, Method, Constructor用来分别获得和成员变量, 成员函数, 以及构造函数相关的内容. 具体实现如下1:
import java.lang.reflect.*;
import javax.swing.*; public class ReflectionTest {
public static void main(String[] args) {
// 从命令行中输入待处理文件, 或是由用户输入
String name;
if (args.length > 0)
name = args[0];
else
name = JOptionPane.showInputDialog
("Class name (e.g. java.util.Date): "); try {
// 输出类名以及父类
Class cl = Class.forName(name);
Class supercl = cl.getSuperclass();
System.out.print("class " + name);
if (supercl != null && supercl != Object.class) {
System.out.print(" extends " + supercl.getName());
} System.out.print(" {\n");
printConstructors(cl);
System.out.println();
printMethods(cl);
System.out.println();
printFields(cl);
System.out.println("}");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} /**
输出一个类的所有构造函数
*/
public static void printConstructors(Class cl) {
Constructor[] constructors = cl.getDeclaredConstructors(); for (int i = 0; i < constructors.length; i++) {
Constructor c = constructors[i];
String name = c.getName();
System.out.print("\t" + Modifier.toString(c.getModifiers()));
System.out.print(" " + name + "("); // 输出参数类型
Class[] paramTypes = c.getParameterTypes();
for (int j = 0; j < paramTypes.length; j++) {
if (j > 0) System.out.print(", ");
System.out.print(paramTypes[j].getName());
}
System.out.println(");");
}
} /**
输出一个类的所有成员方法
*/
public static void printMethods(Class cl) {
Method[] methods = cl.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
Class retType = m.getReturnType();
String name = m.getName();
System.out.print("\t" + Modifier.toString(m.getModifiers()));
System.out.print(" " + retType.getName() + " " + name + "("); // 输出参数类型
Class[] paramTypes = m.getParameterTypes();
for (int j = 0; j < paramTypes.length; j++) {
if (j > 0) System.out.print(", ");
System.out.print(paramTypes[j].getName());
}
System.out.println(");");
}
} /**
输出所有的成员变量
*/
public static void printFields(Class cl) {
Field[] fields = cl.getDeclaredFields(); for (int i = 0; i < fields.length; i++) {
Field f = fields[i];
Class type = f.getType();
String name = f.getName();
System.out.print("\t" + Modifier.toString(f.getModifiers()));
System.out.println(" " + type.getName() + " " + name + ";");
}
}
}
3.2 解释为什么能够在内部类直接访问外部类的成员变量
运行:
javac ReflectionTest.java
java ReflectionTest 'Student$Counting'
输出为:
class Student$Counting {
public Student$Counting(Student, int); public void actionPerformed(java.awt.event.ActionEvent); private int step;
private int num;
final Student this$0;
}
然后再结合该类的反编译的结果图, 就可以发现通过在构造函数中增加Student变量, 并且赋值给了 final Student this$0
, 这就解释了 为什么在内部类中能够访问外部类的成员变量 , 但是又为什么在内部类中能够访问 外部类的私有变量 呢?
让我们来看一下Student$Counting中另一处神奇的地方.
System.out.println(Student.access$000(this.this$0) + " count: " + this.num);
可见, Student类中又多了一个成员函数, 但是在Java Decompiler软件的结果中并没有发现, 于是再次采用了ReflectionTest, 其结果为:
class Student {
public Student(java.lang.String); public void startCount(int);
static java.lang.String access$000(Student); private java.lang.String name;
}
可见编译后的Student中出现了一个新的静态方法, 我们可以很容易的猜测到该静态方法的实现类似如下:
static String access$000(Student s) {
return name;
}
编译器通过这样的方式实现了从一个类中访问另一个类的私有变量, 那么通过直接修改class文件, 使得获取或改变类中的私有变量成为了可能, 当然要进行实现需要更强大的能力了.
4 局部内部类实现
使用局部内部类在代码编写的过程中其他的类均失去了对它的调用能力, 此时Student如下:
/**
学生类, 仅包含学生的名字信息
*/
class Student {
public Student(String name) {
this.name = name;
} public void startCount(final int step) {
/**
用来完成每隔一定时间实施数数功能
局部内部类中可以直接使用外部类中的成员变量 - name
还可以直接调用final的局部变量
*/
class Counting implements ActionListener {
// 进行数数
public void actionPerformed(ActionEvent event) {
System.out.println(name + " count: " + num);
num = num + step;
} private int num = 1;
} // 为了能够知道谁在计数
// 这里需要将学生的名字信息传递给计数类
ActionListener count = new Counting();
Timer t = new Timer(1000, count);
t.start();
} private String name; }
此时编译后新生成的类有:
Demo.class Student$1Counting.class Student.class
继续使用ReflectionTest, 分别对后两个类文件进行处理, 结果如下:
// Student$1Counting.class
class Student$1Counting {
Student$1Counting(Student, int); public void actionPerformed(java.awt.event.ActionEvent); private int num;
final int val$step;
final Student this$0;
} // Student.class
class Student {
public Student(java.lang.String); public void startCount(int);
static java.lang.String access$000(Student); private java.lang.String name;
}
可见通过编译后, 局部变量和外部类, 都成为了内部类的成员变量. 局部变量的可用性, 使得整个程序更加的简单明了.
5 匿名的内部类
匿名内部类的实现也可以采用类似的方式实现.
匿名内部类的实现格式如下:
new SuperType(construction parameters) {
inner class methods and data
}
其实, 可以通过一下方式使得对匿名内部类有一个更清楚的了解:
Person count = new class extends Person("") {...}; // 该形式并不是真正的java格式,只是为了帮助理解
Person count = new Person("") {...}; // 这才是正确的格式
匿名内部类, 还有一个比较常用的方法就是利用初始化块对一个变量进行初始化:
private List<String> maleList = new ArrayList<String>() {
// 初始化块
{
add("Machael");
add("Scorfield");
add("Other");
}
};
想要更清楚的理解上述的初始化块, 可以继续看下面的示例:
class Person {
private String name; {
System.out.println("初始化块 in class Person");
} public Person() {
this.name = null;
} public Person(String name) {
setName(name);
} public void setName(String name) {
this.name = name;
} public String getName() {
return name;
}
} public class Demo {
public static void main(String[] args) {
Person p1 = new Person("xiao ming");
System.out.println("直接初始化: " + p1.getName()); System.out.println(); Person p2 = new Person() {
// 初始化块
{
System.out.println("初始化块 in 匿名类");
setName("xiao ming");
}
};
System.out.println("双括号初始化: " + p2.getName());
}
}
其结果为:
初始化块 in class Person
直接初始化: xiao ming 初始化块 in class Person
初始化块 in 匿名类
双括号初始化: xiao ming
Footnotes:
1 book Core Java, 下载地址: download
补充:
匿名类初始化与直接操作之间的比较, 可以参见 http://stackoverflow.com/questions/924285/efficiency-of-java-double-brace-initialization
Date: 2014-05-19 Mon
Author: Zhong Xiewei
Org version 7.8.11 with Emacs version 24
[java] 深入理解内部类: inner-classes的更多相关文章
- Java 深入理解内部类
摘自海子:Java内部类详解 深入理解内部类 1.为什么成员内部类可以无条件访问外部类的成员? 在此之前,我们已经讨论过了成员内部类可以无条件访问外部类的成员,那具体究竟是如何实现的呢?下面通过反编译 ...
- java 深入理解内部类以及之间的调用关系
什么是内部类 内部类是指在一个外部类的内部再定义一个类.内部类作为外部类的一个成员,并且依附于外部类而存在的.内部类可为静态,可用protected和private修饰(而外部类只能使用public和 ...
- Java抽象类和内部类
类(class) 类是相似对象中共同属性和方法的集合体 在面向对象中定义类,就是在描述事物,就是在定义属性(变量)和行为(方法).属性和行为共同成为类中的成员(成员变量和成员方法). 封装.继承和多态 ...
- Java基础(53):内部类(转)
java中的内部类总结 内部类不是很好理解,但说白了其实也就是一个类中还包含着另外一个类 如同一个人是由大脑.肢体.器官等身体结果组成,而内部类相当于其中的某个器官之一,例如心脏:它也有自己的属性和行 ...
- Effective Java通俗理解(持续更新)
这篇博客是Java经典书籍<Effective Java(第二版)>的读书笔记,此书共有78条关于编写高质量Java代码的建议,我会试着逐一对其进行更为通俗易懂地讲解,故此篇博客的更新大约 ...
- Java 中的内部类
前言 在第一次把Java 编程思想中的内部类这一章撸完后,有点印象.大概知道了什么时内部类,局部内部类,匿名内部类,嵌套内部类.随着时间的推移,自己慢慢的就忘记了,总感觉自己思考的东西不多,于是 看了 ...
- Effective Java通俗理解(上)
这篇博客是Java经典书籍<Effective Java(第二版)>的读书笔记,此书共有78条关于编写高质量Java代码的建议,我会试着逐一对其进行更为通俗易懂地讲解,故此篇博客的更新大约 ...
- Java Inner Class 内部类
内部类 Inner Class 一个内部类可以定义在另一个类里,可以定义在函数里,甚至可以作为一个表达式的一部分. Java中的内部类共分为四种: 静态内部类static inner class ( ...
- Java基础(五)--内部类
内部类简单来说就是把一个类的定义放到另一个类的定义内部 内部类分为:成员内部类.局部内部类.匿名内部类.静态内部类 成员内部类:最常见的内部类 public class Outter { privat ...
随机推荐
- 【SQLServer】“无法对数据库'XXX' 执行删除,因为它正用于复制”的解决方法
从今天起,把编程中遇到的所有问题都记录下来,以便今后参考,以及方便网友查阅,希望我的问题可以帮助到很多志同道合的人们,我也是受很多前辈的提点,一步一步走来,希望大家都不要吝啬,将自己遇到的问题记录下来 ...
- Think in 递归
网上写递归的文章可以用汗牛充栋来形容了,大多数都非常清晰而又细致的角度上讲解了递归的概念,原理等等.以前学生的时候,递归可以说一直是我的某种死穴,原理,细节我都懂,但是不管是在如何运用或者如何试试算法 ...
- 【一】Ubuntu14.04+Jekyll+Github Pages搭建静态博客
本系列有五篇:分别是 [一]Ubuntu14.04+Jekyll+Github Pages搭建静态博客:主要是安装方面 [二]jekyll 的使用 :主要是jekyll的配置 [三]Markdown+ ...
- 解读ASP.NET 5 & MVC6系列(7):依赖注入
在前面的章节(Middleware章节)中,我们提到了依赖注入功能(Dependency Injection),ASP.NET 5正式将依赖注入进行了全功能的实现,以便开发人员能够开发更具弹性的组件程 ...
- GIS规划应用——基于哈夫模型的GIS服务区分析
1. GIS服务区分析 区位因素是商业分析中一个至关重要的因素,因此在商店选址时,例行的服务区分析十分重要.服务区是指顾客分布的主要区域,在其范围内该店的商品销售量或服务营业额超过其竞争对手.对于现 ...
- EF:oracle的number类型映射为C#的boolean类型
一开始用下面的方法映射, Property(p => p.IsFixed).HasColumnName("IS_FIXED").HasColumnType("num ...
- LinuxThreads 和 NPTL
http://www.ibm.com/developerworks/cn/linux/l-threading.html Linux 线程模型的比较:LinuxThreads 和 NPTL 进行移植的开 ...
- 2013 duilib入门简明教程 -- 其他 (18)
一.超链接按钮 代码很简单,参见360Demo: <Button text="{u}{a}求助{/a}{/u}" showhtml="true& ...
- Laravel 5.3 请求处理管道详解
对于一个Web应用来说,在一个请求真正处理前,我们可能会对请求做各种各样的判断,然后才允许后续处理. 我们通常的做法: Script 01.php Script 02.php 优点:直观,容易理解 缺 ...
- 在UWP中页面滑动导航栏置顶
最近在研究掌上英雄联盟,主要是用来给自己看新闻,顺便copy个界面改一下段位装装逼,可是在我copy的时候发现这个东西 当你滑动到一定距离的时候导航栏会置顶不动,这个特性在微博和淘宝都有,我看了@ms ...