JAVA | 内部类

127 阅读10分钟

1 内部类描述

把类定义在另一个类的内部,该类就被称为内部类。

所谓的内部类的概念只是出现在编译阶段,对于 jvm 层是没有内部类这个概念的

2 内部类的分类

  • 非静态成员内部类
  • 静态成员内部类
  • 成员方法内部类
  • 匿名内部类

2.1 非静态成员内部类

对于需要和外部类实例相关联的情况下,可以选择将内部类定义成成员内部类。

语法格式

class Outer {
     class Inner {
         // 逻辑块
     }
}

示例运行

public class CommonMember {
    private String name = "outer private var: hello";
    private static String address = "outer private static var: world";

    // 非静态方法
    void hello() {
        System.out.println("outer func: hello call");
    }

    // 静态方法
    static void world() {
        System.out.println("outer static func: world call");
    }

    // 内部类
    public class InnerClass {
        // 内部类中变量
        String innerName = "inner class var: inner-hello";

        void innerHello() {
            System.out.println("-------------打印成员变量和外部类成员变量----------");
            System.out.println(name);
            System.out.println(address);
            System.out.println(innerName);

            System.out.println("\n--------------外部类的函数调用---------");
            hello();
            world();
        }
    }

    // 其他类可调用此方法生成对应的对象
    public InnerClass getInnerClass() {
        return new InnerClass();
    }

    public void run() {
        InnerClass instance = new InnerClass();
        instance.innerHello();
    }
    
    public static void main(String[] args) {
        // 非静态成员内部类
        CommonMember commonMember = new CommonMember();
        commonMember.run();

        System.out.println("\n");
        
        // 模拟其他类使用内部类
        CommonMember.InnerClass commonMemberInner = commonMember.getInnerClass();
        commonMemberInner.innerHello();
    }
}

运行结果如下:

-------------打印成员变量和外部类成员变量----------
outer private var: hello
outer private static var: world
inner class var: inner-hello

--------------外部类的函数调用---------
outer func: hello call
outer static func: world call


-------------打印成员变量和外部类成员变量----------
outer private var: hello
outer private static var: world
inner class var: inner-hello

--------------外部类的函数调用---------
outer func: hello call
outer static func: world call

访问控制小结

  • 非静态成员内部类就像外部类的普通成员一样,可以访问外部类的属性及方法
  • 非静态成员内部类内部不允许存在任何静态变量或静态方法(static);因为成员内部类是属于对象的,而静态变量、静态方法会先于外部类的对象存在,因此不允许成员内部类存在静态属性、方法
  • 非静态成员内部类如果需在外部类的外部使用,则需通过调用外部类对象的普通方法创建。例如:
        CommonMember.InnerClass commonMemberInner = commonMember.getInnerClass();
        commonMemberInner.innerHello();

2.2 静态成员内部类

静态内部类的定义和普通的静态变量或者静态方法的定义方法是一样的,使用 static 关键字,只不过这次static 是修饰在 class 上的,一般而言,只有静态内部类才允许使用 static 关键字修饰,普通类的定义是不能用 static 关键字修饰的,这一点需要注意一下。

语法格式

class Outer {
     static class Inner {
         // 逻辑块
     }
}

示例运行

public class StaticInnerClass {
    private String name = "outer private var: hello";
    private static String address = "outer private static var: world";

    // 非静态方法
    void hello() {
        System.out.println("outer func: hello call");
    }

    // 静态方法
    static void world() {
        System.out.println("outer static func: world call");
    }

    // 内部类
    static class InnerClass {
        // 内部类中变量
        String innerName = "inner class var: inner-hello";

        void innerHello() {
            System.out.println("-------------打印成员变量和外部类成员变量----------");
            // 这里不能这样调用,错误
            // System.out.println(name);
            System.out.println(address);
            System.out.println(innerName);

            System.out.println("\n--------------外部类的函数调用---------");
            // 调用非静态方法,错误
            // hello();
            world();
        }
    }

    public void run() {
        StaticInnerClass.InnerClass instance = new StaticInnerClass.InnerClass();
        instance.innerHello();
    }

    public static void main(String[] args) {
        // 非静态成员内部类
        System.out.println("\n");

        StaticInnerClass.InnerClass innerInstance = new StaticInnerClass.InnerClass();
        innerInstance.innerHello();
    }
}

运行结果:

-------------打印成员变量和外部类成员变量----------
outer private static var: world
inner class var: inner-hello

--------------外部类的函数调用---------
outer static func: world call

