Java允许在一个类定义中再定义一个类,这就是内部类。可以把内部类看做是外部类的一个域,那么很多问题理解起来就很方便了。根据是否用static关键字修饰类又可以将类分为内部类(Inner Class)和嵌套类(Nested Class)
class Outer { //外部类的域和方法 class Inner { //内部类的域和方法 } }
当把内部类看作是外部类的域时,由于是非static的,所以内部类是与外部类的某个对象相关联的,也就是说得现有外部类对象,再有内部类对象。并且由于内部类是一个域,所以可以访问外部类的所有成员,而不需要特殊条件。
class Outer { // 外部类的域和方法 private String name = "Outer"; public Inner getInner() { return new Inner(); } class Inner { // 内部类的域和方法 void print() { System.out.println(name); } } public static void main(String[] args) { //Inner inner1 = new Inner();编译器会提示没有外围对象与之关联 Outer outer = new Outer(); Inner inner = outer.getInner(); inner.print(); } }
Output:Outer
可以看到内部类是可以访问外部类的name域的,尽管它是private修饰的。而如果想直接创建Inner对象,编译器也会报错。当通过一个外部类的对象获得n内部类对象时就不会报错。
c..this和.new
上面的代码可以看到是通过在外部类中提供一个方法来返回内部类对象的,那么只有这一种方法可以产生内部类对象吗?
其实还有一种方法,就是使用.new,如下
Outer outer=new Outer(); Inner inner2 = outer.new Inner(); inner2.print();
可以使用外部类对象创建出一个内部类。
我们知道如果在一个类中想使用本对象,可以使用this关键字,但是如果想在内部类中使用与之关联的外部类对象,那该怎么做呢?
可以使用外部类名字后面紧跟原点和this(ClassName.this);
class Outer { // 外部类的域和方法 private String name = "Outer"; public Inner getInner() { return new Inner(); } void print() { System.out.println("Outer:" + name); } class Inner { // 内部类的域和方法 void print() { System.out.println(name); } Outer outer() { return Outer.this; } } public static void main(String[] args) { // Inner inner1 = new Inner();编译器会提示没有外围对象与之关联 Outer outer = new Outer(); Inner inner = outer.new Inner(); inner.outer().print(); } }
除了可以在一个类的内部定义内部类外,还可以在一个方法里面或者任意的作用域里定义内部类。这么做有两个理由:
1)实现了某类型的接口,可以返回对其的引用
2)创建一个类来辅助解决复杂问题,并且并不希望这个类公用。
public interface Playable { void play(); } class Outer { public Playable getPlayable() { class Football implements Playable { @Override public void play() { // TODO Auto-generated method stub System.out.println("Play Football"); } } return new Football(); } public static void main(String[] args) { Outer outer = new Outer(); Playable playable = outer.getPlayable(); playable.play(); } }
Output:Play Football
这种在方法体内部定义的内部类称为局部内部类。
class Outer { public void test(boolean b) { if (b) { class Basketball implements Playable { @Override public void play() { // TODO Auto-generated method stub System.out.println("Play Basketball"); } } new Basketball().play(); } } public static void main(String[] args) { Outer outer = new Outer(); outer.test(true); } }
上面的例子就是在在作用域中定义的内部类。
class Outer { private String name = "Outer"; // 匿名内部类 public Inner getInner() { return new Inner() { @Override void print() { // TODO Auto-generated method stub System.out.println("匿名内部类Inner:" + name); } }; } class Inner { void print() { System.out.println("内部类Inner:" + name); } } public static void main(String[] args) { // Inner inner1 = new Inner();编译器会提示没有外围对象与之关联 Outer outer = new Outer(); Inner inner = outer.getInner(); } }
我们会发现getInner方法的方法体有点奇怪,方法将返回值的生成与表示这个返回值的类的定义结合在一起。另外,这个类是匿名的,这种奇怪的语法是指“创建一个继承自Inner的匿名类的对象”,在上述代码上我覆盖了Inner基类中的print方法。通过new表达式返回的引用被自动向上转型为对Inner的引用。
说到继承,就会想到一个初始化的问题。上面代码导出类采用了默认构造器,但如果基类的构造器是有参的,那么匿名内部类的构造器也必须是有参的。
由于匿名内部类没有名字,那么该如何进行初始化呢?这可以通过实例初始化进行解决。
如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是final的。我对此的猜想是Java为了安全,因为匿名内部类可能会修改外部的变量,而使用者却并不知道,这就有可能造成很大的错误,并且不知道何时发生了错误,就不如上面的代码,虽然我在类中重写了print方法,但是对于使用者却看不出效果。所以为了避免有可能出现错误,Java就规定你要使用的参数都必须是final的。
嵌套类与内部类的定义一样,只不过多了一个static关键字修饰。同样把嵌套类看做是域的话,那么静态域是属于类的,也就是说嵌套类是可以独立于对象而存在的。同样,如果在类中如果想使用外部类的域,那么也只能使用类的域,即static域,而不能使用非static域(因为与具体对象有关)。如果想创建嵌套类的对象,也可以直接创建,而不需要创建外围对象。
class Outer { private String name = "Outer"; static int size = 3; // 嵌套类 static class NestedClass { void print() { // System.out.println(name);name为非静态的,不可以访问 System.out.println(size); } } public static void main(String[] args) { NestedClass class1 = new NestedClass(); class1.print(); } }