JAVA 内部类

内部类

一、基本介绍

一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员

【思考:类的五大成员是哪些?[属性、方法、构造器、代码块、内部类]】

内部类最大的特点:就是可以直接访问私有属性并且可以体现类与类之间的包含关系

注意:

  • 内部类是学习的难点,同时也是重点,后面看底层源码时,有大量的内部类.

基本语法:

class Outer{//外部类
    class Inner{//内部类
        
    }
}

class Ohter{}//外部其他类

二、内部类的分类

1.定义在外部类局部位置上 (比如说方法体内):

  • 局部内部类(有类名)
  • 匿名内部类(没有类名,重点!!!!),用的非常的多

2.定义在外部类的成员位置上:

  • 成员内部类(没有static修饰)
  • 静态内部类(使用static修饰)

2.1局部内部类

说明:局部内部类是定义在外部类的局部位置,通常在方法中,并且有类名

  1. 可以访问外部类的所有私有属性,即其他属性
  2. 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。
    • 但是可以使用final修饰符,因为局部变量也可以使用final修饰符
  3. 作用域:仅仅在定义它的方法或者代码块中.
  4. 局部内部类可以直接访问外部类的成员
  5. 外部其它类不能访问局部内部类
  6. 如果外部类和局部内部类的成员重名时,默认遵守就近原则,如果想要访问外部类的成员,则可以使用外部类名.this.成员去访问
    • 它的本质其实就是外部类的对象,即哪个对象调用了内部类所在的方法或者代码块,就指向那个

记住:

  • 位置:局部内部类是定义在一个类的方法/代码块中
  • 作用域:在方法体或者代码块中
  • 本质:本质仍然是个类,特别的一点就是可以访问外部类的所有成员,包括私有的
package com.hspedu.innerclass;

/**
 * 演示局部内部类的使用
 */
public class LocalInnerClass {
    public static void main(String[] args) {
        Outer01 outer01 = new Outer01();
        outer01.m1();
        /*
        内部类Inner01 f1() 方法..
        n1=100
        外部类的m2()方法..
         */
        System.out.println("outer01 hashcode=" +outer01.hashCode() );
    }
}


class Outer01{ //外部类
    private int n1 = 100;
    private void m2(){
        System.out.println("外部类的m2()方法..");
    }

    private void f1(){
        System.out.println("外部类Outer01的 f1()..");
    }
    public void m1(){
        //1.局部内部类是定义在外部类的局部位置,通常在方法中
        //3.不能添加访问修饰符,但是可以使用final修饰
        //    加了final就这个类就不能被继承了
        //4.仅仅在定义它的方法或者代码块中
        class Inner01{ //局部内部类(本质上还是一个类)
            //2.可以访问外部类的私有属性,以及其他成员
            private int n1 = 800;
            public void f1(){
                //5.局部内部类可以直接访问外部类的成员 比如访问了 外部的n1,m2()
                //7.如果外部类和局部内部类的成员重名时,默认遵守就近原则,
                // 如果想要访问外部类的成员,则可以使用外部类名.this.成员去访问

                System.out.println("内部类Inner01 f1() 方法..");
                System.out.println("内部类的 n1=" + n1);
                //Outer02.this 本质就是外部类的对象,
                //即哪个对象调用了m1,Outer01.this就指向了哪个对象
                System.out.println("外部类的 n1=" + this.n1);
                System.out.println("Outer01.this hashcode=" + Outer01.this.hashCode());
                m2();
            }
        }

        //6.外部类在方法中,可以创建inner01这个对象,然后调用方法即可
        Inner01 inner01 = new Inner01();
        inner01.f1();
        f1();

        class Inner02 extends Inner01{

        }

    }
}

2.2匿名内部类

基本介绍:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名

  • 本质是类

  • 且是一个内部类

  • 该类没有名字

  • 同时还是个对象

基本语法:

new 类或接口(参数列表){
    类体
};

2.2.1基于接口

使用匿名内部类的好处:

  1. 如果想要使用某个接口,就要让一个类实现这个接口,并创建对象,若只想使用一次,后面都不再使用,这样就显得很费事
  2. 故使用匿名类部类,能简化开发步骤

注意:

  • 匿名内部类在底层是会分配一个名字的,并且立马实例化,把地址返回
    • 匿名内部类使用一次,就不再使用了,在内存中找不到了
    • 分配的类名为:类名$1

2.2.2基于类

大部分和和基于接口的一致,不过需要注意

  • 他的运行类型还是外部类$数字
  • 且如果有参数,这个参数是直接传给对象的构造器中,进行使用的
package com.hspedu.innerclass;


/**
 * 演示匿名类的使用
 */
public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer02 outer02 = new Outer02();
        outer02.method();
    }
}

class Outer02 {//外部类
    private int n1 = 10;//属性

