Java
语言基础

Java内部类

简介:内部类有时候也被称为嵌套类(nested class),是定义在一个类中或者方法中的类,可以是其他类的一个成员,也可以在一个语句块的内部定义。

内部类有时候也被称为嵌套类(nested class),是定义在一个类中或者方法中的类,可以是其他类的一个成员,也可以在一个语句块的内部定义。内部类有以下特性:

  1. 内部类对象可以访问创建它的对象的实现,包括私有数据。
  2. 对于同一个包中的其他类来说,内部类可以隐藏起来(使用private修饰,外部类不能用private修饰)。
  3. 匿名内部类可以方便地用在回调方法(Callback Method)中。

1. 成员内部类

【成员内部类】定义在另一个类的内部,而且与成员方法和属性平级叫成员内部类。

  • 成员内部类中不能有静态成员,即不存在static关键字,不能声明静态属性、静态方法、静态代码块等。
  • 在成员内部类中访问外部类的成员方法和属性,要使用外部类名.this.成员方法外部类名.this.成员属性的形式。
  • 创建成员内部类的实例使用外部类名.内部类名 实例名 = 外部类实例名.new 内部类构造方法(参数)的形式。在创建内部类实例之前需要先创建外部类的实例对象。
  • public class NestedClassTest {
  • public static void main(String[] args) {
  • Outer outer = new Outer("test");
  • Outer.Inner inner = outer.new Inner(20);
  • }
  • }
  • class Outer {
  • Outer(String str) {
  • }
  • protected class Inner {
  • public Inner(int a) {
  • }
  • }
  • }
  • 成员内部类可以可以使用public、protected、default、private、static、final、abstract来修饰,当然了,被static修饰的内部类,是静态内部类。

被private修饰的内部类不能在包含该内部类的外部类外使用,错误用法如下:

  • class Outer {
  • //private修饰的内部类
  • private class Inner{
  • public void InnerMethod() {
  • System.out.println("这个是InnerMethod方法的输出");
  • }
  • }
  • public void outerMethod() {
  • System.out.println("这个是outerMethod方法的输出");
  • }
  • public Outer() {
  • System.out.println("这个是Outer方法的输出");
  • }
  • }
  • public class NestedClassTest {
  • public static void main(String[] args) {
  • //此处将报错
  • Outer.Inner in = new Outer().new Inner();
  • }
  • }

以上用法会报错:

  • Exception in thread "main" java.lang.Error: Unresolved compilation problems:
  • The type Outer.Inner is not visible
  • The type Outer.Inner is not visible

以private修饰的正确用法如下:

  • class Outer {
  • //private修饰的内部类
  • private class Inner{
  • public void InnerMethod() {
  • System.out.println("这个是InnerMethod方法的输出");
  • }
  • }
  • public void outerMethod() {
  • Inner in = new Inner();
  • in.InnerMethod();
  • System.out.println("这个是outerMethod方法的输出");
  • }
  • public Outer() {
  • System.out.println("这个是Outer方法的输出");
  • }
  • }
  • public class NestedClassTest {
  • public static void main(String[] args) {
  • Outer out = new Outer();
  • out.outerMethod();
  • }
  • }

输出:

  • 这个是Outer方法的输出
  • 这个是InnerMethod方法的输出
  • 这个是outerMethod方法的输出

而以protected修饰的内部类,除了不同包的类不能访问,其他类都能访问。以protected修饰的内部类正确用法:

  • class Outer {
  • //protected修饰的内部类
  • protected class Inner{
  • public void InnerMethod() {
  • System.out.println("这个是InnerMethod方法的输出");
  • }
  • }
  • public void outerMethod() {
  • Inner in = new Inner();
  • in.InnerMethod();
  • System.out.println("这个是outerMethod方法的输出");
  • }
  • public Outer() {
  • System.out.println("这个是Outer方法的输出");
  • }
  • }
  • public class NestedClassTest {
  • public static void main(String[] args) {
  • Outer.Inner in = new Outer().new Inner();
  • in.InnerMethod();
  • }
  • }

