Java的四种内部类

Java四种内部类,静态内部类、成员内部类、局部内部类、匿名内部类的简要介绍

Ahthor: Msuenb

Date: 2023-02-11


在 Java 中,允许一个类的定义位于类一个类的内部,前者称为内部类,后者称为外部类。

内部类分为:静态内部类、成员内部类、局部内部类、匿名内部类

静态内部类

有static修饰的成员内部类叫做静态内部类。它的特点:

  • 和其他类一样,它只是定义在外部类中的另一个完整的类结构

    • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
    • 可以在静态内部类中声明属性、方法、构造器等结构,包括静态成员
    • 可以使用abstract修饰,因此它也可以被其他类继承;可以使用final修饰,表示不能被继承
    • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号
      • 静态内部类完整字节码名称是“包名.外部类名$静态内部类名"
      • 在外部类的外面使用静态内部类名时需要使用”包名.外部类名.静态内部类名“
  • 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private

    • 外部类只允许public或缺省的
  • 在外部类的任意位置都可以直接使用静态内部类,也可以访问静态内部类的所有成员,包括私有的

    • 如果使用静态内部类的静态成员,就通过”静态内部类名.静态成员“的形式
    • 如果使用静态内部类的非静态成员,就先创建静态内部类的对象,然后通过“静态内部类对象.非静态成员”的形式
  • 只可以在静态内部类中使用外部类的静态成员

    • 在静态内部类中不能直接使用外部类的非静态成员哦
    • 如果在内部类中有变量与外部类的静态成员变量同名,可以使用“外部类名."进行区别
  • 如果权限修饰符允许,静态内部类也可以在外部类的外面使用。

    • 如果使用静态内部类的静态成员,就通过”外部类名.静态内部类名.静态成员“的形式
    • 如果使用静态内部类的非静态成员,就先创建静态内部类的对象,在外部类的外面不需要通过外部类的对象就可以创建静态内部类的对象(通常应该避免这样使用)
public class StaticInnerClass {
    public static void main(String[] args) {
        Outer.OuterStaticMethod();
        Outer outer = new Outer();
        outer.OuterMethod();

        Outer.Inner.InnerStaticMethod();
        Outer.Inner inner = new Outer.Inner();
        inner.InnerMethod();
    }
}
class Outer {
    private static String a = "外部类静态成员a";
    private static String b = "外部类静态成员b";
    private String c = "外部类非静态成员b";

    static class Inner {
        private static String a = "内部类静态成员a";
        private String c = "内部类非静态成员c";

        public static void InnerStaticMethod() {
            System.out.println("------内部类静态方法-------");
            System.out.println(Outer.a);   // 外部的 a
            System.out.println(a);  // 内部的 a
            System.out.println(b);  // 外部的 b

            //System.out.println(Outer.this.c);    // 不能使用外部类的非静态成员
            //System.out.println(c);  // 不能使用自己的非静态成员
        }

        public void InnerMethod() {
            System.out.println("------内部类非静态方法------");
            System.out.println(Outer.a);   // 外部的 a
            System.out.println(a);  // 内部的 a
            System.out.println(b);  // 外部的 b

            //System.out.println(Outer.this.c);    // 不能使用外部类的非静态成员
            System.out.println(c);  // 内部的 c
        }
    }

    public static void OuterStaticMethod() {
        System.out.println("------外部类静态方法------");
        System.out.println(Outer.a);    // 外部的 a
        System.out.println(b);  // 外部的 b
        //System.out.println(c); // 不能使用自己的非静态成员

        System.out.println(Inner.a);    // 内部的 a
        System.out.println(new Inner().c);  // 内部的 c
    }

    public void OuterMethod() {
        System.out.println("------外部类非静态方法------");
        System.out.println(Outer.a);   // 外部的 a
        System.out.println(b);  // 外部的 b
        System.out.println(c); // 外部的 c

        System.out.println(Inner.a);    // 内部的 a
        System.out.println(new Inner().c);  // 内部的 c
    }
}