    public void method() {
        //基于接口的匿名内部类
        //老韩解读
        //1.需求:想使用IA接口,并创建对象
        //2.传统方式,写一个类实现接口,并创建对象
        //3.需求:Tiger这个对象只使用一次,后再不使用了
        //4.可以使用匿名内部类来简化开发
        //5.tiger的编译类型 是A 接口,
        //         运行类型就是匿名内部类 XXXX = Outer02$1
        /*
            我们看底层,系统会分配类型 类名$1 用一次就没有了
            class XXXX implement IA{
                 @Override
                 public void cry() {
                    System.out.println("老虎叫唤..");
                 }
            }
         */
        //7.jdk底层在创建匿名内部类 Outer02$1,立马创建Outer02$1实例,并把地址返回给tiger
        //8.匿名内部类使用一次,就不再使用了,在内存中找不到了
        A tiger = new A() {
            @Override
            public void cry() {
                System.out.println("老虎叫唤..");
            }
        };
        System.out.println("tiger的运行类型=" + tiger.getClass());
        //tiger的运行类型=class com.hspedu.innerclass.Outer02$1

        tiger.cry();
        tiger.cry();
        tiger.cry();
        //A tiger = new Tiger();
        //tiger.cry();


        //基于类的匿名内部类
        //分析
        //1.father编译类型 Father
        //2.father运行类型 Outer01$2
        //3.底层
        /*
            class Outer02$2 extend Father{
               public void test() {
                    System.out.println("匿名内部类重写了test方法..");
               }
            }
         */

        //4. 同时也直接返回了 匿名内部类 Outer02$2的对象
        //5. 注意:("jack") 会传递给Father构造器
        Father father = new Father("jack") {
            @Override
            public void test() {
                System.out.println("匿名内部类重写了test方法..");
            }
        };
        System.out.println("father对象的运行类型=" + father.getClass());
        //father对象的运行类型=class com.hspedu.innerclass.Outer02$2
        father.test();

        //基于抽象类的匿名内部类
        Animal animal = new Animal(){
            @Override
            void eat() {
                System.out.println("小狗吃东西..");
            }
        };
        System.out.println("animal的运行类型=" + animal.getClass());
        animal.eat();

    }
}

interface A {//接口

    public void cry();
}

/*class Tiger implements A {
    @Override
    public void cry() {
        System.out.println("老虎叫唤..");
    }
}

class Dog implements A{

    @Override
    public void cry() {
        System.out.println("小狗叫唤..");
    }
}*/

class Father {//类
    String name;
    public Father(String name) {//构造器
        System.out.println("接收到name:" + name);
        this.name = name;
    }

    public void test() {

    }
}

class B extends Father{

    public B(String name) {
        super(name);
    }
}

abstract class Animal{
    abstract void eat();
}

2.2.3注意事项

1.匿名内部类的语法比较奇特,请大家注意,因为匿名内部类既是一个类的定义同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法。

2.可以直接访问外部类的所有成员,包含私有的

3.不能添加访问修饰符,因为它是个局部变量

4.作用域:仅仅是在代码块中

5.匿名类可以直接访问外部类的成员

6.外部其他类不能访问匿名类

7.如果外部类和内部类的成员重名时,匿名内部类访问的话,默认遵守就近原则,如果想要访问外部类的成员,则需要使用外部类名.this.成员去访问

package com.hspedu.innerclass;

public class AnonymousInnerClassDetail {
    public static void main(String[] args) {
        Outer03 outer03 = new Outer03();
        outer03.f1();
        System.out.println("outer03 hashcode=" + outer03.hashCode());
    }
}

class Outer03{
    private int n1 = 99;

