412-423内部类

一、内部类

package com.alice.innerclass_.innerclass01;
/*
    一个类的内部又完整的嵌套了另外的一个类结构,被嵌套的类称内部类
    inner class,嵌套其他类的类称为外部类outer class,是我们类的五大成员(思考:类的五大成员是哪些?),内部类
    最大的特点是可以直接访问私有属性,并且可以体现类与类之间的包含关系。
    基本语法:
    class Outer { // 外部类
        class Inner { // 内部类
        }
    }
    class Other{}  // 外部其他类
    InnerClass01.java

    类的五大成员:属性,方法,构造器,代码块,内部类

    内部类的分类
    定义在外部类局部位置上(比如方法内):
    1、局部内部类(有类名)
    2、匿名内部类(没有类名,重点!!!)
    定义在外部类的成员位置上面:
    1、成员内部类(没有使用static修饰)
    2、静态内部类(使用static修饰)
 */
public class Innerclass01 {  // 外部其他类
}
class Outer {  // 外部类
    private int n1 = 100;  // 属性
    public Outer(int n1) {  // 构造器
        this.n1 = n1;
    }
    public void m1() {  // 方法
        System.out.println("m1()");
    }
    { // 代码块
        System.out.println("代码块...");
    }
    class Inner {
        // 内部类
    }
}

二、局部内部类

package com.alice.innerclass_.innerclass01;

public class LocalInnerClass02 {
    public static void main(String[] args) {
        Outer02 outer02 = new Outer02();
        outer02.m1();
    }
}
class Outer02 {
    // 外部类
    private int n1 = 100;
    private void m2() {
        System.out.println("Outer02 m2()");
    }
    public void m1() {
        // 局部内部类是定义在外部类的局部位置,通常是在方法中
        // 不能添加访问修饰符,但是可以使用final修饰
        final class Inner02 {  // 也就是这里不能加上public,但是可以加上final,加上这个表示不能够再被继承
            // 局部内部类的作用域仅仅是在定义它的方法或者代码块中的
            // 可以直接访问外部类的所有成员,包含私有的
            public void f1() {
                // 局部内部类的本质还是一个类
                System.out.println("n1=" + n1);  // 局部内部类可以直接访问外部类的成员,比如下面的n1和m2
                // 理论上来说,私有的属性只能本类访问,但是因为f1是Outer02中的,所以也能够访问
                // 外部类的所有的成员
                // 所以这里访问外部方法也是可以的
                m2();
            }
        }
//        class Inner03 extends Inner02 {  // 因为这里Inner02添加了final,所以这里不能够被继承
//
//        }
        // 代码块也是可以定义局部内部类的
        {
            class Inner03 {}
        }
        // 外部类在方法中,可以创建Inner02对象,然后调用方法即可
        Inner02 inner02 = new Inner02();
        inner02.f1();

    }

}
/*
    局部内部类的使用LocalInnerClass.java
    说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名
    1、可以直接访问外部类的所有成员,包含私有的
    2、不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的,但是可以使用final修饰,因为局部变量
    也可以使用final
    3、作用域:仅仅是定义它的方法或者代码块中。
    4、局部内部类--访问--外部类的成员
    访问的方式是直接方法
    5、外部类访问局部内部类的成员,访问方式是创建对象再访问。并且必须是在作用域内。

    小结:
    局部内部类定义的位置,作用域,本质还是类。记住这三句话就好。
 */

package com.alice.innerclass_.innerclass01;

public class LocalInnerClass03 {  // 外部其他类
    // 外部其他类不能访问局部内部类,因为局部内部类的地位是一个局部变量
    public static void main(String[] args) {
        Outer03 outer03 = new Outer03();
        outer03.m1();
        System.out.println("outer03的哈希代码:" + outer03);
    }
}

/*

 */
class Outer03 {
    private int n1 = 100;
    public void m1() {
        final class Inner02 {
            private int n1 = 800;  // 局部内部类和外部类的属性重名了。
            public void f1() {
                System.out.println("n1 = " + n1);
                // 这个时候n1是800还是100呢?这个时候是局部内部类的800
                // 如果想要访问到外部的100呢?
                System.out.println("访问外部类的n1 = " + Outer03.this.n1);  // 如果直接Outer03.n1这样要求是一个静态的
                // 这里使用外部类的对象访问
                // 这里的Outer03.this其实就是外部类的一个对象,谁在调用,这个this就是谁outer03.m1();
                // 哈希
                System.out.println("Outer03.this的哈希代码:" + Outer03.this);  // 说明这两个是相同的,同一个对象
                System.out.println(this);  // 这里的是inner02的
            }
        }
        Inner02 inner02 = new Inner02();
        inner02.f1();
        System.out.println(inner02); // 这里是inner02
    }
}