访问控制小结

  • static 修饰的内部类称之为静态内部类,静态内部类和非静态内部类之间存在一个最大的区别;非静态内部类在编译完成之后会隐含的保存着一个引用,该引用是指向创建它的外围类,但是静态类没有
  • 静态内部类的创建不需要依赖外部类可以直接创建
  • 静态内部类不可以使用任何外部类的非 static 属性和方法
  • 静态内部类可以存在自己的成员变量包括非静态和静态属性和方法

2.3 成员方法内部类

定义在一个方法或者一个作用域里面的类,主要是作用域发生了变化,只能在自身所在方法和属性中被使用

语法格式

class Outer {
      public void method(){
          class Inner {
              // 逻辑块
          }
      }
  }

示例运行

public class FunctionInnerClass {
    private String name = "outer private var: hello";
    private static String address = "outer private static var: world";

    // 非静态方法
    void hello() {
        System.out.println("outer func: hello call");
    }

    // 静态方法
    static void world() {
        System.out.println("outer static func: world call");
    }

    // 非静态方法
    public void runOne() {
        class InnerClassOne {
            String innerName = "runOne func inner class local var: name";

            void show() {
                System.out.println("-------------打印成员变量和外部类成员变量----------");
                System.out.println(name);
                System.out.println(address);
                System.out.println(innerName);

                System.out.println("\n--------------外部类的函数调用---------");
                hello();
                world();
            }
        }

        InnerClassOne instance = new InnerClassOne();
        instance.show();
    }

    // 静态方法
    public static void runTwo() {
        class InnerClassTwo {
            String innerName = "runOne func inner class local var: name";

            void show() {
                System.out.println("-------------打印成员变量和外部类成员变量----------");
                // 不能访问非静态变量
                // System.out.println(name);
                System.out.println(address);
                System.out.println(innerName);

                System.out.println("\n--------------外部类的函数调用---------");
                // 不能访问非静态外部类成员方法
                // hello();
                world();
            }
        }

        InnerClassTwo instance = new InnerClassTwo();
        instance.show();
    }

    public static void main(String[] args) {
        FunctionInnerClass publicInstance = new FunctionInnerClass();
        publicInstance.runOne();
        
        System.out.println("\n");
        runTwo();
    }
}

运行结果如下:

-------------打印成员变量和外部类成员变量----------
outer private var: hello
outer private static var: world
runOne func inner class local var: name

--------------外部类的函数调用---------
outer func: hello call
outer static func: world call


-------------打印成员变量和外部类成员变量----------
outer private static var: world
runTwo func inner class local var: name

--------------外部类的函数调用---------
outer static func: world call

访问控制小结

  • 方法内部类不允许使用访问权限修饰符;publicprivateprotected 均不允许
  • 方法内部类对外部完全隐藏,除了创建这个类的方法可以访问它以外,其他地方均不能访问
  • 方法的访问区域范围就是方法内部类可以访问的区域范围

2.4 匿名内部类

一个没有名字的类,是内部类的简化写法。可能内部类的所有分类中,匿名内部类的名号是最大的,也是我们最常用到的,多见于函数式编程,lambda 表达式等(函数式编程又是一块骨头,这里不做描述)。

匿名内部类就是没有名字的内部类,在定义完成同时,实例也创建好了,常常和 new 关键字紧密结合。当然,它也不局限于类,也可以是接口 ,可以出现在任何位置。下面我们定义一个匿名内部类:

如果您必须重写类或接口的方法,则应该使用它。可以通过两种方式创建 Java 匿名内部类。

语法格式

new 普通类|抽象类|接口() {
    重写成员方法或变量;
}

示例运行

示例定义了三种类型,普通类,抽象类,接口,然后基于这三种类型做测试’

普通类

public class NormalClass {
    public String name = "NormalClass";

    void hello() {
        System.out.println("this is hello fun call");
    }

    void world() {
        System.out.println("this is world func call");
    }
}

抽象类

public abstract class AbstractClass {
    public abstract void run();
    public abstract void show();
}

接口

public interface NoNameInterface {
    void run();
}

测试类

public class TestRun {
    static void testNormalClass() {
        NormalClass instance = new NormalClass(){
            public String name = "no name innerclass";

            @Override
            void hello() {
                System.out.println("show var name: " + name);
                System.out.println("this is redefine hello func call");
            }

            @Override
            void world() {
                System.out.println("this is redefine world func call");
            }
        };

        instance.hello();
        instance.world();
    }

    static void testAbstractClass() {
        AbstractClass instance = new AbstractClass() {
            @Override
            public void run() {
                System.out.println("this is run func call");
            }

            @Override
            public void show() {
                System.out.println("this is show func call");
            }
        };

        instance.run();
        instance.show();
    }