    public void f1(){
        //创建一个匿名内部类
        Person p = new Person(){
            private  int n1 = 200;
            @Override
            public void hi() {
                System.out.println("匿名内部类重写了 hi方法");
                System.out.println("匿名类中的 n1="+n1);//200
                System.out.println("外部类中的 n1="+Outer03.this.n1);//99
                //Outer03.this 就是调用f1 的那个对象
                System.out.println("Outer03.this hashcode=" + Outer03.this.hashCode());
            }
        };
        p.hi();//动态绑定,找到匿名类中的hi

        //也可以直接调用,匿名内部类本身也是返回对象的,参数也可以穿
        new Person(){

            @Override
            public void hi() {
                System.out.println("匿名内部类重写了 hi方法,哈哈哈");

            }

            @Override
            public void ok(String str) {
                System.out.println("匿名类 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);
    }
}

2.2.4匿名内部类的最佳实践

  1. 匿名内部类可以当做实参直接传递,简洁高效
package com.hspedu.innerclass;

public class InnerClassExercise {
    public static void main(String[] args) {
        CellPhone cellPhone = new CellPhone();

        //1.这里把Bell 接口的匿名内部类传给了AlarmClock方法
        //2.并重写了ring方法,通过动态绑定机制,调用的是匿名内部类中的ring方法
        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) {
        System.out.println(bell.getClass());
        bell.ring();//动态绑定机制
    }
}

2.3成员内部类

说明:成员内部类是定义在外部类的成员位置,并且没有static修饰

  1. 可以直接访问外部类的所有成员,包含私有的

  2. 可以添加任意的访问修饰符(public,protected,默认,private)

  3. 作用域:和外部的其他成员一样,为整个类体

  4. 成员内部类能直接访问外部类的成员

  5. 外部类 要使用 成员内部类,要创建成员内部类的对象,然后调用相关的属性或/方法

  6. 外部其他类 使用成员内部类

    • 方法一:用外部类的对象去new一个成员内部类;Outer04.Inner08 inner08 = outer04.new Inner08();

    • 方法二:在外部类中编写一个方法,可以返回成员内部类inner08的实例

      Outer04.Inner08 inner081 = outer04.getInner08Instance();

  7. 如果外部类和成员内部类的成员重名时,成员内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

package com.hspedu.innerclass;

public class MemberInnerClass {
    public static void main(String[] args) {
        Outer04 outer04 = new Outer04();
        outer04.t1();
        //n1=10 name=张三

        //外部其它类,使用成员内部类的方法
        //方式一:用外部类的对象去new一个成员内部类;
        //      相当于把new Inner08()当做outer04的成员
        Outer04.Inner08 inner08 = outer04.new Inner08();//这就是个语法
        inner08.say();

        //方式二:在外部类中编写一个方法,可以返回成员内部类inner08的实例
        Outer04.Inner08 inner081 = outer04.getInner08Instance();
        inner081.say();

        //方式三:
        new Outer04().new Inner08();
    }
}

class Outer04{//外部类
    private int n1 = 10;
    public String name = "张三";

    public void hi(){
        System.out.println("hi"
        );
    }

    //注意成员内部类是定义在外部类的成员位置上。
    //可以添加任意的访问修饰符(public,protected,默认,private)
    class Inner08 {//成员内部类
        private double sal = 88.8;
        //作用域:外部类都可以使用这个成员内部类
        public void say(){
            //可以直接访问外部类的所有成员,包括私有的
            System.out.println("n1=" + n1 + " name=" + name);
            hi();
        }
    }

    //该方法返回一个成员内部类的实例
    public Inner08 getInner08Instance(){
        Inner08 inner08 = new Inner08();
        return inner08;
    }
    //写一个方法
    public void t1(){
        //使用成员内部类
        //外部类 要使用 成员内部类,要创建成员内部类的对象,然后调用相关的属性或/方法
        Inner08 inner08 = new Inner08();
        inner08.say();
        System.out.println(inner08.sal);
    }
}



2.4静态内部类

说明:静态内部类是定义在外部类成员位置上的,且有static修饰

  1. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员

  2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员

  3. 作用域:同其他的成员,为整个类体

  4. 静态内部类访问外部类的静态成员:直接访问即可

  5. 外部类访问静态内部类:创建对象再访问

  6. 外部其他类访问静态内部类:

    • 方式一:因为静态内部类,是可以通过类名直接访问的(前提是满足访问权限)

      Outer05.Inner10 inner10 = new Outer05.Inner10();

    • 方法二:编写一个方法,返回静态内部类,通过外部类名.静态类名 对象名 接受即可,可以写成静态的

      Outer05.Inner10 inner101 = outer05.getInner10Instance();

  7. 如果外部类和静态内部类的成员重名时,静态内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

package com.hspedu.innerclass;

public class StaticInnerClass {
    public static void main(String[] args) {
        Outer05 outer05 = new Outer05();
        outer05.m1();

        //方式一:因为静态内部类,是可以通过类名直接访问的(前提是满足访问权限)
        Outer05.Inner10 inner10 = new Outer05.Inner10();
        inner10.say();

        //方式二:编写一个方法,返回静态内部类,通过外部类名.静态类名 对象名 接受即可
        Outer05.Inner10 inner101 = outer05.getInner10Instance();
        inner101.say();
    }
}

class Outer05{
    private int n1 = 10;
    private static String name = "张三";

    private static void cry(){};

    //Inner10就是一个静态内部类
    //1.它是外部类的成员位置上的
    //2.使用了static 修饰
    //3.可以访问外部类的静态类的所有成员,但不能访问非静态类的成员
    //4.可以添加任意访问修饰符(public、protected、默认、private)
    //5.作用域:同其他成员一样,为整个类体
    public static class Inner10{
        public void say(){
            System.out.println(name);
            cry();
        }
    }

    public void m1(){
        Inner10 inner10 = new Inner10();
        inner10.say();
    }

    public Inner10 getInner10Instance(){
        return new Inner10();
    }
}

posted @ 2022-02-21 12:19  DL50  阅读(30)  评论(0编辑  收藏  举报