/*
    6、外部其他类不能访问局部内部类,因为局部内部类的地方相当于是一个局部变量
    7、如果外部类和局部内部类的成员重名时,默认遵循的是就近原则,如果想要访问外部类的成员,则可以使用
    外部类名.this.成员去访问
 */

三、匿名内部类

package com.alice.innerclass_.innerclass01;

public class AnonymousInnerClass04 { // 外部其他类
    public static void main(String[] args) {
        Outer04 outer04 = new Outer04();
        outer04.method();
    }
}

class Outer04 {//外部类
    private int n1 = 10;

    public void method() {
        // 基于接口的匿名内部类
        // 1、需求:想要使用接口IA,并创建对象
        // 2、传统方式就是写一个类实现该接口,并且创建对象--->Test一实现接口IA
//        IA tiger = new Tiger(); // 接口可以指向实现了该接口的类的对象实例
//        tiger.cry(); // 这就是传统的写法
//        // 3、现在有一个新的需求,就是我们的这里对象不想创建tiger,我们只使用一次就不用了
//        // 需求是:希望写的tiger类只是使用一次,以后再也不使用了,如果说有这样的需求,tiger
//        // 定义在这个方法有点浪费如果再来一个狗类实现IA--Test二狗类
//        // 如果这样就会定义很多的类
//        IA dog = new Dog();
//        dog.cry();
        // 而解决的办法可以使用匿名内部类
        // 这里将小狗类,和老虎类全部注释
        // 我们不创建这个类也可以显示老虎叫的一个使用
        IA tiger = new IA() {  // 接口不能直接new,但是这里添加了一个{},这里就相当于是在tiger中写的方法
            @Override
            public void cry() {
                System.out.println("老虎叫唤");
            }
        };
        tiger.cry();  // 这个时候相当于是使用老虎这个对象去调用这里实现的方法,和刚刚的效果是是一样的
//        IA dog = new IA() {
//            @Override
//            public void cry() {
//                System.out.println("小狗汪汪叫");
//            }
//        };
//        dog.cry();
        // 这里使用了匿名内部类来简化开发
        // 我们来看这个匿名内部类有什么特点
        // tiger的编译类型是什么,tiger的运行类型是什么?
        // 编译类型看等号的左边,所以编译类型就是接口类型IA
        // 运行类型是什么?运行类型就是匿名内部类
        // 这个匿名内部类就是
        /*
            我们看底层就会发现这个匿名内部类是
            class XXX implements IA {
                @Override
                public void cry() {
                    System.out.println("老虎叫唤");
                }
            }
            底层有了一个类去实现了,这个底层的类的名字是什么呢!?
            用外部类的名字加了一个$1
            tiger的运行类型是:class com.alice.innerclass_.innerclass01.Outer04$1
         */
        // 前面讲过打印出运行类名只需要使用Object的getClass()方法
        System.out.println("tiger的运行类型是:" + tiger.getClass());
        // 底层会分配一个类名,就是这个,用一次就没有了
        // 注意前面在创建匿名内部类的时候使用了new,JDK底层创建了匿名内部类之后立刻,马上就创建了这个Outer04$1的实例
        // 并且将这个地址返回给tiger这个变量,这个底层其实可以认为栈中有一个tiger指向了一个堆,堆中有一个对象实例,
        // 这个对象实例的类型就是Outer04$1
        // 匿名内部类使用一次就不能再使用,不是说tiger不能再使用,说的是Outer04$1
        // 演示基于类的匿名内部类
        Father father = new Father("jack") {  // 这里的jack还是会传递给Father构造器
            // 如果没有打大括号就是创建一个father对象,如果打了大括号就是一个匿名内部类
            // 这里的编译类型是Father,运行类型是什么,运行类型不是Father
            // Outer04$2,如果不带大括号就是Father
            // 这里面就可以重写test方法了

            @Override
            public void test() {
                System.out.println("匿名内部类重写了test方法");
            }
        };
        // 这里可以打印出运行类型进行证明
        System.out.println("father对象的运行类型 为" + father.getClass());
        // father对象的运行类型 为class com.alice.innerclass_.innerclass01.Outer04$2
        // 如果把之前写的dog匿名对象注释取消,这里就变成了$3了
        // 底层会创建匿名内部类,长的样子也差不多
        /*
            class Outer04$2 extends Father {

            }
         */
        father.test();  // 而因为当前的运行类型是Outer04$2,所以会绑定到上面重写的匿名内部类的test
        // 这是基于类的匿名内部类
        // 同时也是直接返回了Outer04$2的对象
        // 因为这里是一个普通的类Father,可以不用实现方法,如果是一个抽象类的就必须要实现方法了。
        // 比如这里创建一个抽象类Animal
        // 基于抽象类的匿名内部类
        Animal animal = new Animal() {
            @Override
            void eat() {
                System.out.println("小狗吃骨头");
            }
        };
        // 这里的编译类型是Animal,运行类型是Outer04$3
        System.out.println(animal.getClass());
        animal.eat();
    }
}

