跳转至

java 内部类

1. 内部类

1.1 内部类定义

  把一个类定义在另一个类的内部称为内部类。

package com.cmz.demo;//声明包
class Outer//声明类
{
    private String info="hello World";//声明私有属性
    class Inner//声明类
    {
        public void print(){//打印输出的方法
        System.out.println(info);
    } 
}
public void fun(){//定义方法
    new Inner().print();//通过内部类调用方法
    } 
}
public class TestOuterAndInner2//测试类
{
    public static void main(String [] args){
    new Outer().fun();//调用方法
    }
}

内部类轻松访问外部类的私有属性

public class outer{ //外部类
    public class Inner{ //内部类
        public void print(){
            System.out.print("Hello World");
        }
    }
    public void show(){ //外部类的成员方法
        print(); // 调用内部类的成员方法
    }
}

例子
public class OUTER {
    String name = "张三";
    public class Inner{ //内部类
        String name = "李四";
        public void print(){
            System.out.println("外部类:name"+OUTER.this.name);
            System.out.println("外部类:name"+name);
        }
    }
    public static void main(String [] args){
        OUTER o = new OUTER();
        o.new Inner().print();
    }
}
运行结果是
外部类:name张三
外部类:name李四

1.2 在外部访问内部类

  语法

外部类 外部类对象=new 外部类();

外部类.内部类 内部类对象=外部类对象.new 内部类 ();

public static void main(String [] args){
    Outer out=new Outer();//创建外部类的对象
    Outer.Inner inner=out.new Inner();//创建内部类的对象
    inner.print();//访问内部类的方法
}
如果主方法在外部类内部,则可以省略Outer Inner inner=out.new Inner();

2. 内部类

  内部类在 Java 里面算是非常常见的一个功能了,在日常开发中我们肯定多多少少都用过,这里总结一下关于 Java 中内部类的相关知识点和一些使用内部类时需要注意的点。从种类上说,内部类可以分为四类:

  • 普通内部类
  • 静态内部类
  • 匿名内部类
  • 局部内部类
  • 方法内部类

2.1 普通内部类

这个是最常见的内部类之一了,其定义也很简单,在一个类里面作为类的一个字段直接定义就可以了,例:

定义格式:

public class InnerClassTest {

    public class InnerClassA {

    }
}
在这里 InnerClassA 类为 InnerClassTest 类的普通内部类,在这种定义方式下,普通内部类对象依赖外部类对象而存在,即在创建一个普通内部类对象时首先需要创建其外部类对象,我们在创建上面代码中的 InnerClassA 对象时先要创建 InnerClassTest 对象,例:

code

/**
 * @author summer
 * @create 2020-02-02 20:36
 */
public class InnerClassTest {

    public int outField1 = 1;
    protected int outField2 = 2;
    int outField3 = 3;
    private int outField4 = 4;

    public InnerClassTest() {
        // 在外部类对象内部,直接通过 new InnerClass(); 创建内部类对象
        InnerClassA innerObj = new InnerClassA();
        System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
        System.out.println("其内部类的 field1 字段的值为: " + innerObj.field1);
        System.out.println("其内部类的 field2 字段的值为: " + innerObj.field2);
        System.out.println("其内部类的 field3 字段的值为: " + innerObj.field3);
        System.out.println("其内部类的 field4 字段的值为: " + innerObj.field4);
    }

    public class InnerClassA {
        public int field1 = 5;
        protected int field2 = 6;
        int field3 = 7;
        private int field4 = 8;
//        static int field5 = 5; // 编译错误!普通内部类中不能定义 static 属性

        public InnerClassA() {
            System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
            System.out.println("其外部类的 outField1 字段的值为: " + outField1);
            System.out.println("其外部类的 outField2 字段的值为: " + outField2);
            System.out.println("其外部类的 outField3 字段的值为: " + outField3);
            System.out.println("其外部类的 outField4 字段的值为: " + outField4);
        }
    }

    public static void main(String[] args) {
        InnerClassTest outerObj = new InnerClassTest();
        // 不在外部类内部,使用:外部类对象. new 内部类构造器(); 的方式创建内部类对象
//        InnerClassA innerObj = outerObj.new InnerClassA();
    }
}
运行结果是
创建 InnerClassA 对象
其外部类的 outField1 字段的值为: 1
其外部类的 outField2 字段的值为: 2
其外部类的 outField3 字段的值为: 3
其外部类的 outField4 字段的值为: 4
创建 InnerClassTest 对象
其内部类的 field1 字段的值为: 5
其内部类的 field2 字段的值为: 6
其内部类的 field3 字段的值为: 7
其内部类的 field4 字段的值为: 8

