四种内部类
在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
广泛意义上的内部类一般来说包括这四种:静态内部类、成员内部类、局部内部类、匿名内部类
成员内部类看起来像是外部类的一个成员,所以内部类可以拥有private、public等访问权限修饰;当然,也可以用static来修饰。成员内部类分为:
- 静态成员内部类:使用static修饰类;
- 非静态成员内部类:未用static修饰类
在没有特别说明是静态成员内部类时,成员内部类指的就是非静态成员内部类;
静态内部类:
使用static修饰的内部类我们称之为静态内部类,我们要知道只要是static修饰的类那它一定是内部类,不可能是外部类。静态内部类与非静态内部类之间存在一个最大的区别,非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类的对象,但是静态内部类却没有。
- 它的创建是不需要依赖于外围类的对象
- 它不能使用任何外围类的非static成员变量和方法
- 静态内部类内允许有static属性、方法
class OutClass {
//。。。
static class InnerClass {
static String test = "test";
int a = 1;
static void fun1() {}
void fun2() {}
}
}
非静态成员内部类:
非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类的对象
成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量
外部类.this.成员方法
public class Circle {
private double radius = 0.0;
public static int count = 1;
public Circle(double radius) {
this.radius = radius;
}
public class Draw {//内部类
public void drawSahpe() {
System.out.println(radius);//外部类的private成员
System.out.prinlt(count);//外部类的静态成员
}
}
}
创建内部类对象:
成员内部类是依附外部类而存在的,所以要创建成员内部类的对象,前提是必须存在一个外部类的对象。通常有如下两种方法:
public class OutClass {
private InnerClass getInner() {
return new InnerClass();
}
public class InnerClass{}
public static void main(String... str) {
OutClass out = new OutClass();
InnerClass inner1 = out.getInner();
InnerClass inner2 = out.new InnerCLass();
}
}
外部类访问成员内部类信息:
同样,外部类也可以访问内部类的所有成员变量和方法(包括private),但外部类想访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:
public class OutClass {
public static int count = 12;
private double radius;
public OutClass(double radius) {
this.radius = radius;
}
public class InnerClass {//内部类
public String name = "test";
public void test() {
System.out.println(count);//访问外部类成员
System.out.println(radius);//访问外部类成员
}
}
public static void main() {
OutClass out = new OutClass(1.2);
InnerClass inner = out.new InnerClass();
inner.test();//内部类方法
inner.name = "my test";//内部类属性
}
}
成员内部类中不能存在任何static的变量和方法:
对于成员内部内并不是完全不能出现static字段的,如果使用final和static同时修饰一个属性字段,并且这个字段是基本类型或者String类型的,那么是可以编译通过的。原因:
非静态成员内部类要依赖外部类,所以不能有static变量;
但在类加载中,对于final static的变量是存放在常量池中的,不涉及到类的加载;
应用:
api接口响应数据中,我们可以使用成员内部类这种方式来定义复杂的结构,实现json序列化:
@Data
@EqualsAndHashCode
public class PaoPaoMallCrowdActivityInfoResponse {
private String code;
private String msg;
private PaoPaoMallCrowdActivityInfoResponseData data;
public boolean isSuccess() {
return "A00000".equals(code) && data!=null;
}
@Data
public class PaoPaoMallCrowdActivityInfoResponseData {
private long activityId;
private int orderNum;
private int targetNum;
private long startTime;
private long endTime;
}
}
静态内部类实现单例:
public class SingleTon{
private SingleTon(){}
private static class SingleTonHoler{
private static SingleTon INSTANCE = new SingleTon();
}
public static SingleTon getInstance(){
return SingleTonHoler.INSTANCE;
}
外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。即当SingleTon第一次被加载时,并不需要去加载SingleTonHoler,只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用getInstance()方法会导致虚拟机加载SingleTonHoler类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
局部内部类:
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
public void test() {
class InnerClass {
private String name;
final static String test = "1";
public InnerClass(String name) {
super();
this.name = name;
}
public void say(String str) {
System.out.println(name+":"+str);
}
}
new InnerClass("test").say("hello");
}
匿名内部类:
匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名。命令行执行 javac Demo.java,在Demo类同目录下可以看到生成了两个class文件。
一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
public class Demo {
private Runnable runnable = new Runnable() {
@override
public void run() {}
}
}
访问外部局部变量
Java禁止在匿名内部类中修改外部局部变量。 因为:
查看对应的class文件
看到对于非final局部变量,是通过构造器的方式传递进来的
// Demo.java
public void run() {
int age = 10;
Runnable runnable = new Runnable() {
@Override
public void run() {
int myAge = age + 1;
System.out.println(myAge);
}
};
}
package inner;
class Demo$1 implements Runnable {
Demo$1(Demo var1, int var2) {
this.this$0 = var1;
this.val$age = var2;
}
public void run() {
int var1 = this.val$age + 1;
System.out.println(var1);
}
}
age修改为final, 编译器做了优化,既然age是final的,所以在编译期间是确定的,直接将+1优化为11
public void run() {
final int age = 10;
Runnable runnable = new Runnable() {
@Override
public void run() {
int myAge = age + 1;
System.out.println(myAge);
}
};
}
class Demo$1 implements Runnable {
Demo$1(Demo var1) {
this.this$0 = var1;
}
public void run() {
byte var1 = 11;
System.out.println(var1);
}
}
访问外部成员变量
将age改为Demo的成员变量,注意没有加任何修饰符,是包级访问级别。
编译器直接通过外部类的引用操作age,没毛病,由于age是包访问级别,所以这样是最高效的。 如果将age改为private,编译器会在Demo类中生成二个方法,分别用于读取age和设置age
public class Demo {
int age = 10;
public void run() {
Runnable runnable = new Runnable() {
@Override
public void run() {
int myAge = age + 1;
System.out.println(myAge);
age = 20;
}
};
}
}
class Demo$1 implements Runnable {
Demo$1(Demo var1) {
this.this$0 = var1;
}
public void run() {
int var1 = this.this$0.age + 1;
System.out.println(var1);
this.this$0.age = 20;
}
}
需要注意,匿名内部类使用中,使用Lambda表达式并不是匿名内部类的语法糖,它是基于invokedynamic指令,在运行时使用ASM生成类文件来实现的。