注意:对于方法的使用也遵循上面的规则

成员内部类

没有static修饰的成员内部类叫做非静态内部类。非静态内部类的特点:

  • 和其他类一样,它只是定义在外部类中的另一个完整的类结构

    • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
    • 可以在非静态内部类中声明属性、方法、构造器等结构,但是不允许声明静态成员,除了静态常量,或者继承父类的静态成员。
    • 可以使用abstract修饰,因此它也可以被其他类继承;可以使用final修饰,表示不能被继承
    • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号
      • 非静态内部类完整字节码名称是“包名.外部类名$非静态内部类名"
      • 在外部类的外面使用非静态内部类名时需要使用”包名.外部类名.非静态内部类名“
  • 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private

    • 外部类只允许public或缺省的
  • 在外部类中使用非静态内部类有限制

    • 在外部类的静态成员中,只能使用非静态内部类的静态常量
    • 在外部类的非静态成员中,可以创建非静态内部类对象,然后通过非静态内部类的对象使用非静态内部类的所有成员
  • 可以在非静态内部类中使用外部类的所有成员,哪怕是私有的

    • 如果没有重名问题,可以直接使用外部类的所有成员
    • 如果非静态内部类与外部类的静态成员重名,可以使用“外部类名."进行区别
    • 如果非静态内部类与外部类的非静态成员重名,可以使用“外部类名.this."进行区别
  • 如果权限修饰符允许,非静态内部类也可以在外部类的外面使用。

    • 如果使用非静态内部类的静态常量,就通过”外部类名.非静态内部类名.静态常量“的形式
    • 在外部类的外面必须通过外部类的对象才能创建非静态内部类的对象(通常应该避免这样使用)
      • 如果要在外部类的外面使用非静态内部类的对象,通常在外部类中提供一个方法来返回这个非静态内部类的对象比较合适
      • 因此在非静态内部类的方法中有两个this对象,一个是外部类的this对象,一个是内部类的this对象
public class InnerClass {
    public static void main(String[] args) {
        Outer2.OuterStaticMethod();
        Outer2 outer2 = new Outer2();
        outer2.OuterMethod();

        Outer2.Inner2 inner2 = outer2.new Inner2();
        inner2.InnerMethod();
    }
}

class Outer2 {
    private static String a = "外部类静态成员a";
    private static String b = "外部类静态成员b";
    private String c = "外部类非静态成员c";

    class Inner2 {
        //private static String a = "内部类静态成员a";   // 非静态内部类中不能声明静态成员
        private String c = "内部类非静态成员";
        public static final String s = "非静态内部类的静态常量";   // 但可以声明静态常量

        //public static void InnerStaticMethod() {} // 不可以声明静态成员

        public void InnerMethod() {
            System.out.println("-----内部类非静态方法-----");
            System.out.println(a);  // 外部 a
            System.out.println(Outer2.b);   // 外部 b

            System.out.println(Outer2.this.c);  // 外部类的 c
            System.out.println(c);  // 自己的 c
        }
    }

    public static void OuterStaticMethod() {
        System.out.println("-----外部类静态方法-----");
        System.out.println(a);
        System.out.println(Outer2.b);
        //System.out.println(c);      // 不能访问自己的非静态成员

        //Inner2 inner2 = new Inner2();   // 无法使用非静态内部类
        System.out.println(Inner2.s);
    }

    public void OuterMethod() {
        System.out.println("-----外部类非静态方法-----");
        System.out.println(a);
        System.out.println(Outer2.b);
        System.out.println(c);      // 自己的 c

        System.out.println(new Inner2().c);   // 内部的 c
        System.out.println(Inner2.s);   // 内部的 s
    }
}

局部内部类

【修饰符】 class 外部类{
    【修饰符】 返回值类型  方法名(【形参列表】){
         【final/abstract】 class 局部内部类名{
     	 
         }
    }    
}