输出:

  • 这个是Outer方法的输出
  • 这个是InnerMethod方法的输出
  • 内部类在编译之后生成一个单独的class文件,里面包含该类的定义,所以内部类中定义的方法和变量可以跟父类的方法和变量相同。例如上面定义的三个类的class文件分别是:NestedClassTest.classOuter.classOuter$Inner.class三个文件。内部类的定义文件名形式是外部类定义名$内部类定义名.class,即Outer$Inner.class。该文件反编译之后的代码是:
  • public class Outer$Inner {
  • public Outer$Inner(Outer paramOuter, int a) {
  • }
  • }
  • 外部类无法直接访问成员内部类的方法和属性,只能通过内部类实例名.方法名(参数)内部类实例名.属性名的形式访问内部类的方法和属性 。
  • public class NestedClassTest {
  • public static void main(String[] args) {
  • Outer outer = new Outer("test");
  • outer.test();
  • }
  • }
  • class Outer {
  • private Inner inner;
  • Outer(String str) {
  • // 创建内部类实例
  • inner = new Inner(10);
  • }
  • public void test() {
  • // 只能通过实例访问方法和属性
  • inner.print();
  • }
  • protected class Inner {
  • public Inner(int a) {
  • }
  • public void print() {
  • System.out.println("Inner");
  • }
  • }
  • }
  • 与外部类平级的类继承内部类时,其构造方法中需要传入父类的实例对象。且在构造方法的第一句调用外部类实例名.super(内部类参数)。如:
  • public class NestedClassTest {
  • public static void main(String[] args) {
  • Outer outer = new Outer("test");
  • new Extender(outer, 10);
  • }
  • }
  • class Outer {
  • Outer(String str) {
  • }
  • protected class Inner {
  • public Inner(int a) {
  • }
  • }
  • }
  • class Extender extends Outer.Inner {
  • //此处传递了外部类的实例
  • public Extender(Outer outer, int a) {
  • // 外部类实例名.super(内部类参数列表)
  • outer.super(a);
  • }
  • }

2. 静态内部类

【静态内部类】使用static修饰的成员内部类叫静态内部类。

  • 静态内部类可以有静态和非静态的成员。
  • 静态内部类与外部类没有任何关系,只是在生成类名和类定义时有影响。静态内部类可以看做是与外部类平级的类。使用方式与外部类平级的类完全相同。
  • 静态内部类不能访问外部类的非静态的属性和方法。外部类不能访问静态内部类的非静态的属性和方法。
  • 静态内部类可用必须以外部类名.内部类名 实例名 = new 外部类名.内部类名(参数)实例化得到静态内部类的对象,此时包含该内部类的外部类并未实例化,未调用其构造器。使用外部类构造器的初始化的方式实例化静态内部类是不可行的,即外部类名.内部类名 实例名 = new 外部类名(参数).内部类名(参数)将报错。且可以使用外部类名.内部类名.变量名外部类名.内部类名.方法(参数)直接调用静态内部类中的静态成员。示例如下:
  • class Outer {
  • //静态内部类
  • static class Inner{
  • public static int i = 10;
  • public static void staticInnerMethod(){
  • System.out.println("这个是staticInnerMethod方法的输出");
  • }
  • public Inner() {
  • System.out.println("这个是Inner方法的输出");
  • }
  • }
  • public void outerMethod() {
  • System.out.println("这个是outerMethod方法的输出");
  • }
  • public Outer() {
  • System.out.println("这个是Outer方法的输出");
  • }
  • }
  • public class NestedClassTest {
  • public static void main(String[] args) {
  • //静态内部类的实例化
  • Outer.Inner in = new Outer.Inner();
  • //此实例化方式会报错
  • Outer.Inner in = new Outer().Inner();
  • //调用静态内部类静态成员
  • System.out.println(Outer.Inner.i);
  • Outer.Inner.staticInnerMethod();
  • }
  • }

输出如下:

  • 这个是Inner方法的输出
  • 10
  • 这个是staticInnerMethod方法的输出

3. 局部内部类