// Test一实现接口IA
//class Tiger implements IA {
//
//    @Override
//    public void cry() {
//        System.out.println("老虎嗷嗷叫");
//    }
//}
// Test二狗类
//class Dog implements IA {
//
//    @Override
//    public void cry() {
//        System.out.println("小狗汪汪叫");
//    }
//}
interface IA {
    public void cry();
}

class Father {
    public Father(String name) {
        System.out.println("name = " + name);
    }

    public void test() {

    }
}
abstract class Animal {
    abstract void eat();
}
/*
    匿名内部类非常重要,底层框架以及今后的开发中使用非常多
    所以前面其实打了三个叹号,并且难以理解,一些书籍和视频没有将最精华的地方讲到
    什么是匿名内部类?
    1本质是一个类,
    2还是一个内部类,
    3还是一个匿名的,没有名字的,该类没有名字
    匿名内部类其实还是有名字的,只是这个名字不是你能够直接看到的
    而是java虚拟机给你取得一个名字,
    所以匿名内部类是系统给你分配的
    你看不到,我们可以在底层打印出来,但是不能直接看到。
    4匿名内部类同时还是一个对象,

    匿名内部类是定义在外部类的局部位置,比如方法中,代码块中,并且没有类名
    基本语法:
    new 类或者接口(参数列表){类体};
    Anonymous这个单词是匿名的意思
 */
package com.alice.innerclass_.innerclass01;

public class AnonymousInnerClassDetail05 {
    public static void main(String[] args) {
        new Outer05().f1();
    }
}

/*
    2匿名内部类的语法比较特别,匿名内部类既是一个类的定义,同时它本身也是一个对象,因此
    从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,
    因此可以调用匿名内部类方法。
    3可以直接访问外部类的所有成员,包含私有的
    4不能添加访问修饰符,因为它的地位就是一个局部变量
    5作用域:仅仅在定义它的方法或者代码块中。
    6匿名内部类访问外部类成员,直接访问
    7外部其他类不能访问匿名内部类,因为匿名内部类的地位是一个局部变量
    8如果外部类和匿名内部类的成员重名的时候,匿名内部类访问,会默认遵守就近原则,如果想要访问外部类的成员
    ,则可以使用,外部类名.this.成员,去访问。和之前局部内部类同理
 */
class Outer05 {
    private int n1 = 99;
    public void f1() {
        // 创建一个基于类的匿名内部类
        Person p =  new Person() {  // 不能添加访问修饰符,因为它的地位就是一个局部变量
            @Override
            public void hi() {
                System.out.println("匿名内部类重写了hi方法");
                System.out.println("可以直接访问外部类的所有成员,hi方法 n1 = " + n1);
            }
            // 如果这里没有重写就会往上面找,也就是往父类找
            // 执行的是Person hi()
        };
        p.hi();  // p的编译类型是Person,运行类型是Outer05$1
        // 运行的时候会有动态绑定,找到匿名内部类重写了hi方法输出
        // 因为匿名内部类也同时是一个对象,也可以直接调用
        new Person() {  // 这里又创建了一个匿名内部类,这里我们不想要使用变量接收,直接点hi
            @Override
            public void hi() {
                System.out.println("匿名内部类重写了hi方法,哈哈。。。");
            }

            @Override
            public void ok(String str) {
                super.ok(str);
            }
        }.ok("jack");

    }
}
class Person {
    public void hi() {
        System.out.println("Person hi()");
    }
    public void ok(String str) {
        System.out.println("Person ok()" + str);
    }
}  // 另外抽象类还有接口都可以写...
package com.alice.innerclass_.innerclass01;