局部内部类的特点:

  • 和外部类一样,它只是定义在外部类的某个方法中的另一个完整的类结构
    • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
    • 可以在局部内部类中声明属性、方法、构造器等结构,但是不允许声明静态成员,除了静态常量,或者继承父类的静态成员
    • 可以使用abstract修饰,因此它也可以被同一个方法的在它后面的其他内部类继承
    • 可以使用final修饰,表示不能被继承
    • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号。
      • 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类
      • 局部内部类完整字节码名称是“包名.外部类名$编号局部内部类名"
      • 在外部类的外面不能使用局部内部类名
  • 和成员内部类不同的是,它前面不能有权限修饰符等
  • 局部内部类如同局部变量一样,有作用域
  • 局部内部类中是否能访问外部类的非静态的成员,取决于所在的方法
  • 局部内部类中还可以使用所在方法的局部常量,即用final声明的局部变量
    • JDK1.8之后,如果某个局部变量在局部内部类中被使用了,自动加final
    • 为什么在局部内部类中使用外部类方法的局部变量要加final呢?考虑生命周期问题。
public class Main {
    public static void main(String[] args) {
        Outer3.outStaticMethod();

        Outer3 outer3 = new Outer3();
        outer3.outMethod();

        // 这里不能使用局部内部类
        // Outer3.Inner3 inner3 = outer3.new Inner3();

        // 只能可以通过多态引用局部内部类的对象
        Father father = Outer3.outTest();
        father.m();
    }
}

class Outer3 {
    private static String a = "外部类的静态成员a";
    private static String b = "外部类的静态成员b";
    private String c = "外部类的非静态成员c";

    public static void outStaticMethod() {
        class Inner3 {
            // 不能定义静态成员
            private String a = "非静态内部类1对象的非静态a";
            private static final String s = "非静态内部类1对象的静态常量s";

            public void inMethod() {
                System.out.println("--------------");
                System.out.println(a);  // 内部的 a
                System.out.println(Outer3.a);   // 外部的 a
                System.out.println(b);  // 外部的 b

                // System.out.println(Outer3.this.c);   // 不能访问外部类的非静态成员
                System.out.println(s);
            }
        }
        new Inner3().inMethod();
    }

    public void outMethod() {
        class Inner3 {
            // 不能定义静态成员
            private String a = "非静态内部类2对象的非静态a";
            private static final String s = "非静态内部类2对象的静态常量s";

            public void inMethod() {
                System.out.println("----------------");
                System.out.println(a);  // 内部的 a
                System.out.println(Outer3.a);   // 外部的 a
                System.out.println(b);  // 外部的 b

                System.out.println(Outer3.this.c);   // 外部的 c
                System.out.println(s);
            }
        }
        new Inner3().inMethod();
    }

    public static void outOtherMethod() {
        // Inner3 inner3 = new Inner3();   局部内部类有作用范围限制
    }

    public static Father outTest(){
        final int outA = 1;
        class Inner extends Father{
            @Override
            void m() {
                System.out.println("局部内部类重写父类方法");
                System.out.println("局部内部类使用外部类的局部变量outA = " + outA);
            }
        }
        return new Inner();
    }
}

abstract class Father{
    abstract void m();
}

匿名内部类

在开发过程中,需要用到一个抽象类的子类的对象或一个接口的实现类的对象,而且只创建一个对象,使用一次就行,就不需要给类命名,这时就可以使用匿名内部类。

new 父类(【实参列表】) {
    重写方法...
}

new 父接口() {
    重写方法...
}
// ()中没有参数 此时匿名内部类的直接父类是Object类

匿名内部类是没有名字的类,因此在声明类的同时就创建好了唯一的对象。

匿名内部类没有名字,因此字节码文件名是外部类名$编号.class。

