内部类是指在一个外部类的内部再定义一个类。内部类做为外部类的一个成员,而且依附于外部类而存在的。内部类可为静态,可用protected和private修饰(而外部类只能使用public和缺省的包访问权限)。内部类主要有如下几类:成员内部类、局部内部类、静态内部类、匿名内部类
为何须要内部类?
典型的状况是,内部类继承自某个类或实现某个接口,内部类的代码操做建立其的外围类的对象。因此你能够认为内部类提供了某种进入其外围类的窗口。使用内部类最吸引人的缘由是:
每 个内部类都能独立地继承自一个(接口的)实现,因此不管外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。若是没有内部类提供的能够继承多 个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实 现了“多重继承”。java
静态内部类型能够是class,interface,或者enum。而其余类型的内部类型只能是class。shell
静态内部类其实仍是一个顶层类(源代码文件级别的类),他不依赖外部类,只不过它被封装在另外一个class或者interface中,而不是直接定义在文件级别中。所以,它和通常的类静态成员很相似:编程
一、它不包含外部类当前对象引用this,所以不能直接访问外部类的实际成员,但可使用外部类的static成员。数据结构
二、静态内部类做为一个静态成员,所以能够用访问权限修饰符:public . private .......等。用的最多通常是private闭包
引用静态内部类: Wapper.Innerapp
三、不能在非静态内部类中再定义静态内部类。静态内部类能够无限深度的嵌套下去。jvm
提高ide
内部类最终会被javac编译为独立的类,JVM看见的都是top-level类。函数
编译后的class文件形如:WrapperClass $ InnerStaticClass.classthis
下面是使用静态内部类简单实现链式栈数据结构的例子。
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
定义在一个方法(包括了类的构造块和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自动在局部内部类中添加并初始化的结果。
匿名内部类是特殊的局部内部类,它没有类名。它的访问特性和局部内类同样。若是只会使用类的一个对象,则可使用匿名内部类,没有名称避免了再引入一个类名称, 匿名内部类是没有名称的局部内部类,访问特性与局部内部类同样。
由于没有类名,所以只能使用父类名或者接口名来建立对象。
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 的。只是将嵌套类置于接口的命名空间内,这并不违反接口的规则)