//public class AnonymousInnerClass06 {
//    public static void main(String[] args) {
//        f1(new IL() {  // 直接传递的是一个匿名内部类,也是一个对象,传统的写法应该怎么写呢?
//            @Override
//            public void show() {
//                System.out.println("这是一幅名画。。。");
//            }
//            // 这种方式可以比较随意,我们更改一下这里的匿名内部类只会影响到这个局部区域
//        });
//        // 传统的方式
//        f1(new Picture());  // 将实现了这个接口的对象传过去,这个叫做硬编码,这种硬编码不推荐,如果反复用这个对象还好
//
//    }
//    // 静态方法,形参是一个接口类型
//    public static void f1(IL il) {
//        il.show();  // 当前编译类型就是IL可以点到show
//    }
//}
//// 在什么时候我们会使用到匿名内部类呢?
//// 被当作实参直接传递的时候
//
//// 接口
//interface IL {
//    // 接口中有一个方法,叫做
//    void show();
//}
//// 传统的方式写一个类实现IL
//class Picture implements IL {
//    // 如果在这里更改会影响到所有这里的对象实例
//
//    @Override
//    public void show() {
//        System.out.println("这是一幅名画...");
//    }
//}

/*
    第二题
        1、有一个铃声接口Bell,里面有一个ring方法
        2、有一个手机类Cellphone,具有闹钟功能alarmClock,参数是Bell类型
        3、测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
        4、再传入另外一个匿名内部类(对象),打印:小伙伴上课了。
 */
public class AnonymousInnerClass06 {
    public static void main(String[] args) {
        Cellphone cellphone = new Cellphone();
        cellphone.alarmClock(new Bell() {
            @Override
            public void ring() {
                System.out.println("懒猪起床了");
            }
        });
        cellphone.alarmClock(new Bell() {
            @Override
            public void ring() {
                System.out.println("小伙伴上课了");
            }
        });
    }
}
interface Bell {
    void ring();
}
class Cellphone {
    public void alarmClock(Bell bell) {
        bell.ring();
    }
}
// 到此匿名内部类完成。匿名内部类涉及到了继承,多态,动态绑定机制,内部类。。

四、成员内部类

package com.alice.innerclass_.innerclass01;

public class MemberInnerClass07 {
    public static void main(String[] args) {
        Outer01 outer01 = new Outer01();
        outer01.t1();

        // 在外部其他类中使用我们的成员内部类的三种方式
        // 第一种方式是这样子
        // 使用外部类的对象,去调用方法
        // 这里使用外部类的对象创建了一个内部类的实例返回
        Outer01.Inner01 inner01 = outer01.new Inner01();
        inner01.say();
        // 如何去理解?最容易理解的其实是new Inner01(),但是我们这里需要知道,并没有写在外面
        // 所以这里的语法需要变化,这里当作一个成员来看,所以是outer01.new Inner01();

        // 第二种方式是在外部类中写一个方法然后将这个Inner01的对象实例即可
        Outer01.Inner01 inner08Instance = outer01.getInner01Instance();
        inner08Instance.say();


    }
}
class Outer01 {
    // 外部类
    private int n1 = 10;
    public String name = "张三";
    private void hi() {
        System.out.println("hi()方法...");
    }
    public class Inner01 {
        private double sal = 99.9;
        // 可以直接访问外部类的所有成员,包含私有的
        public void say() {
            System.out.println("Outer01的n1 = " + n1 + "  Outer01的name = " + name);
            hi();  // 可以直接调用外部类的所有成员不管是不是私有的,方法还是属性
        }
    }
    public void t1() {
        // 使用成员内部类
        Inner01 inner01 = new Inner01();
        inner01.say();
        System.out.println(inner01.sal);  // 外部类访问内部类
    }
    public Inner01 getInner01Instance() {  // 返回一个成员内部类的实例
        return new Inner01();
    }
}
/*
    成员内部类是定义在外部类的成员位置,并且没有static修饰
    1、可以直接访问外部类的所有成员,包括私有的
    2、可以添加任意访问修饰符
        public protected 默认 private,因为它的地位就是一个成员
    作用域
    3和外部类的其他成员一样,为整个类体
    比如前面的案例,在外部类的成员方法中创建成员内部类对象,再调用方法
    4成员内部类访问外部类,访问方法是直接访问
    5外部类访问内部类,访问方式是创建对象之后再访问
    6外部其他类访问成员内部类
    7如果外部类和内部类的成员重名的时候,内部类访问的话,默认遵循就近原则,如果想要访问外部类的成员,则可以使用
    外部类名.this.成员去访问,和以前一样。
    成员内部类结束
 */

