Java中提供了内部类机制(Innner Class),是指将一个类的定义放在另一个类的内部,其中内部类可以访问包含它们外部类的域,内部类技术主要用于设计具有相互协作关系的类集合,在编写GUI事件的代码时会经常使用到内部类。内部类和组合是完全不同的概念。
为什么需要内部类?
- 内部类可以访问外部类的数据,包括私有的数据。
- 内部类可以对同一个包中的其他类隐藏
- 当要定义一个回调函数又不想编写大量的代码的时候,使用匿名(anonymous)内部类比较方便
为什么内部类拥有外部类的所有元素的访问权?
当某个外围类对象创建一个内部连对象时,内部类对象必定会捕获一个指向那个外围类对象的引用。内部类对象只能在与其外部类对象关联的情况下才能被创建(在内部类非static时),构建内部类需要一个外部类的引用,内部类正是利用这个引用去访问外部类的。
内部类的种类
按照内部类所在的位置不同,内部类可以分为以下几种:
- 成员内部类
- 方法内部类
- 匿名内部类
- 静态内部类
1.成员内部类的创建
a、内部类直接在类的内部进行声明。可以声明为private、protected、public或者默认访问权限,这个访问权限约定和外部类完全一样。
b、内部类自动拥有对其外围类所有成员(方法、属性)的访问权。如果内部类和外部类成员的名字完全相同,在内部类方法中要访问外部类成员,则需要使用下面的方式来访问:外部类名.this.外部成员名,例如Outer.this.i++; (看例子)
c、必须使用外部类对象来创建内部类对象,而不是直接去new一个。
格式为:外部对象名.new 内部类构造方法
b、内部类自动拥有对其外围类所有成员(方法、属性)的访问权。如果内部类和外部类成员的名字完全相同,在内部类方法中要访问外部类成员,则需要使用下面的方式来访问:外部类名.this.外部成员名,例如Outer.this.i++; (看例子)
c、必须使用外部类对象来创建内部类对象,而不是直接去new一个。
格式为:外部对象名.new 内部类构造方法
d、成员内部类中不能定义静态的变量和静态方法。
下面创建的Outer外部类包含了一个私有的内部类Inner,在内部类中访问外部类中的数据域,然后通过外部类去创建内部类
public class Outer {
public int i = 10;
private String str = "outer class";
private class Inner {
private int i = 11;
public int getInner() {
return i;
}
public int getOuter() {
return Outer.this.i;
}
public String getOuterStr() {
return Outer.this.str;
}
}
public static void main(String[] args) {
Outer outer = new Outer();
// Inner inner = new Inner(); //这样创建内部类编译器会报错
Inner inner = outer.new Inner();
System.out.println(inner.getInner());
System.out.println(inner.getOuter());
System.out.println(inner.getOuterStr());
}
}
需要说明的是,创建内部类必须要用外部类的引用去创建,虽然这里的内部类没有构造方法,但是之后编译器会给Inner加上一个构造方法,然后外部类的引用就作为参数传递给内部类,创建好的内部类就维护了这个外部类的引用,并用它访问外部类。并且即使Inner有构造方法,编译器也会修改那个构造方法加入一个外部类对象的类型参数。
public Inner(Outer outer) {
this.outer = outer;
}
如若像注释中直接创建内部类,编译器就会直接报如下错误:
No enclosing instance of type Outer is accessible. Must qualify the allocation with an enclosing instance of type Outer (e.g. x.new A() where x is an instance of Outer).
另外,可以发现这个Inner内部类是private,这就意味着Inner只在这个Outer类中可见,是一个私有类,即时在同包的其他类中通过Outer也无法访问。当然,如果不是private内部类,还是可以访问的。
上述Outer类经过javac编译后一共生成了3个Class文件
2.方法内部类
将一个类的定义放在方法体的内部,并且方法内部类只能在该方法内可见
public class MethodInner {
private int i;
public MethodInner(int i) {
this.i = i;
}
public int method(int val) {
class InnerClass {
private int i;
public InnerClass(int i) {
this.i = i;
}
public int increment() {
return ++i;
}
}
InnerClass inner = new InnerClass(val);
return inner.increment() + add();
}
public int add() {
return ++i;
}
public static void main(String[] args) {
MethodInner mi = new MethodInner(5);
int i = mi.method(2);
System.out.println(i);
}
}
程序的输出:9
这里在method方法内定义了Inner内部类,Inner内部类只能在该方法内可见。
3.匿名内部类
所谓匿名内部类就是不给出类的名字,直接定义一个类,通常这个类实现了某个接口,在多线程编程中经常使用到匿名内部类
public class AnonymousClass {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("It's runnable target...");
}
});
t.start();
}
}
其中new Thread(Runnable); 中Runnable就是一个匿名内部类,这样的写法简化了程序的编写。上述写法等价于下面的代码,很显然,上面的匿名类省去了Target类的命名
public class AnonymousClass {
public static void main(String[] args) {
Target target = new Target();
Thread t = new Thread(target);
t.start();
}
}
class Target implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("It's runnable target...");
}
}
其中匿名内部类在经过javac编译之后生成的Class文件也只有两个:
4.静态内部类
如果不需要内部类和外部类对象之间有任何联系,即不让内部类访问外部类可以将内部类声明为static,称为静态内部类,也叫嵌套内部类,相比于其他内部类,嵌套内部类和外围类的关系没那么紧密。
这种内部类的特定是:它不能访问外部类的非静态成员;创建静态内部类对象的时候,也不需要外部类对象。
以下定义了一个静态内部类,并在静态内部类中访问外部类的静态成员,但是不能访问外部类的非静态成员,然后直接创建静态内部类,不需要传递外部类的引用。
public class StaticInner {
private int i = 1;
private static String str = "outer";
private static class StaticInnerClass {
private int i = 11;
private String name;
public StaticInnerClass(String str) {
this.name = str;
}
public void say() {
System.out.println("outer : " + StaticInner.str);
System.out.println("inner : i=" + i + " - name=" + name);
}
}
public static void main(String[] args) {
StaticInnerClass inner = new StaticInnerClass("inner");
inner.say();
}
}
还有就是,有时会分出一种接口内部类,但是接口内部类可以归结为静态内部类,在接口中声明的内部类默认就是public static的,所以可以参照静态内部类去学习接口内部类。