我们注意到,内部类对象可以访问外部类对象中所有访问权限的字段,同时,外部类对象也可以通过内部类的对象引用来访问内部类中定义的所有访问权限的字段,后面我们将从源码里面分析具体的原因。

内部类特点:

  • 内部类可以方便的访问外部类的私有属性
  • 外部类不能访问内部类的私有属性
  • 内部类中不能定义静态属性
  • 如果外部类和内部类具有相同的成员变量戒方法,内部类默认访问自己的成员变量戒方法,如果要访问外部类的成员变量,需使用外部类名.this关键字.属性

2.2 静态内部类

我们知道,一个类的静态成员独立于这个类的任何一个对象存在,只要在具有访问权限的地方,我们就可以通过 类名.静态成员名 的形式来访问这个静态成员,同样的,静态内部类也是作为一个外部类的静态成员而存在,创建一个类的静态内部类对象不需要依赖其外部类对象。例:

code

public class InnerClassTest {
    public int field1 = 1;

    public InnerClassTest() {
        System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
        // 创建静态内部类对象
        StaticClass innerObj = new StaticClass();
        System.out.println("其内部类的 field1 字段的值为: " + innerObj.field1);
        System.out.println("其内部类的 field2 字段的值为: " + innerObj.field2);
        System.out.println("其内部类的 field3 字段的值为: " + innerObj.field3);
        System.out.println("其内部类的 field4 字段的值为: " + innerObj.field4);
    }

    static class StaticClass {

        public int field1 = 1;
        protected int field2 = 2;
        int field3 = 3;
        private int field4 = 4;
        // 静态内部类中可以定义 static 属性
        static int field5 = 5;

        public StaticClass() {
            System.out.println("创建 " + StaticClass.class.getSimpleName() + " 对象");
//            System.out.println("其外部类的 field1 字段的值为: " + field1); // 编译错误!!
        }
    }

    public static void main(String[] args) {
        // 无需依赖外部类对象,直接创建内部类对象
//        InnerClassTest.StaticClass staticClassObj = new InnerClassTest.StaticClass();
        InnerClassTest outerObj = new InnerClassTest();
    }
}

运行结果是
创建 InnerClassTest 对象
创建 StaticClass 对象
其内部类的 field1 字段的值为: 1
其内部类的 field2 字段的值为: 2
其内部类的 field3 字段的值为: 3
其内部类的 field4 字段的值为: 4

可以看到,静态内部类就像外部类的一个静态成员一样,创建其对象无需依赖外部类对象(访问一个类的静态成员也无需依赖这个类的对象,因为它是独立于所有类的对象的)。但是于此同时,静态内部类中也无法访问外部类的非静态成员,因为外部类的非静态成员是属于每一个外部类对象的,而本身静态内部类就是独立外部类对象存在的,所以静态内部类不能访问外部类的非静态成员,而外部类依然可以访问静态内部类对象的所有访问权限的成员,这一点和普通内部类无异。

2.3 匿名内部类

code

public class InnerClassTest {

    public int field1 = 1;
    protected int field2 = 2;
    int field3 = 3;
    private int field4 = 4;

    public InnerClassTest() {
        System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
    }
    // 自定义接口
    interface OnClickListener {
        void onClick(Object obj);
    }

    private void anonymousClassTest() {
        // 在这个过程中会新建一个匿名内部类对象,
        // 这个匿名内部类实现了 OnClickListener 接口并重写 onClick 方法
        OnClickListener clickListener = new OnClickListener() {
            // 可以在内部类中定义属性,但是只能在当前内部类中使用,
            // 无法在外部类中使用,因为外部类无法获取当前匿名内部类的类名,
            // 也就无法创建匿名内部类的对象
            int field = 1;

            @Override
            public void onClick(Object obj) {
                System.out.println("对象 " + obj + " 被点击");
                System.out.println("其外部类的 field1 字段的值为: " + field1);
                System.out.println("其外部类的 field2 字段的值为: " + field2);
                System.out.println("其外部类的 field3 字段的值为: " + field3);
                System.out.println("其外部类的 field4 字段的值为: " + field4);
            }
        };
        // new Object() 过程会新建一个匿名内部类,继承于 Object 类,
        // 并重写了 toString() 方法
        clickListener.onClick(new Object() {
            @Override
            public String toString() {
                return "obj1";
            }
        });
    }

