zl程序教程

您现在的位置是:首页 >  其它

当前栏目

内部类

内部
2023-09-14 09:04:05 时间

思维导图

点击标题可下载xmind文件

代码练习 

public class Main {
    public static void main(String[] args) {
        // 外部类外,创建非静态内部类变量
        Outter.Inner1 inner1;
        // 外部类外,创建非静态内部类对象,注意是通过”外部类对象“调用非静态内部类构造器
        inner1 = new Outter().new Inner1();

        // 外部类外,创建静态内部类变量
        Outter.Inner2 inner2;
        // 外部类外,创建静态内部类对象,注意是通过new “外部类类名”.内部类构造器
        inner2 = new Outter.Inner2();
    }
}

class Outter{
    private String str = "out";
    private static String str2 = "static out";

    // 成员内部类:非静态内部类,可以用private,默认,protected,public修饰
    protected class Inner1{
        // 非静态内部类只能包含非静态成员,不能包含静态成员
        String str = "in1";
        //private static String str2 = "static in1"; //编译报错:Inner classes cannot have static declarations

        void get(){
            // 如果非静态内部类成员变量和外部类变量重名,
            // 则可以用”(this.)成员变量名“来获取内部类成员,用“外部类名.this.成员变量名”来获取外部类成员
            System.out.println(str);
            System.out.println(this.str);
            // 非静态内部类可以直接访问外部类的实例成员,包括私有
            System.out.println(Outter.this.str);
            // 非静态内部可以直接方法外部类的类成员,包括私有
            System.out.println(str2);
        }
    }

    void userInner1(){
        // 外部类内,创建非静态内部类变量
        Inner1 inner1;
        // 外部类内,创建非静态内部类对象
        inner1 = new Inner1();
        // 作为父类,请看Child1
        Child1 child1 = new Child1(new Outter());
        // 外部类需要访问非静态内部类成员时,只能创建内部类对象,通过内部类对象访问其成员
        System.out.println(inner1.str);
    }

    // 成员内部类:静态内部类
    static class Inner2{
        // 静态内部类既可以包含静态成员,也可以包含非静态成员
        private String str = "in2";
        private static String str2 = "static in2";

        public void get(){
            System.out.println(str);
            System.out.println(this.str);
            // 静态内部类只能访问外部类的静态成员,不能访问外部类的非静态成员
            // System.out.println(Outter.this.str); // 编译报错:'inner.Outter.this' cannot be referenced from a static context
            System.out.println(str2);
            System.out.println(this.str2);
            System.out.println(Inner2.str2);
            System.out.println(Outter.str2);
        }
    }

    void useInner2(){
        // 外部类访问静态内部类静态成员时,可以通过类名直接访问
        System.out.println(Inner2.str2);
        // 外部类内,创建静态内部类变量
        Inner2 inner2;
        // 外部类内,创建静态内部类对象
        inner2 = new Inner2();
        // 外部类访问静态内部类非静态成员时,只能通过创建内部类对象来访问
        System.out.println(inner2.str);
        // 外部类访问静态内部类静态成员时,也可以通过内部类对象访问
        System.out.println(inner2.str2);
        // 作为父类,请看Child2
        Child2 child2 = new Child2();
    }

    // 局部内部类:普通局部内部类
    void innerTest1(){
        // 如果方法局部变量和局部内部类成员变量同名,则方法局部变量会被覆盖
        String str = "out method";
        String str2 = "out method2";
        class Inner3{
            String str = "in3";
            // 局部内部类不能有静态成员,只有内部类只有静态内部类可以有静态成员
            // static String str2 = "static in3"; // 编译报错:Inner classes cannot have static declarations
            void get(){
                System.out.println(str);
                System.out.println(this.str);
                System.out.println(Outter.this.str);
                // 如果方法局部变量和外部类成员变量同名,则局部内部类优先访问方法局部变量
                System.out.println(str2);
                // 局部内部类访问方法局部变量后,方法局部变量会自动被final修饰为常量,不可二次赋值
                //str2 = ""; // 编译报错:Variable 'str2' is accessed from within inner class, needs to be final or effectively final
                System.out.println(Outter.str2);
            }
        }

        // 局部内部类只能在方法中使用,不能再方法外使用
        Inner3 inner3;
        inner3 = new Inner3();
        System.out.println(inner3.str);
    }

