匿名内部类也是 嵌套类 中的一种。又或者说,匿名内部类是一种特殊的内部类。
匿名内部类是一个没有名称的内部类,只创建了一个单独的对象。
匿名内部类是非常有用的,也经常看到。经常用于那些只使用一次,又需要创建具有某些 额外 功能的对象实例( 例如重载类或接口的方法 )的场景。有了匿名内部类,我们就不需要创建只使用一次的类的子类。
匿名内部类主要有两种方式创建:
- 从一个类( 可以是虚类,也可以是普通类)中创建
- 从一个接口中创建。
匿名内部类的语法
Java 中创建匿名内部类的语法格式如下
Test t = new Test() { // 成员声明 public void test_method() { ........ ........ } };
其中,Test
可以是一个接口,一个虚类或一个普通的类。
从匿名内部类的语法中可以看出,创建一个匿名类,就是创建一个类/接口的子类并创建该子类的实例。
Test t = new Test()
其实是创建一个实例的语法{}
中的语句,则是创建一个子类的语法。
为了让你更好的理解匿名内部类的创建语法,我们写一个范例来演示下。
我们先来看看常规情况下如何实现一个接口
JavaTester.java
// 创建一个 Age 接口 interface Age { int x = 21; void getAge(); } // Myclass 实现了 Age 接口,这是普通的创建子类方式 class MyClass implements Age { @Override public void getAge() { System.out.println("Age is "+x); } } public class JavaTester { public static void main(String[] args) { // Myclass 实现了 Age 接口 MyClass obj=new MyClass(); // 调用 getAge() 方法 obj.getAge(); } }
编译运行上面的代码,输出结果如下
[yufei@www.twle.cn java]$ javac JavaTester.java && java JavaTester Age is 21
在上面这段代码中,我们使用常规的 class MyClass implements Age
方法实现了接口 Age
。
如果这个 MyClass
类只使用一次,只是为了创建一个 obj
对象。我们可以不用这么复杂。我们只要使用匿名内部类来实现即可。就像下面代码中所展示的那样
Age oj1 = new Age() { @Override public void getAge() { System.out.print("Age is "+x); } };
看起来是不是简单明了的多了。
比较下两段代码,我们并没有对类的实现做任何更改。而匿名内部类的方式只更改了几个小的地方。
- 创建一个要继承的父类或要实现的接口的变量。
- 新建一个要继承的弗雷或要实现的接口的对象。
- 把我们要在子类中完成的代码全部放到一个大括号里面。
我们把前面一个范例给改一下,看看使用匿名内部类的完整版。
// 创建一个 Age 接口 interface Age { int x = 21; void getAge(); } public class JavaTester { public static void main(String[] args) { Age obj=new Age() { @Override public void getAge() { System.out.println("Age is "+x); } }; // 调用 getAge() 方法 obj.getAge(); } }
上面这段代码是可以运行的,且运行的结果和前面的范例一模一样。
匿名内部类的类型
有了匿名内部类,我们就不需要关心到底要给子类取一个什么名字。但烦恼也不是没有,就是匿名内部类的类型。
匿名内部类的类型,取决于如何声明和具体的行为,总的来说,有三种情况:
-
匿名内部类扩展自一个普通类
匿名内部类是可以扩展自一个普通类的。比如我们经常见到的匿名内部类扩展自
Thread
类。假设我们需要一个只使用一次的线程,但我们不想创建一个始终扩展 Thread 类的类。借助这种类型的匿名内部类,我们可以定义一个即时线程,如下所示
public class JavaTester { public static void main(String[] args) { Thread t = new Thread() { public void run() { System.out.println("Child Thread"); } }; t.start(); System.out.println("Main Thread"); } }
编译运行上面这段代码,输出结果如下
[yufei@www.twle.cn java]$ javac JavaTester.java && java JavaTester Main Thread Child Thread
-
匿名内部类实现了一个接口
我们还可以创建实现了某个接口的匿名内部类。比如经常见到的实现了
Runnable
接口的匿名内部类。public class JavaTester { public static void main(String[] args) { Runnable r = new Runnable() { public void run() { System.out.println("Child Thread"); } }; new Thread(r).start(); System.out.println("Main Thread"); } }
编译运行上面这段代码,输出结果如下
[yufei@www.twle.cn java]$ javac JavaTester.java && java JavaTester Child Thread Main Thread
-
作为方法或构造函数参数的匿名内部类
还有一种匿名内部类,比较少见的。当一个方法或构造函数需要某个类的对象时。我们可以创建一个匿名内部类作为它们的参数。
例如,因为
Thread
类的构造函数接受一个Runnable
类型的参数,因此,我们还可以对上面的实例进行简化。public class JavaTester { public static void main(String[] args) { new Thread(new Runnable() { public void run() { System.out.println("Child Thread"); } } ).start(); System.out.println("Main Thread"); } }
编译运行上面这段代码,输出结果如下
[yufei@www.twle.cn java]$ javac JavaTester.java && java JavaTester Child Thread Main Thread
普通类和匿名内部类的区别?
-
普通类可以实现任意数量的接口,但匿名内部类一次只能实现一个接口。
-
常规类可以扩展自一个类并同时实现任意数量的接口。但匿名内部类只可以扩展自一个类,或只实现一个接口,但两者不可兼得。
-
对于常规/普通类,我们可以编写任意数量的构造函数,但我们不能为匿名内部类编写任何构造函数,因为匿名类没有任何名称,而定义构造函数类名和构造函数名必须相同。
匿名内部类如何访问外部变量?
跟本地类一样,匿名类可以捕获变量: 它们对相同作用域范围的局部变量具有相同的访问权限。
- 匿名类可以访问外部类的成员。
- 匿名类无法访问封闭作用域范围中未声明为
final
或者effectively final
的局部变量 - 与嵌套类一样,匿名类中的类型(例如变量)的声明会影响封闭范围中具有相同名称的任何其他声明