浅谈Java中的四种内部类
概述
在 Java 中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、静态内部类和匿名内部类。
成员内部类
从名字就可以看出,这种内部类是作为类的成员而存在的,其定义位于类的内部。
//外部类Outer public class Outer { //外部类成员属性 private int a = 3; //创建一个内部类的实例 private Inner in; public Outer(){ in = new Inner(); } public int getInnerA(){ // 引用内部类的变量,需通过实例 return in.a; } //成员内部类Inner public class Inner { //内部类成员属性 public int a = 2; //内部类成员方法 public void doSomething() { // 调用外部类的方法或属性:外部类名.this.成员方法/属性。 System.out.println(Outer.this.a);//3 System.out.println(a);//2 } } } //与外部类Outer平级的类Extender继承内部类Inner class Extender extends Outer.Inner{ //构造方法中需要传入父类的实例对象 public Extender(Outer outer){ //外部类实例名.super(内部类参数列表) outer.super(); } } public class Test { public static void main(String[] args) { Outer outer=new Outer(); System.out.println(outer.getInnerA());//2 //外部类名.内部类名 实例名 = 外部类实例名.new 内部类构造方法(参数) //Outer.Inner inner = outer.new Inner(); //外部类名.内部类名 实例名 = new 外部类构造方法(参数).new 内部类构造方法(参数) Outer.Inner inner = new Outer().new Inner(); inner.doSomething(); Extender extender=new Extender(outer); extender.doSomething(); } }
注意事项
- 外部类无法直接访问成员内部类的方法和属性,需要通过内部类的一个实例来访问。
成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员),访问外部类的成员方法和属性时使用:外部类名.this.成员方法/属性。
- 创建成员内部类的实例使用:
直接方法:
- 外部类名.内部类名 实例名 = new 外部类构造方法(参数).new 内部类构造方法(参数)
- 外部类名.内部类名 实例名 = 外部类实例名.new 内部类构造方法(参数)
前提是已经创建了一个外部类的对象,可以理解为隐式地保存了一个引用,指向创建它的外部类对象
间接方法:
在外部类的方法中使用内部类,在主函数中只是调用外部类的方法。
- 成员内部类中不能存在static关键字,即,不能声明静态属性、静态方法、静态代码块等。
- 非静态内部类也可以定义静态成员但需要同时有final关键词修饰,静态方法鉴于无法用final修饰,仍必须是在静态内部类或者非内部类中定义。
- 与外部类平级的类继承内部类时,其构造方法中需要传入父类的实例对象。且在构造方法的第一句调用“外部类实例名.super(内部类参数)”。
- 内部类在编译之后生成一个单独的class文件,里面包含该类的定义,所以内部类中定义的方法和变量可以跟父类的方法和变量相同。
例如上面定义的四个类的class文件分别是:Outer.class、Outer$Inner.class、Extender.class和Test.class四个文件。
局部内部类
定义在代码块、方法体内、作用域(使用花括号“{}”括起来的一段代码)内的类叫局部内部类。
public class Outer { public void methodOuter(){ final int num=20; class Inner{//局部内部类 public void methodInner(){ System.out.println(num);//20 } } Inner inner=new Inner(); inner.methodInner(); } } public class Test { public static void main(String[] args) { Outer obj=new Outer(); obj.methodOuter(); } }
注意事项
- 局部内部类只能在代码代码块、方法体内和作用域中使用(如创建对象和使用类对象等)
- 局部内部类访问作用域内的局部变量,该局部变量需要使用final修饰。
注意:该局部变量也可以不用final修饰(从Java8+开始),是有效final的也行(就是只赋给局部变量一次值并且保证不变即可) - 可以使用abstract修饰,声明为抽象类。
补充
定义一个类时,权限修饰符规则:
- 外部类:public、(default)
- 成员内部类:public、protected、(default)、private
- 局部内部类:(default)
静态内部类
使用static修饰的成员内部类叫静态内部类。
与成员内部类的对比如下:
说明 |
成员内部类 |
静态内部类 |
静态成员 |
静态成员需同时有final关键词修饰 |
可以 |
静态方法 |
不可定义 |
可以 |
访问外部类非static属性/方法 |
外部类名.this.成员方法/属性 |
不可以 |
外部类访问内部类 |
需要通过内部类的一个实例来访问 |
需要通过内部类的一个实例来访问 |
创建实例 |
外部类名.内部类名 实例名 = 外部类实例名.new 内部类构造方法(参数) |
外部类名.内部类名 实例名 = new 外部类名.内部类名(参数) |
编译后的class文件 |
单独的class文件(内部类中的方法和变量可以跟父类的方法和变量同名),外部类$内部类.class |
单独的class文件(内部类中的方法和变量可以跟父类的方法和变量同名),外部类$内部类.class |
其他 |
与外部类平级的类继承内部类时,其构造方法中需要传入父类的实例对象。且在构造方法的第一句调用“外部类实例名.super(内部类参数)” |
无 |
匿名内部类
匿名内部类的定义和实例化形式如下:
new 父类构造方法(参数){ //注:该方法名必须在父类中已经存在 修饰符 返回参数类型 方法名(参数列表){ //... } }
注意事项
- 只能使用一次,创建实例之后,类定义会立即消失。
- 必须继承一个类(抽象的、非抽象的都可以)或者实现一个接口。如果父类(或者父接口)是抽象类,则匿名内部类必须实现其所有抽象方法。
- 不能是抽象类,因为匿名内部类在定义之后,会立即创建一个实例。
- 不能定义构造方法,匿名内部类没有类名,无法定义构造方法,但是,匿名内部类拥有与父类相同的所有构造方法。
- 可以定义代码块,用于实例的初始化,但是不能定义静态代码块。
- 可以定义新的方法和属性(不能使用static修饰),但是无法显式的通过“实例名.方法名(参数)”的形式调用,因为使用new创建的是“上转型对象”(即父类声明指向子类对象)。
- ①匿名内部类,在创建对象的时候,只能使用唯一一次。
如果希望多次创建对象,而且类的内容一样的话,那么就要使用单独定义的实现类了。
②匿名对象,在调用方法的时候,只能调用唯一一次。
如果希望同一个对象,调用多次方法,那么必须给对象起个名字。
③匿名内部类是省略了实现类/子类名称,但是匿名对象是省略了对象名称(匿名内部类和匿名对象不是一回事)。
说明 |
成员内部类 |
匿名内部类 |
静态成员 |
静态成员需同时有final关键词修饰 |
不可定义 |
静态方法 |
不可定义 |
不可定义 |
访问外部类非static属性/方法 |
外部类名.this.成员方法/属性 |
外部类名.this.成员方法/属性 |
外部类访问内部类 |
需要通过内部类的一个实例来访问 |
需要通过内部类的一个实例来访问 |
创建实例 |
外部类名.内部类名 实例名 = 外部类实例名.new 内部类构造方法(参数) |
如上:父类 实例名 = new 父类(){} |
编译后的class文件 |
单独的class文件(内部类中的方法和变量可以跟父类的方法和变量同名),外部类$内部类.class |
单独的class文件,使用类$数字.class |
其他 |
与外部类平级的类继承内部类时,其构造方法中需要传入父类的实例对象。且在构造方法的第一句调用“外部类实例名.super(内部类参数)” |
无 |