【局部内部类】定义在代码块、方法体内、作用域(使用花括号{}括起来的一段代码)内的类叫局部内部类。

  • 局部内部类只能在代码块、方法体内和作用域中使用(创建对象和使用类对象),如:
  • public class NestedClassTest {
  • public static void main(String[] args) {
  • }
  • }
  • class Outer {
  • Outer(String str) {
  • }
  • {
  • class Inner {
  • public Inner(int a) {
  • }
  • }
  • System.out.println("Inner.class.getName():" + Inner.class.getName());
  • }
  • public void test() {
  • // 这是错误的,局部内部类只在本作用域中可见,报错信息为:Inner cannot be resolved to a type
  • // System.out.println("Inner.class.getName():"+Inner.class.getName());
  • }
  • }
  • 局部内部类不能有静态成员,不能有访问说明符(public、protected和private),因为它不是外部类的一部分,但是它可以访问当前代码块中的常量,以及此外部类的所有成员。使用public、protected和private修饰时提示错误信息 Illegal modifier for the local class Inner; only abstract or final is permitted
  • 局部内部类访问外部类的属性和方法使用外部类名.this.属性名外部类名.this.方法名(参数)的形式。
  • 局部内部类访问作用域内的局部变量,该局部变量需要使用final修饰。
  • public class NestedClassTest {
  • public static void main(String[] args) {
  • Outer outer = new Outer();
  • outer.test(10);
  • }
  • }
  • class Outer {
  • public void test(int i) { //此处的i由编辑器隐式地声明为final类型了
  • class LocalInnerClass {
  • public void localTest() {
  • System.out.println("Inner的localTest方法被调用");
  • //此处调用i++将报错Local variable i defined in an enclosing scope must be final or effectively final
  • System.out.println(i++);
  • }
  • }
  • //方法中可以直接初始化局部内部类
  • LocalInnerClass in = new LocalInnerClass();
  • in.localTest();
  • }
  • }
  • 外部类不能直接访问局部内部类,但可通过外部类的方法来间接访问内部类。
  • public class NestedClassTest {
  • public static void main(String[] args) {
  • Outer outer = new Outer();
  • //外部类通过自身的方法来间接访问局部内部类
  • outer.test(10);
  • }
  • }
  • class Outer {
  • public void test(int i) {
  • class LocalInnerClass {
  • public void localTest() {
  • System.out.println("Inner的localTest方法被调用");
  • }
  • }
  • LocalInnerClass in = new LocalInnerClass();
  • in.localTest();
  • }
  • }
  • 局部内部类可以用abstract及final修饰。

4. 匿名内部类

【匿名内部类】匿名内部类定义和实例化形式如下:

  • new 父类构造方法(参数){
  • //注:该方法名必须在父类中已经存在
  • 修饰符 返回参数类型 方法名(参数列表){
  • }
  • }
  • 匿名内部类只能和new连用,用于创建一个实例。匿名内部类只能使用一次,创建实例之后,类定义会立即消失(想要多次使用需要用到反射)。
  • 匿名内部类必须继承一个类(抽象的、非抽象的都可以)或者实现一个接口。如果父类(或者父接口)是抽象类,则匿名内部类必须实现其所有抽象方法。如:
  • package com.coderap.Test;
  • public class NestedClassTest {
  • public static void main(String[] args) {
  • // 定义一个匿名内部类,并实例化对象
  • Test test = new Test() {
  • @Override
  • public void print() {
  • System.out.println("匿名内部类实现父类所有的抽象方法!");
  • }
  • };
  • test.print();
  • System.out.println("匿名内部类的类名:test.getClass().getName():" + test.getClass().getName());
  • System.out.println("父类的类名:Test.class.getName():" + Test.class.getName());
  • System.out.println("test.getClass().equals(Test.class):" + test.getClass().equals(Test.class));
  • System.out.println("test.getClass().getSuperclass().equals(Test.class):"
  • + test.getClass().getSuperclass().equals(Test.class));
  • }
  • }
  • abstract class Test {
  • public abstract void print();
  • }

