一、内部类的概念
在 Java 中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。内部类的出现,再次打破了Java单继承的局限性。
内部类一般分为4种:成员内部类、静态内部类、局部内部类和匿名内部类。
注意:内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两个类。
二、内部类的分析
-
内部类的位置
- 成员内部类:定义在外部类的内部,类似于成员方法,类和成员都不能使用static修饰
- 静态内部类:定义在外部类的内部,类似于静态方法,类使用static修饰,成员也可使用static修饰
- 局部内部类:定义在外部类的某一作用域内部(如静态代码块、代码块、静态方法、普通方法),类和成员都不能使用static修饰
- 匿名内部类:定义在外部类的任意位置(如静态变量、成员变量、静态代码块、代码块、静态方法、普通方法),类和成员都不能使用static修饰
-
内部类与外部类的依赖
- 成员内部类:依赖外部类,需要通过外部类对象来创建成员内部类的实例对象
- 静态内部类:不依赖外部类,可直接创建静态内部类的实例对象
- 局部内部类:与外部类无关,只能在该局部位置内创建局部内部类对象
- 匿名内部类:与外部类无关
-
内部类可以拥有的成员
- 成员内部类:可拥有非静态的成员,可使用任何修饰符修饰
- 静态内部类:可拥有任何的成员(静态、非静态),可使用任何修饰符修饰
- 局部内部类:可拥有非静态的成员,可使用任何修饰符修饰
- 匿名内部类:可拥有非静态的成员,可使用任何修饰符修饰
-
内部类访问外部类成员
- 成员内部类:可以访问外部类任何的成员(静态、非静态),可以是任何修饰符修饰的
- 静态内部类:可以访问外部类静态的成员,可以是任何修饰符修饰的
- 局部内部类:
- 若定义在静态部分中,可以访问外部类静态的成员,可以是任何修饰符修饰的;
- 若定义在非静态部分中,可以访问外部类任何的成员(静态、非静态),可以是任何修饰符修饰的
- 匿名内部类:
- 若定义在静态部分中,可以访问外部类静态的成员,可以是任何修饰符修饰的;
- 若定义在非静态部分中,可以访问外部类任何的成员(静态、非静态),可以是任何修饰符修饰的
-
外部类访问内部类成员
- 成员内部类:外部类想要访问成员内部类的成员,需要先创建成员内部类实例对象,再通过实例对象去访问。
- 静态内部类:
- 外部类可访问静态内部类的静态成员,可以是任何修饰符修饰的;
- 外部类想要访问静态内部类的非静态成员,需要先创建静态内部类实例对象,再通过实例对象去访问。
- 局部内部类:外部类不可访问局部内部类成员
- 匿名内部类:外部类想要访问匿名内部类的成员,需要能拿到匿名内部类实例对象,再通过实例对象去访问(但也只能访问当匿名内部类的父类原有成员,若是匿名内部类自定义的成员则还是无法访问)。
-
内部类成员和外部类成员重名
内部类使用成员时,当外部类的成员和内部类的成员重名时,会发生隐藏现象(外部类的成员会被隐藏),默认情况下访问的是内部类的成员(就近原则),所以一般建议:
- 在内部类中访问自己的静态成员:<内部类>.<内部类中的成员名>
- 在内部类中访问自己的非静态成员:this.<内部类中的成员名>
- 在内部类中访问外部类的静态成员:<外部类类名>.<外部类中的成员名>
- 在内部类中访问外部类的非静态成员:<外部类类名>.this.<外部类中的成员名>
-
内部类和外部类的编译
外部类和内部类在编译后是两个不同的class文件。
- 成员内部类:内部类的class文件名称:外部类名$内部类名.class
- 静态内部类:内部类的class文件名称:外部类名$内部类名.class
- 局部内部类:内部类的class文件名称:外部类名$编号+内部类名.class
- 匿名内部类:内部类的class文件名称:外部类名$编号.class
三、内部类的分类
内部类可分为四种:
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
1. 成员内部类
成员内部类是最普通的内部类,它的定义为位于另一个类的内部。类似于成员方法,成员内部类不能拥有静态域但是可以访问外部类的静态部分。
- 定义在外部类的内部,类似于成员方法,类和成员都不能使用static修饰
- 依赖外部类,需要通过外部类对象来创建成员内部类的实例对象
- 可拥有非静态的成员,可使用任何修饰符修饰
- 可以访问外部类任何的成员(静态、非静态),可以是任何修饰符修饰的
- 外部类想要访问成员内部类的成员,需要先创建成员内部类实例对象,再通过实例对象去访问
- 内部类使用成员时,当外部类的成员和内部类的成员重名时,会发生隐藏现象(外部类的成员会被隐藏),默认情况下访问的是内部类的成员(就近原则)
- 外部类和内部类在编译后是两个不同的class文件,内部类的class文件名称:外部类名$内部类名.class
public class OuterClass {
private static String a = "outer static filed";
private String b = "outer filed";
public static String method1() {
return "outer static method";
}
public String method2() {
return "outer method";
}
class InnerClass {
public void getOuter() {
// 1.成员内部类访问外部类的静态变量
System.out.println(OuterClass.a);
// 2.成员内部类访问外部类的静态方法
System.out.println(OuterClass.method1());
// 3.成员内部类访问外部类的成员变量
System.out.println(OuterClass.this.b);
System.out.println(b);// 不建议使用这种方式
// 4.成员内部类访问外部类的普通方法
System.out.println(OuterClass.this.method2());
System.out.println(method2());// 不建议使用这种方式
}
}
}
public static void main(String[] args) {
// 方式1:
OuterClass.InnerClass innerClass = new OuterClass().new InnerClass();
// 方式2:
OuterClass outerClass = new OuterClass();
InnerClass innerClass1 = outerClass.createInnerClass();
}
创建成员内部类实例
格式:外部类名.内部类名 成员内部类对象名 = new 外部类名().new 内部类名()
2. 静态内部类
静态内部类也是定义在另一个类里面的类,使用static修饰,类似于静态方法,静态内部类不需要外部类对象产生就能使用,静态内部类只能访问外部类的静态部分,不能访问外部类的实例部分。
- 定义在外部类的内部,类似于静态方法,类使用static修饰,成员也可使用static修饰
- 不依赖外部类,可直接创建静态内部类的实例对象
- 可拥有任何的成员(静态、非静态),可使用任何修饰符修饰
- 可以访问外部类静态的成员,可以是任何修饰符修饰的
- 外部类可访问静态内部类的静态成员,可以是任何修饰符修饰的;
- 外部类想要访问静态内部类的非静态成员,需要先创建静态内部类实例对象,再通过实例对象去访问。
- 内部类使用成员时,当外部类的成员和内部类的成员重名时,会发生隐藏现象(外部类的成员会被隐藏),默认情况下访问的是内部类的成员(就近原则)
- 外部类和内部类在编译后是两个不同的class文件,内部类的class文件名称:外部类名$内部类名.class
public class OuterClass {
private static String a = "outer static filed";
private String b = "outer filed";
public static String method1() {
return "outer static method";
}
public String method2() {
System.out.println(b);
return "outer method";
}
static class InnerClass {
private static String b = "inner filed";
public String method2() {
return "outer method";
}
public void getOuter() {
System.out.println(a);
}
}
}
创建静态内部类实例
格式:外部类名.内部类名 成员内部类对象名 = new 外部类名.内部类名()
public class Test {
public static void main(String[] args) {
OuterClass.InnerClass innerClass = new OuterClass.InnerClass();
}
}
3. 局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内,类似于局部变量。
- 定义在外部类的某一作用域内部(如静态代码块、代码块、静态方法、普通方法),类和成员都不能使用static修饰
- 局部内部类与外部类无关,只能在该局部位置内创建局部内部类对象
- 可拥有非静态的成员,可使用任何修饰符修饰
- 若定义在静态部分中,可以访问外部类静态的成员,可以是任何修饰符修饰的;
- 若定义在非静态部分中,可以访问外部类任何的成员(静态、非静态),可以是任何修饰符修饰的
- 外部类不可访问局部内部类成员
- 内部类使用成员时,当外部类的成员和内部类的成员重名时,会发生隐藏现象(外部类的成员会被隐藏),默认情况下访问的是内部类的成员(就近原则)
- 外部类和内部类在编译后是两个不同的class文件,内部类的class文件名称:外部类名$编号+内部类名.class
public class OuterClass {
private static String a = "outer static filed";
private String b = "outer filed";
static {
class InnerClass1 {
public void method2() {
System.out.println(a);
}
}
}
{
class InnerClass1 {
public void method2() {
System.out.println(a);
System.out.println(b);
}
}
}
public static void method1() {
class InnerClass1 {
public void method2() {
System.out.println(a);
}
}
}
public void method2() {
class InnerClass1 {
public void method2() {
System.out.println(a);
System.out.println(b);
}
}
InnerClass1 innerClass1 = new InnerClass1();
}
}
创建局部内部类实例
在局部内部类的作用域内通过new对象的方式创建对象。
格式:局部内部类名 局部内部类对象名 = 局部内部类名();
4. 匿名内部类
匿名内部类应该是我们编写代码时用得最多的,它满足了我们动态自定义代码的需求。
匿名内部类可以看作是特殊的局部内部类,其本质就是一个继承/实现了某个类(接口,普通类,抽象类)的子类匿名对象。
- 匿名内部类没有名称,没有构造函数,且使用者无法创建构造函数,但是实际上JDK为匿名内部类生成了构造函数
- 定义在外部类的任意位置(如静态变量、成员变量、静态代码块、代码块、静态方法、普通方法),类和成员都不能使用static修饰
- 匿名内部类与外部类无关
- 可拥有非静态的成员,可使用任何修饰符修饰
- 若定义在静态部分中,可以访问外部类静态的成员,可以是任何修饰符修饰的;
- 若定义在非静态部分中,可以访问外部类任何的成员(静态、非静态),可以是任何修饰符修饰的
- 外部类想要访问匿名内部类的成员,需要能拿到匿名内部类实例对象,再通过实例对象去访问(但也只能访问当匿名内部类的父类原有成员,若是匿名内部类自定义的成员则还是无法访问)。
- 外部类和内部类在编译后是两个不同的class文件,内部类的class文件名称:外部类名$编号.class
匿名内部类的定义
格式:new 接口名|类名(){重写方法}
如果重写方法为非必要的,原则上是可以没有重写方法部分的。
public class Test {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
}
};
Runnable runnable1 = new Runnable() {
@Override
public void run() {
}
};
}
}
匿名内部类与Lambda表达式
从Java8开始,引入了Lambda表达式,将代码块作为参数使用更简洁的代码来创建只有一个抽象方法的接口(这种接口被称为函数式接口)的实例。
Lambda表达式的代码块就是实现接口方法的方法体。
public class Test {
public static void main(String[] args) {
Runnable runnable = () -> {
System.out.println("run.... ");
};
}
}
Lambda代码块写的是Runnable接口的run方法的方法体。
两者的不同之处:
- 使用匿名内部类方式定义的内部类:编译之后,会生成一个单独的class字节码文件(外部类名$编号.class);
- 使用Lambda表达式创建的内部类:编译之后,不会生成一个单独的class字节码文件,对应的字节码会在运行的时候动态生成。