    // 局部内部类:匿名内部类
    void innerTest2(){
        String str3 = "in4";
        // 匿名内部类,顾名思义是没有名字的内部类,适用只想创建一次性子类对象或实现类对象
        new Test() {
            // 匿名内部类不能定义构造器,因为构造器名字必须和类名一致,但是匿名内部类没有名字
            // 匿名内部类可以通过实例初始化块完成初始化
            {
                int i = 0;
            }

            @Override
            public void test() {
                System.out.println("局部内部类");
            }
        };

        // 可以看出,局部内部类本质是一个对象,且是父接口或者父类的子类对象
        // 可以看出局部内部类的构造器是和父类或父接口相同的
        Test2 test2 = new Test2("局部内部类",8) {
            @Override
            public void test() {
                // 匿名内部类是一个特殊的局部内部类,它可以直接访问外部类成员
                System.out.println(str);
                System.out.println(str2);
                System.out.println(str3);
                // 和局部内部类一样,匿名内部类访问的方法局部变量会自动被final修饰,无法二次赋值
                //str3 = ""; // 编译报错:Variable 'str3' is accessed from within inner class, needs to be final or effectively final
            }
        };
    }

}

interface Test{
    void test();
}

abstract class Test2{
    public Test2(String name,int age){
        System.out.println(name+age);
    }

    public abstract void test();
}

class Child1 extends Outter.Inner1{
    // 非静态内部类作为父类的话,则子类构造器必须要提供一个外部类对象来调用父类(非静态内部类)的构造器。
    // 否则声明子类时,编译报错 No enclosing instance of type 'inner.Outter' is in scope
    public Child1(Outter outter){
        outter.super();
    }
}

class Child2 extends Outter.Inner2{
    // 静态内部类作为父类的话,子类只需要继承静态内部类即可
}

 思考题

1.为什么局部内部类访问方法的局部变量后,该局部变量会自动隐式地被final修饰?

局部内部类访问方法局部变量后,方法局部变量自动隐式被final修饰,这个能力是jdk8后有的。

jdk8之前需要我们手动定义被访问的局部变量为final,否则编译报错。

为什么需要加这个设定呢?

方法的局部变量的生命周期是:
从定义这个局部变量开始,到方法结束为止。

所以方法的局部变量的生命周期很短。

那局部内部类的生命周期呢?
局部内部类的生命周期是这个类被首次使用时加载,当类不在使用时,垃圾回收器会去回收这个类,但是具体垃圾回收器是否及时回收就不能保证了。

那么就可能存在这样一种情况,方法已经运行结束了,方法的局部变量也被销毁了。而局部内部类中访问了这个已经被销毁的局部变量,且局部内部类还没有被回收。那就出现非法引用了。

为了解决这个问题,java会拷贝一份方法局部变量到引用该变量的局部内部类中,这样,就算方法局部变量被销毁了,局部内部类引用该变量也不会出现非法引用的问题。

但是如何确保局部内部类中拷贝的变量和方法局部变量始终一致呢?

想一想,当局部内部类拷贝完方法局部变量后,我们手动修改了这个局部变量,那么拷贝变量和被拷贝变量就会不一致。这就会导致这个拷贝动作没有任何用处。

那么怎么才能让拷贝变量和被拷贝变量始终一致呢?

一个简单的做法就是把被局部内部类访问的方法局部变量定义成final,一个常量,那它就不会发生二次修改了。

总结起来:因为方法局部变量的生命周期没有局部内部类长,所以为了避免出现非法引用问题,java将方法局部变量拷贝一份到了局部内部类中,但是为了保证拷贝变量和被拷贝变量值始终一致,所以将方法局部变量设置为final。

2.为什么内部类可以直接访问外部类的成员,而外部类不能直接访问内部类成员?

这与内部类和外部类的生命周期有关。

概括一下就是:存在外部类对象时,不一定存在内部类对象。
            存在内部类对象时,一定存在其所在外部类的对象。

因为java在创建内部类对象时,会在内部类对象的内存中存储一个其所寄生的外部类对象的引用。

所以由于内部类实例生成时,外部类实例也早就存在了,所以内部类可以直接访问外部类的成员。

但是外部类实例存在时,内部类可能还没创建,所以外部类不能直接访问内部类成员。

