一、什么是内部类
将一个类定义置入另一个类定义中,这个类就叫做内部类。简单来说就是在一个类的内部再定义一个类。
二、为什么要用内部类
- 内部类与外部类之间可以很方便地访问彼此的私有域。
- 内部类是另外的一种封装,对外部的其他类隐藏。
- 内部类可以打破Java单继承的局限。
缺点就是结构复杂。
如何使用内部类呢?
- 在外部类外部生成非静态内部类实例
Outer.Inner in = new Outer().new Inner();
- 在外部类外部生成静态内部实例
Outer.Inner in = new Outer.Inner();
- 在外部类内部生成内部类实例
Inner in = new Inner();
三、内部类的分类
Java内部类主要分为四种:成员内部类、静态内部类、方法内部类、匿名内部类。
3.1 成员内部类
成员内部类是内部类中最普通的一种,对比成员方法:
public class Outer1 {
private String name = "test";
public static int age = 20;
class Inner {
public void getOuter() {
System.out.println("name: " + name + " age: " + age);
}
}
public static void main(String[] args) {
Inner inner = new Outer1().new Inner();
inner.getOuter();
}
}
输出:
name: test age: 20
- 成员内部类中不允许存在static域,正如成员方法中不允许存在static域。
- 成员内部类依附于外部类,只有创建了外部类实例才能创建内部类实例。
3.2 静态内部类
static修饰的内部类称之为静态内部类。
public class Outer3 {
public static String name = "张三";
private int age = 20;
static class Inner{
private int num = 10;
private static int height = 170;
public void getOuter() {
System.out.println("name: " + name + " num: " + num);
}
}
public static void main(String[] args) {
new Inner().getOuter();
System.out.println(Inner.height);
}
}
输出:
name: 张三 num: 10
170
- 静态内部类的创建不需要依赖外部的类,可直接创建。
- 静态内部类不可以使用外部类的任何非static的域。
3.3 局部内部类
局部内部类就是定义在方法里的类。
public class Outer2 {
private int num = 10;
public void getNum(final int tmp) {
final int b = 100;
class Inner {
public void test() {
System.out.println(b);
System.out.println(num);
System.out.println(tmp);
}
}
new Inner().test();
}
public static void main(String[] args) {
new Outer2().getNum(20);
}
}
输出:
100
10
20
- 局部内部类不允许使用任何访问权限修饰符。
- 局部内部类对外部完全隐藏,只有定义了这个类的方法才可以访问。
- jdk1.8之前局部内部类中的方法如果想使用定义它的方法的形参,则该参数必须使用final声明。
3.4 匿名内部类
匿名内部类就是一个没有名字的局部内部类。
public interface MyInterface {
void test();
}
public class Outer4 {
private int age = 10;
public MyInterface display(final int tmp) {
final int a = 10;
return new MyInterface() {
@Override
public void test() {
System.out.println("age: " + age + " tmp: " + tmp);
System.out.println(a);
}
};
}
public static void main(String[] args) {
Outer4 outer4 = new Outer4();
outer4.display(20).test();
}
}
输出:
age: 10 tmp: 20
10
- 匿名内部类必须继承一个抽象类或者实现一个接口。
- 匿名内部类没有类名,因此没有构造方法。
- 在JDK1.8之前,如果我们在匿名内部类中需要访问局部变量,那么这个局部变量必须用final修饰符修饰。
四、内部类的相关面试问题
4.1 jdk1.8之前,局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?
用final修饰实际上就是为了保护数据的一致性。进一步就是为了保证代码执行结果的正确性。
Outer2.java:
public class Outer2 {
private int num = 10;
public void getNum(final int tmp) {
final int b = 100;
class Inner {
public void test() {
System.out.println(b);
System.out.println(num);
System.out.println(tmp);
}
}
new Inner().test();
}
public static void main(String[] args) {
new Outer2().getNum(20);
}
}
使用 javac Outer2.java 命令进行编译,得到两个.class文件:Outer2$1Inner.class、Outer2.class。
再用 javap -private Outer2$1Inner 反编译内部类的字节码文件,得到:
class com.example.demo.inner.Outer2$1Inner {
final int val$tmp;
final com.example.demo.inner.Outer2 this$0;
com.example.demo.inner.Outer2$1Inner();
public void test();
}
可以看到方法的局部变量tmp被复制为内部类的成员变量。
那么问题来了:将局部变量复制为内部类的成员变量时,必须保证这两个变量的值是一样的,也就是如果在内部类中修改了成员变量,方法中的局部变量也要跟着改变,如何解决呢?
解决办法就是将局部变量设置成final的,禁止修改这个变量,从而就保证了内部类的成员变量和方法内的局部变量的一致性。