运行结果:

  • 匿名内部类实现父类所有的抽象方法!
  • 匿名内部类的类名:test.getClass().getName():com.coderap.Test.NestedClassTest$1
  • 父类的类名:Test.class.getName():com.coderap.Test.Test
  • test.getClass().equals(Test.class):false
  • test.getClass().getSuperclass().equals(Test.class):true

NestedClassTest$1.class文件反编译之后的结果如下,证明它确实是继承了父类:

  • class NestedClassTest$1 extends Test {
  • public void print() {
  • System.out.println("匿名内部类实现父类所有的抽象方法!");
  • }
  • }
  • 匿名内部类不能是抽象类,因为匿名内部类在定义之后,会立即创建一个实例。
  • 匿名内部类不能定义构造方法,匿名内部类没有类名,无法定义构造方法,但是,匿名内部类拥有与父类相同的所有构造方法。
  • 匿名内部类中可以定义代码块,用于实例的初始化,但是不能定义静态代码块。如下:
  • public class NestedClassTest {
  • public static void main(String[] args) {
  • // 定义一个匿名内部类,并实例化对象
  • Test test = new Test() {
  • {
  • System.out.println("匿名内部类的代码块");
  • }
  • public void print() {
  • System.out.println("匿名内部类的print方法");
  • }
  • };
  • test.print();
  • }
  • }
  • class Test {
  • public void print() {
  • System.out.println("class Test的print方法");
  • }
  • }

输出结果为:

  • 匿名内部类的代码块
  • 匿名内部类的print方法
  • 匿名内部类中可以定义新的方法和属性(不能使用static修饰),但是无法显式的通过实例名.方法名(参数)的形式调用,因为使用new创建的是“上转型对象”。

【注意】上转型对象特点如下:

  1. 父类声明指向子类对象,形式:父类名(父接口名) 上转型实例名 = new 子类构造方法(参数)
  2. 子类可以重写父类的方法,上转型实例调用的方法只能是子类重写父类的方法或者父类的方法,不能调用子类独有的方法;
  3. 上转型对象可以强制转换成子类对象:形式:子类名 实例名 = (子类名)上转型对象
  4. 转换成子类对象之后,与new 子类构造方法(参数)形式定义的对象无区别;
  5. 虽然不能通过实例名.方法名(参数)的形式调用子类独有的方法,但是可以通过反射来调用:
  • import java.lang.reflect.InvocationTargetException;
  • import java.lang.reflect.Method;
  • public class NestedClassTest {
  • public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException,
  • IllegalAccessException, InvocationTargetException {
  • // 定义一个匿名内部类,并实例化对象
  • Test test = new Test() {
  • // 匿名内部类中可以定义属性
  • private int a;
  • // 匿名内部类中可以定义方法
  • public void xxx() {
  • System.out.println("xxx方法");
  • }
  • // 匿名内部类中可以重写父类的方法
  • public void print() {
  • a = 10;
  • }
  • };
  • // 通过反射调用子类方法
  • Class c = test.getClass();
  • Method m = c.getMethod("xxx");
  • m.invoke(test);
  • }
  • }
  • class Test {
  • public void print() {
  • System.out.println("父类方法");
  • }
  • }
  • 匿名内部类访问外部类的局部变量,则该局部变量需要使用final声明。匿名内部类中访问外部类的成员方法和属性使用外部类名.this.方法名(参数)内部类名.this.属性名的形式。
  • public class NestedClassTest {
  • private int a;
  • public static void main(String[] args) {
  • new InnerClassTest().go();
  • }
  • public void go() {
  • a = 100;
  • // 外部类的局部变量
  • final int b = 10;
  • Test test = new Test() {
  • public void print() {
  • // 匿名内部类访问外部类的属性
  • System.out.println("InnerClassTest.this.a==" + InnerClassTest.this.a);
  • // 匿名内部类中访问外部类的局部变量
  • System.out.println("b==" + b);
  • }
  • };
  • test.print();
  • }
  • }
  • abstract class Test {
  • public abstract void print();
  • }
  • 匿名内部类不能重写父类的静态方法。