3.为什么成员内部类可以用private,默认,protected,public修饰,而外部类只能用默认,public修饰?

其实这题可以这样问:

外部类为什么不能被private,protected修饰?为什么可以被默认,public修饰?

我们知道

被private修饰的东西只能在类中访问,即这个东西顶层是类级别

被protected修饰的东西,可以在同包其他类访问,也可以被不同包子类访问,
            分析一下,同包其他类访问,即这个东西顶层是包级别的,
                    不同包子类访问,即这个东西顶层是类级别的。
    所以被protected修饰的东西的顶层可能是包级别,也可能是类级别的。

被默认修饰的东西,可以被同包其他类访问,即这个东西顶层是包级别的。

被public修饰的东西,可以被所有包的类访问,即这个东西顶层是包级别的。

而外部类的顶层是包级别的,所以只能被默认,public修饰。而protected不完全满足这个要求,所以不能用来修饰外部类,private更不满足。

成员内部类的顶层是类级别的,所以四个访问权限修饰符都可以用。

4.为什么局部内部类不能用访问权限修饰符修饰和static修饰?

局部内部类是定义在外部类方法中的内部类。

而方法中定义的变量无需访问权限修饰符,因为它们只能在方法中使用。

static修饰的成员属于类本身,但是方法中定义的变量属于方法本身。

同理,局部内部类也是如此。

5.成员内部类和局部内部类都是外部类的成员吗?

成员内部类是外部类的成员之一。

局部内部类是定义在外部类方法中的内部类,不属于外部类的成员。

6.局部内部类如何可以访问方法局部同名变量?

public class Outter{
    private String a = "外部类的成员变量a";
    public void test(){
        String a = "外部类成员方法的局部变量a";
        class Inner{
            private String a = "局部内部类变量a";
            public void info(){
                System.out.println(a);
                System.out.println(this.a);
                System.out.println(Outter.this.a);
            }
        }
		
		new Inner().info();
    }
	
	public static void main(String[] args){
		Outter out = new Outter();
		out.test();
	}
}

如上情况,局部内部类Inner中是无法访问到方法同名局部变量的,因为方法同名局部变量已经被局部内部类的同名变量覆盖了。

7.内部类是否可以定义main方法入口?是否可以同时和外部类同时定义main方法入口?如果同时定义,那么哪个main方法入口优先执行?

首先我们需要知道

1.外部类和内部类是平级的,都是类
2.每个类中都可以定义一个main方法入口
3.编译类后生成字节码文件,运行字节码文件要求对应类中定义了main方法入口
4.外部类和内部类编译后会自动生成不同的字节码文件
5.需要注意main方法时静态的,所以非静态内部类不能定义main方法。另外局部内部类不能被static修饰,所以也不能定义main方法。

所以静态内部类和外部类可以同时定义main方法,main方法的运行需要看运行的字节码文件是哪个。

8.内部类的成员有哪些?这些成员和外部类对应成员有区别吗?

首先我们需要知道外部类的成员有哪些

一般外部类有六种成员:
成员变量,成员方法,构造器,初始化块,内部类,枚举类

对于内部类需要分情况说明
1.成员内部类
1.1 静态内部类
可以包含成员变量,成员方法,构造器,初始化块,内部类,枚举类
其中枚举类默认被static修饰

1.2 非静态内部类
可以包含非静态变量,非静态方法,构造器,非静态初始化块,非静态内部类
因为内部类中的枚举类默认被static修饰,所以非静态内部类不能包含枚举类

2.局部内部类
2.1 普通局部内部类
可以包含非静态变量,非静态方法,构造器,非静态初始化块,非静态内部类
因为内部类中的枚举类默认被static修饰,所以非静态内部类不能包含枚举类

2.2 匿名内部类
可以包含非静态变量,非静态方法,非静态初始化块,非静态内部类
因为内部类中的枚举类默认被static修饰,所以非静态内部类不能包含枚举类,
由于匿名内部类不能显式定义构造器,因为构造器名字必须和类名相同,而匿名内部类没有名字,所以匿名内部类无法显式定义构造器。

内部类的这些成员在用法上和外部类没有区别。

9.内部类是否可以派生子类?如果可以,那么内部类子类是否只能也是内部类? 如果不是,则内部类子类有什么特点?

