java 静态内部类小总结
内部类是指在一个外部类的内部再定义一个类。内部类作为外部类的一个成员,并且依附于外部类而存在的。内部类可为静态,可用protected和private修饰(而外部类只能使用public和缺省的包访问权限)。内部类主要有以下几类:成员内部类、局部内部类、静态内部类、匿名内部类
为什么需要内部类?
典型的情况是,内部类继承自某个类或实现某个接口,内部类的代码操作创建其的外围类的对象。所以你可以认为内部类提供了某种进入其外围类的窗口。使用内部类最吸引人的原因是:
每 个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。如果没有内部类提供的可以继承多 个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实 现了“多重继承”。
静态内部类型(static nested type)
静态内部类型可以是class,interface,或者enum。而其他类型的内部类型只能是class。
静态内部类其实还是一个顶层类(源代码文件级别的类),他不依赖外部类,只不过它被封装在另一个class或者interface中,而不是直接定义在文件级别中。因此,它和一般的类静态成员很类似:
1、它不包含外部类当前对象引用this,因此不能直接访问外部类的实际成员,但可以使用外部类的static成员。
2、静态内部类作为一个静态成员,因此可以用访问权限修饰符:public . private .......等。用的最多一般是private
引用静态内部类: Wapper.Inner
3、不能在非静态内部类中再定义静态内部类。静态内部类可以无限深度的嵌套下去。
提升
内部类最终会被javac编译为独立的类,JVM看见的都是top-level类。
编译后的class文件形如:WrapperClass $ InnerStaticClass.class
下面是使用静态内部类简单实现链式栈数据结构的例子。
class LinkedSatck<E> { private static class Node<T> { public Node(Node<T> next, T data) {
this.next = next;
this.data = data;
} @SuppressWarnings("unused")
public Node() {
this(null, null);
} private Node<T> next;
private T data; public T getData() {
return data;
} public Node<T> getNext() {
return next;
} public boolean isEndNode() {
return (next == null);
} } public LinkedSatck() {
topNode = new Node<E>(null, null); // size =0;
} private int size = 0;
private Node<E> topNode = null; public void push(E e) { Node<E> newTopNode = new Node<E>(topNode, e);
++size;
topNode = newTopNode; } public E pop() {
if (topNode.isEndNode())
return null;
else {
E re = topNode.getData(); topNode = topNode.getNext(); --size;
return re; }
} public int size() {
return size;
} public boolean isEmpty() {
return size == 0;
// return topNode.isEnd();
} }
成员内部类(inner member class)
成员内部类最重要的特点就是它可以直接使用外部类的实例成员和static成员,即便是使用private修饰也是如此。
因为成员内部类总包含了一个外部类的当前对象引用 ,奇怪的名字 this$0,这个引用在成员内部类实例化时被外部类的当前对象引用this初始化。
大致实现如下:
class Outter
{
private class Inner
{
private final Outter this$0; //javac自动添加
Inner(Outter o) //javac自动添加
{
this.this$0 = o;
}
}
//使用成员内部类对象时发生:new Inner(Outter.this)
}
这也是为什么在创建一个成员内部类对象时,要先创建一个外部类对象的原因了。
和普通实例成员一样,成员内部里是属于外部类的对象的,那么,在成员内部类就理所当然可以直接使用外部类的其他实例成员以及static成员。
因为是实例成员,所以可以使用访问修饰符:public 、protected、private、和默认的包访问权限。
因为是实例成员,因此,在类的外部使用内部类时,必须先创建1个外部类对象,在实际开发中很少使用这个。
class Outter
{
public class Inner
{ }
} public class Test
{
public static void main(String[] args) { Outter out = new Outter();
Outter.Inner in = out.new Inner();
} }
一个例子,自定义一个Str类,来支持迭代。
class Str implements Iterable<Character>
{ private String innerStr; public Str(String s)
{
this.innerStr = (s==null?"":s);
} private class StrIterator implements Iterator<Character> //迭代器类 作为成员内部类
{ private int curIndex = 0; @Override
public boolean hasNext() { return curIndex < innerStr.length(); //直接访问外部类的成员 innerStr
} @Override
public Character next() { return innerStr.charAt(curIndex++); //直接访问外部类的成员 innerStr
} @Override
public void remove() {
throw new UnsupportedOperationException(); } } @Override
public Iterator<Character> iterator() { return new StrIterator();
} }
成员内部类中不能有static成员,即不能有static方法 和 static字段(除非static字段修饰为static final,那样的话它只不过是一个符号常量罢了)。因为java中类的静态成员必须定义在一个top-level顶层类中,而成员内部类(包括后面的方法内部类,匿名内部类)不是top-level顶层类。
static成员需要定义在top-level类中,而成员内部类不是top-level类。
提升
JVM是不理解nested类型的,也就是在它看来,所有的类型都是top-level的,
在每一个成员内部类中,javac都会自动添加一个字段:this$0,用来引用外部类当前对象。同时, 内部类的构造函数会自动为这个字段添加一个参数,当构造内部类对象时,
外部类当前对象就会传递给this$0,让这个字段引用外部类当前实例对象。
从这点我们也会发现,为什么要实例化一个成员内部类前,需要先实例化一个外部类对象。因为成员内部包含了一个外部类对象。
编译后的class文件形如:WrapperClass $ InnerClass.class
局部内部类(local inner class)
定义在一个方法(包括了类的构造块和static构造块)内部的类,叫局部内部类。它不能有任何访问权限修饰符,因为它只能被包装它的方法使用,离开方法后就不可用了。
局部内部类可以和成员内部类一样,访问外部类的实例成员。同时,它还能直接使用包含它的方法的局部final常量,final参数。javac会复制使用了的外部方法的局部final量保存在局部内部类中作为私有的备份。
因此,当这个外部方法执行完毕后,虽然方法中的局部变量的 lifetime结束了,但是如果局部类的实例作为返回值,它会带着外部方法的局部final量离开这个局部作用域,也就是说,局部变量的生命延长到了和局部内部类的对象的生命周期一致。并不会随着方法执行完立刻被清理掉。我们可以以此来形成闭包。
同样,局部内部类不是top-level类,不能有static成员,除非是static final 字段。
public class Main
{
public static void main(String[] args)
{ MsgGenerator g5 = fac(5);
System.out.println(g5.generatorMsg()); MsgGenerator g2 = fac(2);
System.out.println(g2.generatorMsg()); } public static MsgGenerator fac(final int times)
{
class Generator implements MsgGenerator
{
@Override
public StringBuffer generatorMsg()
{ StringBuffer s= new StringBuffer() ; for(int i=0;i<times;++i)
{
s.append("hello ");
} return s;
} } //end of class return new Generator(); //向外发出闭包
} } interface MsgGenerator
{
StringBuffer generatorMsg();
}
提升:
局部内部类之所以能访问外部类的实例成员,其原因和成员内部类是一样的:内部类中有保存了外部类对象的引用。除此之外,局部内部类还能访问包装方法的final字段,javac会将内部类使用了的final 局部常量拷贝到局部内部类中保存,并在局部内部类对象实例化时,初始化这些final常量。因此,局部内部类使用的final常量是自己的拷贝分。
局部内部类的实现原理(模拟)
class Wapper
{ public void wapperFunction() //在方法中定义一个局部类
{
final int x = 10; class Local //局部类
{ private final int local_copy_x; //假如在局部类中使用了局部常量x,则javac自动生成 private final Wapper this$0; //javac自动生成的字段,用于保存外部类当前对象引用 //首先,局部内部类必定会包含外部类对象,着就是javac插入的第一个构造参数,这是必定的。
//其次,如果我们在局部内部类中使用了包装方法foo中的局部final常量,如x,则会在局部类中
//自动添加隐藏字段local_copy_x,并在构造器中初始化它。
Local(Wapper w,final int x)
{
this$0 = w;
local_copy_x = x;
} }//end of Local
new Local(); //当实例化局部内部类对象时,等价于 new Local(Wapper.this,x) } }
所以,之所以能在局部内部类中访问外部类的实例,是因为javac自动添加并用外部类当前对象this初始化了局部内部类的字段this$0,这样this$0就引用了外部类当前对象。
局部内部类能使用包装 方法的final字段,也是因为javac自动在局部内部类中添加并初始化的结果。
匿名内部类 (inner anonymous class)
匿名内部类是特殊的局部内部类,它没有类名。它的访问特性和局部内类一样。如果只会使用类的一个对象,则可以使用匿名内部类,没有名称避免了再引入一个类名称, 匿名内部类是没有名称的局部内部类,访问特性与局部内部类一样。
因为没有类名,因此只能使用父类名或者接口名来创建对象。
new + superClass 或者 new+interface 。创建对象 是 使用new表达式。
new 表达式:匿名类对象的创建方式是使用new表达式,创建对象的同时也是类结构的编写。表达式的值是一个匿名类对象的引用。
new SuperClass(param1,param2){ 类体 }
一般来说,匿名类没有构造参数,如果有,则传递给他的父类的构造函数。
匿名内部类由于没有类名,所以你不能定义新的构造函数,只能有默认的构造函数(javac添加的)。补救的做法是使用构造块。
下面是使用swing 中的Timer定时触发回调函数的例子,使用匿名类创建 ActionListener对象。程序每经过1000ms,就会调用 ActionListener对象的actionPerformed方法。
public static void main(String[] args) { //javax.swing.Timer;
Timer t = new Timer(1000,
new ActionListener() {
@Override
public void actionPerformed(ActionEvent e)
{
Toolkit.getDefaultToolkit().beep(); //系统响铃声
}
} ); t.start(); while(true)
{ } }
内部类的工作原理
内部类只是Java的语法糖,jvm是不理解内部类的,它所看见的都是top-level顶层类。将内部类分离为单独的顶层类,是javac 的任务。内部类被javac合成为单独的类,并形成独立的class文件,这个文件有独特的名称,形式如下:
static内部类: OutterClass$InnerClass.class
成员内部类:OutterClass$InnerClass.class
局部内部类: OutterClass$XInnerClass.class # X为一个正整数
局部内部类: OutterClass$X.class # X为一个正整数
对于static内部类,无需多解释,因为 static内部类和外部类是无依赖关系的,static内部类不包含外部类引用,javac只是将他们简单的分离。
成员内部类为什么能访问外部类的成员?因为内部类会被javac自动插入一个字段this&0去保存外部类当前对象this的引用。
public class OutterClass
{ private int outFiled = 100; public class InnerClass
{ public void f()
{
int i = outFiled;
}
}
}
javap反编译后的结果
Compiled from "OutterClass.java"
public class OutterClass$InnerClass {
final OutterClass this$0; //由javac自动合成:内部类包含1个外部类的当前对象的引用this&0,使用this&0避免和this冲突 //javac自动合成:合成的构造函数,有1个外部类参数,用于初始化 this&0,this&0被赋值为OutterClass.this
public OutterClass$InnerClass(OutterClass);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:LOutterClass;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return public void f();
Code:
0: aload_0
1: getfield #1 // Field this$0:LOutterClass;
4: invokestatic #3 // Method OutterClass.access$000:(LOutterClass;)I
7: istore_1
8: return
}
局部内部类和匿名内部类除了可以访问外部类的成员(原因和成员内部类是相同的),还可以访问外部方法的局部final量和final参数。原因如下:
A local class can use local variables because javac automatically gives the class a
private instance field to hold a copy of each local variable the class uses.
The compiler also adds hidden parameters to each local class constructor to initial‐
ize these automatically created private fields. A local class does not actually access
local variables but merely its own private copies of them. This could cause inconsis‐
tencies if the local variables could alter outside of the local class.-- 《Java int a Nutshell》
因为javac会自动在(局部和匿名)内部类中插入私有 的实例字段来保存 使用了的外部方法的final量的拷贝。javac还会在(局部和匿名)内部类中的构造函数的添加参数来初始化插入的私有 的字段。所以,(局部和匿名)内部类使用的实质是自己获得的拷贝量,而不是直接使用外部方法的final量。如果外部方法的量不修饰为final的话,那么意味着它的值可以改变,这就可能会导致(局部和匿名)内部类中获得的拷贝和外部方法不一致。所以java强制要求只能使用final量。
静态内部类和非静态内部类的区别
如果你不需要内部类对象与其外围类对象之间有联系,那你可以将内部类声明为static。这通常称为嵌套类(nested class)。Static Nested Class是被声明为静态(static)的内部类,它可以不依赖于外部类实例被实例化。而通常的内部类需要在外部类实例化后才能实例化。想要理解static应用于内部类时的含义,你就必须记住,普通的内部类对象隐含地保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时,就不是这样了。嵌套类意味着:
1. 嵌套类的对象,并不需要其外围类的对象。
2. 不能从嵌套类的对象中访问非静态的外围类对象。
如下所示代码为定义一个静态嵌套类
public class StaticTest{
private static String name = "woobo";
private String num = "X001";
static class Person{ // 静态内部类可以用public,protected,private修饰
// 静态内部类中可以定义静态或者非静态的成员
private String address = "China";
Private Static String x=“as”;
public String mail = "kongbowoo@yahoo.com.cn";//内部类公有成员
public void display(){
//System.out.println(num);//不能直接访问外部类的非静态成员
// 静态内部类不能访问外部类的非静态成员(包括非静态变量和非静态方法)
System.out.println(name);//只能直接访问外部类的静态成员
//静态内部类只能访问外部类的静态成员(包括静态变量和静态方法)
System.out.println("Inner " + address);//访问本内部类成员。
}
}
public void printInfo(){
Person person = new Person();
// 外部类访问内部类的非静态成员:实例化内部类即可
person.display();
//System.out.println(mail);//不可访问
//System.out.println(address);//不可访问
System.out.println(person.address);//可以访问内部类的私有成员
System.out.println(Person.x);// 外部类访问内部类的静态成员:内部类.静态成员
System.out.println(person.mail);//可以访问内部类的公有成员
}
public static void main(String[] args){
StaticTest staticTest = new StaticTest();
staticTest.printInfo();
}
}
在静态嵌套类内部, 不能访问外部类的非静态成员, 这是由Java语法中"静态方法不能直接访问非静态成员"所限定.注意, 外部类访问内部类的的成员有些特别, 不能直接访问, 但可以通过内部类实例来访问, 这是因为静态嵌套内的所有成员和方法默认为静态的了.同时注意, 内部静态类Person只在类StaticTest 范围内可见, 若在其它类中引用或初始化, 均是错误的.
一 . 静态内部类可以有静态成员,而非静态内部类则不能有静态成员。
二 . 静态内部类的非静态成员可以访问外部类的静态变量,而不可访问外部类的非静态变量;
三 . 非静态内部类的非静态成员可以访问外部类的非静态变量。
生成一个静态内部类不需要外部类成员:这是静态内部类和成员内部类的区别。静态内部类的对象可以直接生成:Outer.Inner in = new Outer.Inner();而不需要通过生成外部类对象来生成。这样实际上使静态内部类成为了一个顶级类(正常情况下,你不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分,因为它是static 的。只是将嵌套类置于接口的命名空间内,这并不违反接口的规则)
java 静态内部类小总结的更多相关文章
- Java开发小技巧(三):Maven多工程依赖项目
前言 本篇文章基于Java开发小技巧(二):自定义Maven依赖中创建的父工程project-monitor实现,运用我们自定义的依赖包进行多工程依赖项目的开发. 下面以多可执行Jar包项目的开发为例 ...
- 微信机器人 返现机器人 pc版本 移动版本 java开发 小范省钱
微信机器人 返现机器人 pc版本 移动版本 java开发 小范省钱 微信搜索微信号 fanli-x 或 扫描下方二维码,可查看效果. 非web版微信,pc/移动版微信 支持新号24小时 不封号! 有任 ...
- java的小程序在html中的运行测试
java的小程序在html中的运行测试,打开vs2012,以网站模式打开,生成,调用iis临时服务器运行.
- java 编写小工具 尝试 学习(七)
1.在java 编写小工具 尝试 学习(六)里学会了,控件 的随意摆放, 以及大小(x,y,width,height),又根据前面学习的按钮 被点击 的事件监控 的方法 ,点击 按钮 在显示区域显示“ ...
- 第一个java的小东西
第一次自己写的一个java的小东西,毕竟自己第一次写的,其中可谓是历经艰难,最后总结下来就是java实在是不适合写界面化的东西代码量比较大,这还不是最关键的,最关键的是控件的位置实在是太难控制了. 这 ...
- Java太阳系小游戏分析和源代码
Java太阳系小游戏分析和源代码 -20150809 近期看了面向对象的一些知识.然后跟着老师的解说做了一个太阳系各行星绕太阳转的小游戏,来练习巩固一下近期学的知识: 用到知识点:类的继承.方法的重载 ...
- 福利贴——爬取美女图片的Java爬虫小程序代码
自己做的一个Java爬虫小程序 废话不多说.先上图. 目录命名是用标签缩写,假设大家看得不顺眼能够等完成下载后手动改一下,比方像有强迫症的我一样... 这是挂了一个晚上下载的总大小,只是还有非常多由于 ...
- Java五子棋小游戏(控制台纯Ai算法)
Java五子棋小游戏(控制台纯Ai算法) 继续之前的那个五子棋程序 修复了一些已知的小Bug 这里是之前的五子棋程序 原文链接 修复了一些算法缺陷 本次增加了AI算法 可以人机对战 也可以Ai对Ai看 ...
- Java web 小测验
题目要求: 1登录账号:要求由6到12位字母.数字.下划线组成,只有字母可以开头:(1分) 2登录密码:要求显示“• ”或“*”表示输入位数,密码要求八位以上字母.数字组成.(1分) 3性别:要求用单 ...
随机推荐
- .NET Core采用的全新配置系统[1]: 读取配置数据
提到“配置”二字,我想绝大部分.NET开发人员脑海中会立马浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置定义在这两个文 ...
- ASP.NET Core管道深度剖析[共4篇]
之所以称ASP.NET Core是一个Web开发平台,源于它具有一个极具扩展性的请求处理管道,我们可以通过这个管道的定制来满足各种场景下的HTTP处理需求.ASP. NET Core应用的很多特性,比 ...
- SubSonic3.0使用存储过程查询时,不能使用output参数返回值的问题修改
有个群友问SubSonic3.0执行存储过程时能不能使用output参数返回值,说测试过后获取不到返回值,早上有些时间所以就尝试修改了一下 首先在数据库中创建一个存储过程 CREATE PROCEDU ...
- c 进程和系统调用
这一篇博客讲解进程和系统调用相关的知识 有这样一个场景,我需要输入一串文字,然后把我输入的文字加上一个本地的时间戳 保存在一个文件中,可以初步理解为一个备忘录也行 #include <stdio ...
- 【中文分词】二阶隐马尔可夫模型2-HMM
在前一篇中介绍了用HMM做中文分词,对于未登录词(out-of-vocabulary, OOV)有良好的识别效果,但是缺点也十分明显--对于词典中的(in-vocabulary, IV)词却未能很好地 ...
- ASP.NET Core 整合Autofac和Castle实现自动AOP拦截
前言: 除了ASP.NETCore自带的IOC容器外,我们还可以使用其他成熟的DI框架,如Autofac,StructureMap等(笔者只用过Unity,Ninject和Castle). 1.ASP ...
- 前端开发:css基础知识之盒模型以及浮动布局。
前端开发:css基础知识之盒模型以及浮动布局 前言 楼主的蛮多朋友最近都在学习html5,他们都会问到同一个问题 浮动是什么东西? 为什么这个浮动没有效果? 这个问题楼主已经回答了n遍.今天则是把 ...
- H-1B身份六年后的延期问题
http://www.hooyou.com/cn_version/h-1b/extension.html H-1B首次获签的在美国居留时限是三年,三年期满后还可以申请延期再续三年,总计在美国的最长时限 ...
- KMP算法
KMP算法是字符串模式匹配当中最经典的算法,原来大二学数据结构的有讲,但是当时只是记住了原理,但不知道代码实现,今天终于是完成了KMP的代码实现.原理KMP的原理其实很简单,给定一个字符串和一个模式串 ...
- Mysql FROM_UNIXTIME效率 VS PHP date()效率 数据说话!
这几天在做数据统计,有几个统计图的需求是这样的: 按照年.月.日统计订单数量, 比方一年12个月,统计出1月多少订单,二月多少订单,按照这种模式统计. 但是数据库里存放的是 timestamp 的 ...