    static void testInterface() {
        NoNameInterface instance = new NoNameInterface() {
            @Override
            public void run() {
                System.out.println("this is run func call");
            }
        };
        instance.run();
    }


    public static void main(String[] args) {
        System.out.println("-----------------基于通用类的匿名内部类---------------");
        // 普通类
        testNormalClass();

        System.out.println("\n");
        System.out.println("-----------------基于抽象类的匿名内部类---------------");
        testAbstractClass();

        System.out.println("\n");
        System.out.println("-----------------基于接口的匿名内部类---------------");
        testInterface();
    }
}

运行结果如下

-----------------基于通用类的匿名内部类---------------
show var name: no name innerclass
this is redefine hello func call
this is redefine world func call


-----------------基于抽象类的匿名内部类---------------
this is run func call
this is show func call


-----------------基于接口的匿名内部类---------------
this is run func call

从上述代码中可以很显然的让我们看出来,匿名内部类必定是要依托一个类或者接口,因为它是没有名字的,无法用一个具体的类型来表示。所以匿名内部类往往都是通过继承一个父类或者实现一个接口,重写或者重新声明一些成员来实现一个匿名内部类的定义。实际上还是利用了里式转换原理。

其实在看了上述四种内部类的原理之后,反而觉得匿名内部类的实现较为简单了。主要思路还是将内部类抽离出来,通过初始化传入外部类的实例以达到对外部类所有成员的访问。只是在匿名内部类中,被依托的父类不是他的外部类。匿名内部类的主要特点在于,没有名字,对象只能被使用一次,可以出现在任意位置。所以它的使用场景也是呼之欲出,对于一些对代码简洁度有所要求的情况下,可首选匿名内部类。

访问控制小结

  • 匿名内部类就是一个没有名字的方法内部类,因此特点和方法与方法内部类完全一致
  • 匿名内部类必须继承一个类,或抽象类,或者实现一个接口
  • 匿名内部类没有类名,因此没有构造方法
  • 匿名内部类使得编码更加简洁

3 为什么需要内部类

3.1 多继承

每个内部类都能独立地继承一个类(实现多个接口),无论外部类是否已经继承或者实现,对于内部类都没有影响。内部类的存在使得 Java 的多继承机制变得更加完善

3.2 封装

可以将存在一定逻辑关系的类组织在一起,同时可以对外界隐藏或关闭

作为一个类的编写者,我们很显然需要对这个类的使用访问者的访问权限做出一定的限制,我们需要将一些我们不愿意让别人看到的操作隐藏起来,

如果我们的内部类不想轻易被任何人访问,可以选择使用private修饰内部类,这样我们就无法通过创建对象的方法来访问,想要访问只需要在外部类中定义一个public修饰的方法,间接调用

3.3 匿名内部类实现回调功能

对于一些有大量冗余代码的回调函数可以通过匿名内部类来实现

我们用通俗讲解就是说在 Java 中,通常就是编写一个接口,然后你来实现这个接口,然后把这个接口的一个对象作以参数的形式传到另一个程序方法中, 然后通过接口调用你的方法,匿名内部类就可以很好的展现了这一种回调功能

public interface InterfaceWorld { 
    void show(); 
} 

public class RunTest{ 
    public test(InterfaceWorld instance){ 
        System.out.println("test call"); 
    } 
    
    public static void main(String[] args) { 
        RunTest instance = new RunTest(); 

        //这里我们使用匿名内部类的方式将接口对象作为参数传递到 test 方法中去了 
        md.test(new InterfaceWorld){ 
            public void show(){ 
                System.out.println("interface logic") 
            } 
        } 
   } 
}

3.4 解决继承及实现接口出现同名方法的问题

接口

public interface InterfaceWorld {
      void show();
  }

public class World {

    public void show() {
        System.out.println("parent class show func called");
    }

}

测试类:继承了上面的类同时实现了上面的接口

public class WorldTest extends World implements InterfaceWorld {
    public void show() {
    }
}

这样如何区分这个方法是接口的还是继承的,可以使用内部类解决这个问题

public class WorldTest extends World {

    private class innerClass implements InterfaceWorld {
        public void show() {
            System.out.println("接口的 show 方法");
        }
    }

    public InterfaceWorld getInnerClassObj() {
        return new innerClass();
    }


    public static void main(String[] args) {
        WorldTest instance = new WorldTest();
        InterfaceWorld obj = instance.getInnerClassObj();

        //调用继承而来的test()方法
        instance.show();

        //调用匿名内部类基于接口的实现
        obj.show();
    }
}

参考引用文章如下