编写java程序时,一般一个类(或者接口)都是放在一个独立的java文件中,并且类名同文件名(如果类是public的,类名必须与文件名一致;非public得,无强制要求)。如果想把多个java类放在一个java文件中,则只能有一个public类。如下面的两个类放在同一个文件中就会报错,无法编译通过。
可以看出,因为TestOne.java文件中已经有一个public类TestOne,这时再加了一个public类TestTwo就报错了。如果将类TestTwo前面的public修饰符去掉就没有问题了。
我们下面介绍内部类的概念和使用,所谓内部类,简单的说,就是一个类定义在另一个类的内部。与上面的两个类在同一个文件中不同(TestOne和TestTwo虽然在一个文件中,但相互没有嵌套,是并列的)。
采用内部类,有时会对代码的可读性带来一点问题,但很多场景下还是很有必要的。尤其在使用一些框架(如java的swing,集合框架中的排序操作)等,使用内部类(尤其是匿名内部类)会带来很多便利;再比如我们在开发Android app时,就会大量的使用内部类,如果不了解内部类的含义和使用规则,几乎无法顺利的进行Android app的开发。
内部类有其特别的地方,其中核心之一是,内部类实例可以访问包含它的外部类的所有成员,包括private成员。另外非常关键的一点是,内部类的使用必须与一个外部类的实例绑定,注意是实例,后面的例子中会说明这点。
内部类也分好几种情况,下面一一来解释。
一、一般内部类
例1:TestTwo就是一个内部类,它可以直接访问外部类中的私有成员a。
public class TestOne { private int a = 2; public static void main(String[] args) { new TestOne().call(); } public void call(){ TestTwo two = new TestTwo(); two.show(); } class TestTwo { public void show(){ System.out.println(a); } } }
例2:如果需要在外部类的静态方法中使用内部类,必须创建一个外部类的实例,通过该实例来new内部类。不能直接new,否则会报编译错误。
public class TestOne { private int a = 2; public static void main(String[] args) { TestOne testOne = new TestOne(); TestTwo testTwo = testOne.new TestTwo(); testTwo.show(); } public void call(){ TestTwo two = new TestTwo(); two.show(); } class TestTwo { public void show(){ System.out.println(a); } } }
例3:如何在内部类中引用外部类的实例。有两种方法,一是普通的方法,在new内部类时,将外部类的this作为内部类的构造函数参数传给内部类;还有一种更简单的方法,直接在内部类中通过 "外部类类名.this" 就可以使用外部类的实例。如下面代码:
public class TestOne { private int a = 2; public static void main(String[] args) { new TestOne().call(); } public void call(){ new TestTwo(this).show(); } class TestTwo { private TestOne one; public TestTwo(TestOne one){ this.one = one; } public void show(){ System.out.println(a); System.out.println(one.a); System.out.println(TestOne.this.a); } } }
上述情况下使用内部类,通常该内部类只是被该外部类使用,这样可以对内部类起到隐藏作用,并且跟外部类绑定的更紧密。
二、方法内的内部类
就是内部类定义在一个方法的内部。这种情况下,只有在该方法内才能使用该内部类;内部类也可以访问外部类的成员;但注意不能访问包含它的方法的局部变量(包括参数),除非方法内的变量(或参数)是final的。 举例:
public class TestOne { private int a = 2; public static void main(String[] args) { new TestOne().call(); } public void call(){ final int b=3; class TestTwo { private TestOne one; public TestTwo(TestOne one){ this.one = one; } public void show(){ System.out.println(a); System.out.println(one.a); System.out.println(TestOne.this.a); System.out.println(b); } } new TestTwo(this).show(); } }
需要注意的是,如果是在静态方法中定义内部类,则该内部类只能使用外部类的静态成员。
三、匿名内部类
匿名内部类,其实也分很多情况。最常见的情况是,直接通过父类(比如抽象类)创建子类的实例,或直接通过接口创建对象,该对象可传递作为方法的参数,或者赋值给某个变量。实际上在内部类的使用中,匿名类是用途最广泛的。
因为我们都知道,在java中,一般情况下,不像c/c++一样,我们可以把一个方法作为一个变量进行传递或引用。
我们只能传递和引用java对象。比如我们定义了一个方法,有个参数是接口类型或抽象类,这样不使用匿名内部类,我们每次都需要先定义一个类实现该接口或抽象类,再创建该类的实例传递给该方法。当接口或抽象类中需要实现的方法数量比较少时(比如只有1个),这个先创建类,再使用就显的比较啰嗦了。而且这种场景下,往往创建的类就这一个地方使用。而通过匿名内部类,就可以让编写的代码简洁很多。 实际上,java本身提供的一些api(如多线程、io、集合框架等)就大量的使用了这个特性。 我们来看几个例子:
例1:通过接口创建对象
public class TestThread { public static void main(String[] args) { TestThread testThread = new TestThread(); testThread.fun1(); testThread.fun2(); } public void fun1(){ Runnable runnable = new Runnable() { @Override public void run() { System.out.println("fun1"); } }; new Thread(runnable).start(); } public void fun2(){ new Thread(new Runnable() { @Override public void run() { System.out.println("fun2"); } }).start(); } }
可以看出,方法fun1中直接通过Runnable接口创建了对象,供Thread类使用。fun2更简化了下,直接连变量名也省掉了,对象的创建直接在传入参数时创建。
例2:直接创建父类的子类对象
public class TestChild { public static void main(String[] args) { new Thread() { @Override public void run() { System.out.println("thread is run"); } }.start(); } }
可以看出,Thread类有一个run方法,但这个run方法是空的,我们如果要用继承Thread类来实现多线程,必须在子类中重载run方法。上面的代码我们实际上没有显示的定义一个Thread的子类,而是直接通过上述形式创建了一个线程实例,并启动它。
四、静态嵌套类(内部类)
也就是在一个类中定义一个静态类(注意不能在方法中定义)。 与前面介绍的内部类不同。这个嵌套类只是命名空间限制在外部类中。实质上它不需要与外部类的实例绑定,也无法访问外部类的实例成员,只能访问外部类的静态成员。代码如:
public class TestOne { private int a = 2; static private int b = 2; public static void main(String[] args) { TestTwo testTwo = new TestTwo(); } public void call(){ TestTwo testTwo = new TestTwo(); } static class TestTwo { public void show(){ System.out.println(b); } } } class TestThree{ public static void main(String[] args) { TestOne.TestTwo testTwo = new TestOne.TestTwo(); } public void call(){ TestOne.TestTwo testTwo = new TestOne.TestTwo(); } }
可以看出,TestTwo的使用不需要依赖外部类TestOne的实例。
上面介绍了内部类的几种情况,使用的时候稍加注意即可。