匿名内部类是一种特殊的局部内部类,只不过没有名称而已。所有局部内部类的限制都适用于匿名内部类。例如:

  • 在匿名内部类中是否可以使用外部类的非静态成员变量,看所在方法是否静态
  • 在匿名内部类/局部内部类中如果需要访问当前方法的局部变量,该局部变量需要加 final 或有 final 的效果。保证该方法执行完局部变量不能被销毁,在内部类中可以访问。
public class Main {
    private static String a = "外部类的静态成员变量a";
    private String b = "外部类的非静态成员变量b";

    public static void main(String[] args) {
        String c = "当前方法的局部变量c";     // 有 final 的效果 
        final String s = "当前方法的局部常量s";
        A obj = new A() {
            @Override
            public void method() {
                System.out.println(a);
                // System.out.println(Main.this.b);  静态方法 不能使用外部类静态成员
                System.out.println(c);  // 可以访问
                // c = "abc";  // 不能修改
                System.out.println(s);
            }
        };
        obj.method();
        System.out.println(obj.getClass().getName());   // Main$1

        new Main().outFun();
    }

    public void outFun() {
        A obj = new A() {
            @Override
            public void method() {
                System.out.println(a);
                System.out.println(Main.this.b);
            }
        };
        obj.method();
        System.out.println(obj.getClass().getName());   // Main$2	多个内部类编号顺延
    }
}

interface A {
    void method();
}

匿名内部类的使用场景:

  1. 使用匿名内部类直接调用方法

    public class Main {
        public static void main(String[] args) {
            new A() {
                @Override
                public void method() {
                    System.out.println("通过父接口的变量多态引用匿名内部类的对象");
                }
            }.method();
        }
    }
    
    interface A {
        void method();
    }
    
  2. 通过父类或父接口的变量多态引用匿名内部类的对象

    public class Main {
        public static void main(String[] args) {
            A obj = new A() {
                @Override
                public void method() {
                    System.out.println("通过父接口的变量多态引用匿名内部类的对象");
                }
            };
            obj.method();
        }
    }
    
    interface A {
        void method();
    }
    
  3. 匿名内部类的对象作为实参

    public class Main {
        public static void main(String[] args) {
            test(new A() {
                @Override
                public void method() {
                    System.out.println("匿名内部类作为实参");
                }
            });
        }
    
        public static void test(A a) {  // a 的运行时类型是 匿名类 Main$1
            a.method();
        }
    }
    
    interface A {
        void method();
    }
    

内部类对比

静态内部类(了解) 非静态内部类(了解) 局部内部类(了解) 匿名内部类
类角色 字节码文件 外部类名$静态内部类名.class 外部类名$非静态内部类名.class 外部类名$编号局部内部类名.class 外部类名$编号.class
父类或父接口 正常 正常 正常 指定一个直接父类或一个直接父接口
非静态成员 正常 正常 正常 只有默认构造器(Object的)
静态成员 正常 只能有静态常量 只能有静态常量 只能有静态常量
权限修饰符 public、protected、缺省、private 同左
其他修饰符 abstract、final 同左 同左
静态修饰符 static
成员角色 依赖于外部类的对象 不依赖 依赖 看所在方法 看所在方法
使用 在外部类的静态成员中使用内部类 没有限制 只能使用非静态内部类的静态常量 有严格作用域 有严格作用域
在外部类的非静态成员中使用内部类 没有限制 同左 有严格作用域 有严格作用域
在内部类中使用外部类的静态成员 没有限制 同左 同左 同左
在内部类中使用外部类的非静态成员 不能 没有限制 看所在方法 看所在方法
在内部类中使用外部类的某局部变量 无(作用域问题) 同左 局部变量加final 同左
重名 与外部类的静态成员重名 外部类名.静态成员 外部类名.静态成员 同左 同左
与外部类的非静态成员重名 外部类名.this.非静态成员 同左 同左
posted @ 2023-02-12 19:20  msuenb  阅读(84)  评论(0编辑  收藏  举报