    public static void main(String[] args) {
        InnerClassTest outObj = new InnerClassTest();
        outObj.anonymousClassTest();
    }
}

运行结果是
创建 InnerClassTest 对象
对象 obj1 被点击
其外部类的 field1 字段的值为: 1
其外部类的 field2 字段的值为: 2
其外部类的 field3 字段的值为: 3
其外部类的 field4 字段的值为: 4

  上面的代码中展示了常见的两种使用匿名内部类的情况:

  • 直接 new 一个接口,并实现这个接口声明的方法,在这个过程其实会创建一个匿名内部类实现这个接口,并重写接口声明的方法,然后再创建一个这个匿名内部类的对象并赋值给前面的 OnClickListener 类型的引用;

  • new 一个已经存在的类 / 抽象类,并且选择性的实现这个类中的一个或者多个非 final 的方法,这个过程会创建一个匿名内部类对象继承对应的类 / 抽象类,并且重写对应的方法。

  同样的,在匿名内部类中可以使用外部类的属性,但是外部类却不能使用匿名内部类中定义的属性,因为是匿名内部类,因此在外部类中无法获取这个类的类名,也就无法得到属性信息。

2.4 局部内部类

  局部内部类使用的比较少,其声明在一个方法体一段代码块的内部,而且不在定义类的定义域之内便无法使用,其提供的功能使用匿名内部类都可以实现,而本身匿名内部类可以写得比它更简洁,因此局部内部类用的比较少。来看一个局部内部类的小例子:

code

public class InnerClassTest {

    public int field1 = 1;
    protected int field2 = 2;
    int field3 = 3;
    private int field4 = 4;

    public InnerClassTest() {
        System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
    }

    private void localInnerClassTest() {
        // 局部内部类 A,只能在当前方法中使用
        class A {
            // static int field = 1; // 编译错误!局部内部类中不能定义 static 字段
            public A() {
                System.out.println("创建 " + A.class.getSimpleName() + " 对象");
                System.out.println("其外部类的 field1 字段的值为: " + field1);
                System.out.println("其外部类的 field2 字段的值为: " + field2);
                System.out.println("其外部类的 field3 字段的值为: " + field3);
                System.out.println("其外部类的 field4 字段的值为: " + field4);
            }
        }
        A a = new A();
        if (true) {
            // 局部内部类 B,只能在当前代码块中使用
            class B {
                public B() {
                    System.out.println("创建 " + B.class.getSimpleName() + " 对象");
                    System.out.println("其外部类的 field1 字段的值为: " + field1);
                    System.out.println("其外部类的 field2 字段的值为: " + field2);
                    System.out.println("其外部类的 field3 字段的值为: " + field3);
                    System.out.println("其外部类的 field4 字段的值为: " + field4);
                }
            }
            B b = new B();
        }
//        B b1 = new B(); // 编译错误!不在类 B 的定义域内,找不到类 B,
    }

    public static void main(String[] args) {
        InnerClassTest outObj = new InnerClassTest();
        outObj.localInnerClassTest();
    }
}
运行结果是
创建 A 对象
其外部类的 field1 字段的值为: 1
其外部类的 field2 字段的值为: 2
其外部类的 field3 字段的值为: 3
其外部类的 field4 字段的值为: 4
创建 B 对象
其外部类的 field1 字段的值为: 1
其外部类的 field2 字段的值为: 2
其外部类的 field3 字段的值为: 3
其外部类的 field4 字段的值为: 4

  同样的,在局部内部类里面可以访问外部类对象的所有访问权限的字段,而外部类却不能访问局部内部类中定义的字段,因为局部内部类的定义只在其特定的方法体 / 代码块中有效,一旦出了这个定义域,那么其定义就失效了,就像代码注释中描述的那样,即外部类不能获取局部内部类的对象,因而无法访问局部内部类的字段。

2.5 方法内部类

极少使用

  方法内部类是指:将内部类定义在外部类的方法中.

code

public class methodclass {

    public void show(){
        System.out.println("show");

        class InnerClass{
            private String name = "张三";
            public void test(){
                System.out.println("test");
            }
        }

        new InnerClass().test();
    }

    public static void main(String[] args) {
        methodclass mc = new methodclass();
        mc.show();
    }
}

运行结果是
show
test

注意事项:

方法内部类不能在外部类的方法以外的地方使用,所以方法内部类不能使用访问控制符和static修饰符