五、静态内部类

package com.alice.innerclass_.innerclass01;

public class StaticInnerClass08 {
    public static void main(String[] args) {
        Outer10 outer10 = new Outer10();
        outer10.m1();

        // 外部其他类访问静态内部类
        // 方式一
        Outer10.Inner10 inner10 = new Outer10.Inner10();
        inner10.say();
        // 通过类名访问的静态内部类是可以的。因为静态内部类是可以通过类名直接访问,前提是需要有这种访问权限
        // 方式二
        // 编写一个访问返回静态内部类的一个实例
        Outer10.Inner10 inner101 = outer10.getInner10();
        inner101.say();

        Outer10.Inner10 inner10_ = Outer10.getInner10_();
        inner10_.say();
    }
}
class Outer10 {
    private int n1 = 10;
    private static String name = "张三";
    private static void cry() {

    }
    // Inner10就是静态内部类
    // 1、是放在外部类的成员位置
    // 2、使用static修饰
    public static class Inner10 {  // 由前面的知识可以知道,静态的只能访问静态的。
        private static String name = "贾宝玉";
        public void say() {
//            System.out.println(n1);  // 这里n1是一个非静态的,使用静态的访问非静态会报错
            System.out.println(name);  // 所以访问静态的是可以的。如果静态内部类中也有同名的成员则会就近原则
            System.out.println(Outer10.name);  // 因为是一个static的,所以这里直接类名输出即可
            cry();  // 访问静态方法也可以直接访问
        }
    }
    public void m1() {  // 外部类访问静态内部类可以创建对象然后再访问
        Inner10 inner10 = new Inner10();  // 作用域为整个类体,因为是成员
        inner10.say();
    }
    public Inner10 getInner10() {
        return new Inner10();
    }
    public static Inner10 getInner10_() {
        return new Inner10();
    }
}
/*
    静态内部类的使用
    说明:静态内部类是定义在外部类的成员位置,并且又static修饰
    1、可以直接访问外部类的所有静态成员,包含私有的,但是不能直接访问非静态成员
    2、可以添加任意访问修饰符public,protected,默认,private,因为它的地位就是一个成员
    3、作用域:同其他的成员,为整个类体
    4、静态内部类想要访问外部类比如静态属性可以直接访问
    5、外部类访问静态内部类可以创建对象,然后再访问
    6、外部其他类如何访问静态内部类呢?
    7、如果外部类和静态内部类的成员重名,静态内部类访问的时候,默认遵循的是就近原则,如果想要
    访问到外部类成员,则可以使用外部类名.成员的方式访问。


    小结:
    内部类由四种:
    局部内部类
    匿名内部类
    这两个内部类都是放到方法或者代码块中
    还有成员内部类,静态内部类
    重点需要掌握匿名内部类
    成员内部类和静态内部类放到成员位置
 */

六、结束

package com.alice.innerclass_.innerclass01;

public class InnerClass09 {
    public static void main(String[] args) {
        Test t = new Test();
        Test.Inner inner = t.new Inner();
        System.out.println(inner.a);
    }
}
/*
    关于内部类的题目
 */
class Test {
    public Test() {
        Inner s1 = new Inner();
        s1.a = 10;
        Inner s2 = new Inner();
        System.out.println(s2.a);
    }
    class Inner {
        public int a = 5;
    }
}
// 5 5,到此为止面向对象结束
posted @ 2025-05-14 23:27  请叫我虾  阅读(17)  评论(0)    收藏  举报