首先我们需要知道内部类是和外部类其实是平级概念,都是类概念。

所以外部类可以继承其他外部类的内部类,不存在内部类的子类只能是内部类的限制。

另外,内部类派生子类的情况需要分情况讨论:

1.局部内部类派生子类只能发生在定义局部内部类所在的方法中,此时局部内部类的子类只能是局部内部类
2.成员内部类派生子类需要进一步分情况讨论
    分情况讨论前,我们需要知道
        ①非静态成员内部类的构造器不能直接【new关键字】调用,必须通过【外部类对象.new】 来调用,因为非静态内部类属于外部类的实例成员,外部类的实例成员只能通过外部类实例调用。所以调用非静态内部类构造器必须要有一个外部类对象。
        ②子类构造器最终总会先调用父类构造器
        ③成员内部类的全称应该在内部类名前加上外部类名限定,即OutterClass.InnerClass

       
    2.1 非静态内部类派生子类
    如果一个外部类B继承了另一个外部类A的非静态内部类A1,那么B中总有一个构造器的第一行必定要调用父类A1的构造器,但是A1的构造器必须通过其所在外部类的对象调用,所以子类B的构造器中需要提供一个外部类A的实例,通过A实例显式调用A1,如  new A().super();

     另外B继承A1的语法格式

     class B extends A.A1{
     }


    2.2 静态内部类派生子类
    由于静态内部类是属于外部类的类成员,不是实例成员。所以调用静态内部类构造器时,直接
    new OutterClass.InnerClass()即可。
    所以对于静态内部类派生的子类的构造器没有特殊要求,只需要保证继承语法格式即可,语法格式同2.1
    

10.接口中是否可以定义内部类?如果可以,那么该内部类有何特点?

接口中可以定义内部类,且内部类默认被public static修饰,public static可以省略

11.外部类的子类是否可以重写该外部类的成员内部类?

这个问题,我们首先要知道 重写 的概念。

我们说的重写是指方法重写,所以只能重写方法,不能重写内部类。另外重写需要遵守“两同,两小,一大”的原则

方法重写前提是:发生在父子类之间
“两同”是指:方法名相同,方法参数列表相同
“两小”是指:子类重写方法的返回值类型要比父类被重写方法的返回值类型小或相等
           子类重写方法抛出的异常要比父类被重写方法抛出的异常小或相等
"一大"是指:子类重写方法的访问权限修饰符要比父类被重写方法的访问权限修饰符大


如果允许外部类子类重写外部类的内部类的话,则应该也要满足“两同,两小,一大” 原则

我们知道一般描述一个外部类的内部类的话,需要在内部类名前加上外部名限定
即

class Outter{
    class Inner{
    }
}

此时 内部类的全称应该是 Outter.Inner

如果Outter的子类OutterChild中重写的内部类Inner的全称应该是什么呢?

class OutterChild extends Outter{
    class Inner{
    }
}

应该是 OutterChild.Inner

那么重写后,内部类的名字已经改变了。不满足两同的要求

12.匿名内部类本质是一个接口(或父类)的实现类(或子类)实例,那么匿名内部类何时创建对象?它的构造器从哪来?它的构造器有何特点?

匿名内部类的语法格式

new 实现接口()|父类构造器(实参列表){
    匿名内部类类体;
}


另外我们需要知道:
匿名内部类由于没有类名,所以没办法定义构造器,因为构造器的名字必须和类名一致。

总结以上:
当匿名内部类实现接口时,匿名内部类只能new 无参构造器
当匿名内部类继承父类时,匿名内部类可以拥有和父类相似的构造器(注意是相似,不是继承),这里的相似是指匿名内部类的隐式构造器和父类构造器有相同参数列表。


所以这个问题的答案是:
定义匿名内部类时就直接生成该匿名内部类的实例。匿名内部类不能显式定义构造器,如果是实现接口的话,则可以拥有一个无参构造器,如果是继承父类的话,则可以拥有和父类相似的构造器(参数列表相同,没有构造器名字)。

13.内部类编译后的字节码文件命名规则是什么?

该问题需要分情况讨论
1.成员内部类 : 外部类名$内部类名.class
2.普通局部内部类 : 外部类名$N内部类名.class  (其中N是数字,从1开始)
3.匿名内部类 : 外部类名$N.class   (其中N是数字,从1开始)