内部类 Inner Class
一个内部类可以定义在另一个类里,可以定义在函数里,甚至可以作为一个表达式的一部分。
Java中的内部类共分为四种:
静态内部类static inner class (also called nested class)
成员内部类member inner class
局部内部类local inner class
匿名内部类anonymous inner class
1 成员内部类 member inner class
1.1 形式
成员内部类也是定义在另一个类中,但是定义时不用static修饰。形如:
1 class Outer { 2 class Inner{ 3 4 } 5 }
编译之后会产生如下2个class文件:
---->这可以将相关的类组织在一起,从而降低了命名空间的混乱。
成员内部类的修饰符:
对于普通的类,可用的修饰符有final、abstract、strictfp、public和默认的包访问。
但是成员内部类更像一个成员变量和方法。
可用的修饰符有:final、abstract、public、private、protected、strictfp和static。
一旦用static修饰内部类,它就变成静态内部类了。
1.2 创建内部类实例
成员内部类就像一个实例变量,他依赖于外部类的实例存在 --> 必须先有外部类的实例 才能创建成员内部类对象
在外部类里面创建成员内部类的实例:this.new Innerclass(); 或可以用 Inner inner = new Inner(); 方法直接创建
在外部类之外创建内部类的实例:(new Outerclass()).new Innerclass();
或 Inner inner = new Outer().new Inner()
或 Outer outer = new Outer(); Inner inner = outer.new Inner();
案例:从外部类的非静态方法中实例化内部类对象。
1 class Outer { 2 private int i = 10; 3 public void makeInner(){ 4 Inner in = new Inner(); 5 in.seeOuter(); 6 } 7 class Inner{ 8 public void seeOuter(){ 9 System.out.print(i); 10 } 11 } 12 }
表面上,我们并没有创建外部类的对象就实例化了内部类对象,和上面的话矛盾。
事实上,如果不创建外部类对象也就不可能调用makeInner()方法,所以到头来还是要创建外部类对象的。
你可能试图把makeInner()方法修饰为静态方法,即static public void makeInner()。
这样不创建外部类就可以实例化外部类了!但是在一个静态方法里能访问非静态成员和方法吗?显然不能。
--> 必须先有外部类的实例 才能创建成员内部类对象
案例:从外部类的静态方法中实例化内部类对象
1 class Outer { 2 private int i = 10; 3 class Inner{ 4 public void seeOuter(){ 5 System.out.print(i); 6 } 7 } 8 public static void main(String[] args) { 9 Outer out = new Outer(); 10 Outer.Inner in = out.new Inner(); 11 //Outer.Inner in = new Outer().new Inner(); 12 in.seeOuter(); 13 } 14 }
被注释掉的那行是它上面两行的合并形式,一条简洁的语句。
对比一下:在外部类的非静态方法中实例化内部类对象是普通的new方式:Inner in = new Inner();
在外部类的静态方法中实例化内部类对象,必须先创建外部类对象:Outer.Inner in = new Outer().new Inner();
1.3 成员内部类操作外部类
成员内部类可以访问它的外部类的所有成员变量和方法,不管是静态的还是非静态的都可以。
内部类就像一个实例成员一样存在于外部类,所以内部类可以访问外部类的所有成员就想访问自己的成员一样没有限制。
内部类中的this指的是内部类的实例对象本身,如果要用外部类的实例对象就可以用类名.this的方式获得。
普通的类可以用this引用当前的对象,内部类也是如此。
但是假若内部类想引用外部类当前的对象呢?用“外部类名”.this;的形式,如下例的Outer.this。
1 class Outer { 2 class Inner{ 3 public void seeOuter(){ 4 System.out.println(this); 5 System.out.println(Outer.this); 6 } 7 } 8 9 public static void main(String[] strs){ 10 new Outer().new Inner().seeOuter(); 11 } 12 } 13 14 输出: 15 Outer$Inner@61de33 16 Outer@14318bb
1.4 内部类对象中不能有静态成员
原因很简单,内部类的实例对象是外部类实例对象的一个成员,若没有外部类对象,内部类就不会存在,何谈静态成员呢。
1 class Outer { 2 class Inner{ 3 static int i = 0; 4 public void seeOuter(){ 5 System.out.println(this); 6 System.out.println(Outer.this); 7 } 8 } 9 10 public static void main(String[] strs){ 11 new Outer().new Inner().seeOuter(); 12 } 13 }
我们编译这个类:
1 E:\>javac Outer.java 2 Outer.java:3: 内部类不能有静态声明 3 static int i = 0; 4 ^ 5 1 错误
2 局部内部类local inner class
局部内部类local inner class 也可以成为方法内部类
顾名思义,就是把类放在方法内。局部内部类定义在方法中,比方法的范围还小。是内部类中最少用到的一种类型。
像局部变量一样,不能被public, protected, private和static修饰。
局部内部类在方法中定义,所以只能在方法中使用,即只能在方法当中生成局部内部类的实例并且调用其方法。
1 class Outer { 2 public void doSomething(){ 3 class Inner{ 4 public void seeOuter(){ 5 System.out.println("inner class"); 6 } 7 } 8 9 Inner inner = new Inner(); 10 inner.seeOuter(); 11 } 12 13 public static void main(String ... args){ 14 new Outer().doSomething(); 15 } 16 }
输出:
inner class
局部内部类只能在声明的方法内是可见的,因此定义局部内部类之后,想用的话就要在方法内直接实例化,
记住这里顺序不能反了,一定是要先声明后使用,否则编译器会说找不到。
方法内部类的修饰符:
与成员内部类不同,方法内部类更像一个局部变量。
可以用于修饰方法内部类的只有final和abstract。
注意事项:
A: 方法内部类只能在定义该内部类的方法内实例化,不可以在此方法外对其实例化
B: 方法内部类对象不能使用该内部类所在方法的非final局部变量。
原因:
因为方法的局部变量位于栈上,只存在于该方法的生命期内。当一个方法结束,其栈结构被删除,局部变量成为历史。
但是该方法结束之后,在方法内创建的内部类对象可能仍然存在于堆中!例如,如果对它的引用被传递到其他某些代码,并存储在一个成员变量内。
正因为不能保证局部变量的存活期和方法内部类对象的一样长,所以内部类对象不能使用它们。下面是完整的例子:
1 class Outer { 2 public void doSomething(){ 3 final int a =10; 4 class Inner{ 5 public void seeOuter(){ 6 System.out.println(a); 7 } 8 } 9 Inner in = new Inner(); 10 in.seeOuter(); 11 } 12 public static void main(String[] args) { 13 Outer out = new Outer(); 14 out.doSomething(); 15 } 16 }
C:静态方法内的方法内部类。
静态方法是没有this引用的,因此在静态方法内的内部类遭受同样的待遇,即:只能访问外部类的静态成员。
3 匿名内部类Anonymous Inner Class
顾名思义,没有名字的内部类。
匿名内部类就是没有名字的局部内部类,不使用关键字class, extends, implements, 没有构造方法。
匿名内部类隐式地继承了一个父类或者实现了一个接口。
匿名内部类使用得比较多,通常是作为一个方法参数。
A、继承式的匿名内部类。
1 class Car { 2 public void drive(){ 3 System.out.println("Driving a car!"); 4 } 5 } 6 7 8 class Test{ 9 public static void main(String[] args) { 10 Car car = new Car(){ 11 public void drive(){ 12 System.out.println("Driving another car!"); 13 } 14 }; 15 car.drive(); 16 } 17 }
结果输出了:Driving another car!
建立匿名内部类的关键点是重写父类的一个或多个方法。再强调一下,是重写父类的方法,而不是创建新的方法。
因为用父类的引用不可能调用父类本身没有的方法!创建新的方法是多余的。简言之,参考多态。
B、接口式的匿名内部类。
1 interface Vehicle { 2 public void drive(); 3 } 4 5 6 class Test{ 7 public static void main(String[] args) { 8 Vehicle v = new Vehicle(){ 9 public void drive(){ 10 System.out.println("Driving a car!"); 11 } 12 }; 13 v.drive(); 14 }
这种形式的代码我们一定写过,这也是内部类的存在的一个重要作用:便于编写 线程和事件驱动的代码
1 public class ThreadDemo { 2 public static void main(String[] args) { 3 4 new Thread(new Runnable(){ 5 public void run(){ 6 System.out.println("Hello World!"); 7 } 8 }).start(); 9 } 10 } 11 12 13 E:\>javac ThreadDemo.java 14 E:\>java ThreadDemo 15 Hello World!
1 public class SwingTest 2 { 3 public static void main(String[] args) 4 { 5 JFrame frame = new JFrame("JFrame"); 6 JButton button = new JButton("JButton"); 7 8 button.addActionListener(new ActionListener(){ 9 10 @Override 11 public void actionPerformed(ActionEvent arg0){ 12 System.out.println("Hello World"); 13 14 } 15 }); 16 17 frame.getContentPane().add(button); 18 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 19 frame.setSize(200, 200); 20 21 frame.addWindowListener(new WindowAdapter() { 22 23 @Override 24 public void windowClosing(WindowEvent e){ 25 System.out.println("Closing"); 26 System.exit(0); 27 } 28 }); 29 30 frame.setVisible(true); 31 } 32 }
若你了解安卓的话 会发现这样的代码编写方式有着很多的应用
好处就是简化我们的代码。
C、参数式的匿名内部类。
1 class Bar{ 2 void doStuff(Foo f){} 3 } 4 5 inteface Foo{ 6 void foo(); 7 } 8 9 class Test{ 10 static void go(){ 11 Bar b = new Bar(); 12 b.doStuff(new Foo(){ 13 public void foo(){ 14 System.out.println("foofy"); 15 } 16 }); 17 } 18 } 19
4 静态内部类static inner class
在定义成员内部类的时候,可以在其前面加上一个权限修饰符static。此时这个内部类就变为了静态内部类。
同样会被编译成一个完全独立的.class文件,名称为OuterClass$InnerClass.class的形式。
只可以访问外部类的静态成员和静态方法,包括了私有的静态成员和方法。
生成静态内部类对象的方式为:OuterClass.InnerClass inner = new OuterClass.InnerClass();
静态内部类使用代码:
1 package com.learnjava.innerclass; 2 3 class StaticInner 4 { 5 private static int a = 4; 6 7 // 静态内部类 8 public static class Inner 9 { 10 public void test() 11 { 12 // 静态内部类可以访问外部类的静态成员 13 // 并且它只能访问静态的 14 System.out.println(a); 15 } 16 17 } 18 } 19 20 public class StaticInnerClassTest 21 { 22 23 public static void main(String[] args) 24 { 25 StaticInner.Inner inner = new StaticInner.Inner(); 26 inner.test(); 27 } 28 }
与一般内部类不同,在静态代码中不能够使用this操作,所以在静态内部类中只可以访问外部类的静态变量和静态方法。
使用静态内部类的目的和使用内部类相同。如果一个内部类不依赖于其外部类的实例变量,或与实例变量无关,则选择应用静态内部类。
可以在静态内部类的方法中,直接访问外部类的静态变量和调用静态方法。但不允许访问外部类的实例变量以及实例方法。
静态内部类的实例方法中亦只允许访问外部类的静态成员。
静态内部类不同于其他3种内部类,他有着自己特殊的特性,参看:解析静态内部类的使用目的与限制
5 小结
5.1 几种内部类的共性:
A、内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类命和$符号。
B、内部类不能用普通的方式访问。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否是private的。
5.2 java中为什么要引入内部类?还有匿名内部类?
1)可以是单继承的一种补充解决方案 inner classes能有效实际地允许“多重实现继承(multiple implementation)”
Java中一个类只能继承一个类 可以通过内部类达到继承多个类的效果
:每个inner class都能够各自继承某一实现类(implementation),因此,inner class不受限于outer class是否已继承自某一实现类。
2)针对具体的问题提供具体的解决方案,同时又能对外隐藏实现细节 看具体的案例
案例1
在集合中可以使用Iterator遍历 但每一种集合的数据结构不同 导致遍历的方法必然也不同
所以Java在每个具体的集合里定义了一个内部类Itr 他实现了Iterator接口 从而根据所在类的具体情况进行遍历
1 public interface Iterator {//迭代器的功能 2 boolean hasNext(); 3 Object next(); 4 } 5 6 public interface Iterable {//返回迭代器的能力 7 Iterator iterator(); 8 } 9 10 public interface Collection extends Iterable { 11 Iterator iterator(); 12 } 13 14 public interface List extends Collection { 15 Iterator iterator(); 16 } 17 18 public class ArrayList implements List { 19 public Iterator iterator() { 20 return new Itr(); 21 } 22 23 private class Itr implements Iterator { 24 //每种集合的具体实现采用了不同的数据结构 25 public boolean hasNext() {......} 26 public Object next(){......} 27 } 28 } 29 30 Collection c = new ArrayList(); 31 c.add("hello"); 32 c.add("world"); 33 c.add("java"); 34 Iterator it = c.iterator(); //new Itr(); 35 while(it.hasNext()) { 36 String s = (String)it.next(); 37 System.out.println(s); 38 }
Itr是private的类 对外并不可见 因为我的遍历方法我自己知道就可以了 别人并不需要了解
集合类有实现了Tterable接口 通过里面的iterator方法获取该内部类实例 外部不能直接创建该内部类的实例
案例2
为某个类提供特定的数据结构 : 为了共同解决一个具体的问题 但又可以对外部保持透明
如HashMap中有Entry ConcurrentHashMap有HashEntry 和Segment
看HashMap中的内部类:
1 public class HashMap<K,V> 2 extends AbstractMap<K,V> 3 implements Map<K,V>, Cloneable, Serializable 4 { 5 6 static final Entry<?,?>[] EMPTY_TABLE = {}; 7 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; 8 9 static class Entry<K,V> implements Map.Entry<K,V> { 10 final K key; 11 V value; 12 Entry<K,V> next; 13 int hash; 14 // ... ... 15 } 16 }
关于HashMap 可以参考:HashMap源码解析
关于ConcurrentHashMap:ConcurrentHashMap ConcurrentHashMap原理分析