详解java各种内部类

简介: 详解java各种内部类

一、介绍

在java中,我们被允许在编写一个类(外部类OuterClass)时,在其内部再嵌套一个类(嵌套类NestedClass),java将嵌套类分为两种:非静态内部类(简称内部类)静态内部类,如下所示

public class OuterClass {
   

    class InnerClass {
   

    }

    static class StaticInnerClass {
   

    }
}

嵌套类作为外部类的一个成员,无论其是否为静态内部类,我们都可以对其添加任何的访问修饰符(publicprotectedprivatedefault),如下所示

public class OuterClass {
   

    public class InnerClass {
   

    }

    protected static class StaticInnerClass {
   

    }
}

非静态内部类对其外部类中的所有成员变量和方法都具有访问权,即便它被private修饰也可以。但是静态内部类对外部类中的成员变量和方法没有访问权。


二、为什么要使用内部类

内部类为我们提供了以下便利:

  • 它对只在一个地方使用的类进行逻辑分组

    如果一个类A.java只对另一个类B.java有用,那么将它嵌入到那个类中并把两个类放在一起岂不合理?即把A.java作为内部类,而将B.java作为外部类。

  • 它增加了封装性

    考虑两个类A.javaB.java,其中B.java需要访问A.java的被声明为private的成员变量或方法。通过将类B.java隐藏在类A.java中,A.java的成员变量或方法可以被声明为privateB.java也可以访问它们。另外,B.java本身也可以对外界隐藏。

  • 它使代码更加可读和可维护

    将内部类嵌套在外部类中使代码更接近使用它的地方。


三、非静态内部类

非静态内部类有以下特点:

  • 对其外部类中的所有成员变量和方法都具有访问权,即便它被private修饰也可以。
  • 不允许定义任何static修饰的成员变量和方法。
  • 外部类必须通过非静态内部类的实例对象访问其内部的属性和方法。
  • 编译后生成两个class文件:外部类.class外部类$内部类.class

当我们实例化一个非静态内部类的对象时,使用以下方法

  • 外部类中实例化内部类

    public class OuterClass {
         
    
        public void initInnerClass() {
         
            InnerClass innerClass = new InnerClass();
            innerClass.innerClassMethod_1();
        }
    
        class InnerClass {
         
            public void innerClassMethod_1() {
         
                System.out.println("调用内部类方法");
            }
        }
    }
    
  • 其他类中实例化内部类

    class InnerClassTest {
         
        public static void main(String[] args) {
         
            // 方式一:创建内部类innerClass实例
            OuterClass outerClass = new OuterClass();
            OuterClass.InnerClass innerClass1 = outerClass.new InnerClass();
            innerClass1.innerClassMethod_1();
    
            // 方式二:创建内部类innerClass实例
            OuterClass.InnerClass innerClass2 = new OuterClass().new InnerClass();
            innerClass2.innerClassMethod_1();
        }
    }
    

非静态内部类还有两种特殊的类型:局部内部类匿名内部类。后面细说。


四、静态内部类

静态内部类在行为上与其他类相似

  • 对外部类成员的访问只能通过外部类的实例对象。
  • 编译后生成两个class文件:外部类.class外部类$内部类.class

当我们实例化一个静态内部类的对象时,使用以下方法

  • 外部类中实例化内部类

    public class OuterClass {
         
    
        public void initStaticInnerClass() {
         
            StaticInnerClass staticInnerClass = new StaticInnerClass();
            staticInnerClass.staticInnerClassMethod_1();
        }
    
        static class StaticInnerClass {
         
            public void staticInnerClassMethod_1() {
         
                System.out.println("调用静态内部类方法");
            }
        }
    }
    
  • 其他类中实例化内部类

    class InnerClassTest {
         
        public static void main(String[] args) {
         
            OuterClass.StaticInnerClass staticInnerClass = new OuterClass.StaticInnerClass();
            staticInnerClass.staticInnerClassMethod_1();
        }
    }
    


五、局部内部类

在一个代码块声明的类叫局部内部类。此处的代码块指任何{}内部(如静态代码块、方法、for循环、if块)

  • 局部内部类可声明在任何代码块
  • 局部内部类内部可以访问其外部的成员变量,但该成员变量的值不允许被修改,因此我们需要使用final修饰。
  • 不允许定义任何static静态成员变量或方法
  • 静态方法中的局部内部类,只允许访问外部类中的static静态成员变量和方法。
  • 不允许被static修饰
  • {}代码块中不可以声明接口interface。因为interface接口天生就是静态的。
  • 局部内部类中仅允许在常量变量声明中使用static。从java16开始允许不被final修饰
  • 编译后生成外部类.class外部类+$+编号+内部类.class

创建局部内部类的方式如下:

public class OuterClass {
   

    public void localClassMethod() {
   

        for (int i=0; i<10; i++) {
   

            // for循环中的局部内部类
            class LocalClassInFor {
   

            }
        }

        if (true) {
   

            // if代码块中的局部内部类
            class LocalClassInIf {
   

            }
        }

        // 方法中的局部内部类
        class LocalClass {
   

        }
    }
}


六、匿名内部类

在一个方法内部声明的类但没有命名该类的名称局部内部类。匿名类使您的代码更加简洁。允许我们能够同时声明和实例化一个类。除了没有名字之外,它们类似于局部内部类。如果只需要使用一次局部内部类,就使用它们。

  • 本质上是一个表达式。
  • 实例化匿名内部类需要借助一个父类接口
  • 可以访问外部类的成员变量和方法。与局部内部类相同。
  • 不可以修改外部变量,但该变量的值不允许被修改,因此我们需要使用final修饰。与局部内部类相同。
  • 与外部变量或方法重名时,默认采用就近原则访问。与非静态内部类相同。
  • 不可以声明static静态变量或方法。但可以声明final static常量。
  • 编译后生成外部类.class外部类+$+编号.class

使用匿名内部类需要借助父类接口实现,其本质相同,都是对方法的重写。如下所示

  • 使用父类

    public class AnonymousOuterClass {
         
    
        class InnerClass {
         
            private String innerField = "field in InnerClass";
            public void getField() {
         
                System.out.println(innerField);
            }
        }
    
        public void anonymousInnerMethod() {
         
            // 声明并实例化匿名内部类
            InnerClass innerClass = new InnerClass() {
         
                private String innerField = "a";
                @Override
                public void getField() {
         
                    // 输出匿名内部类中的成员变量innerField
                    System.out.println(innerField);
                    // 输出父类类中的成员变量innerField
                    super.getField();
                }
            };
    
            innerClass.getField();
        }
    }
    
    class AnonymousOuterClassTest {
         
        public static void main(String[] args) {
         
            AnonymousOuterClass anonymousOuterClass = new AnonymousOuterClass();
            anonymousOuterClass.anonymousInnerMethod();
        }
    }
    
  • 使用接口

    public class AnonymousOuterClass {
         
    
        interface InnerInterface {
         
            void getField();
        }
        public void anonymousInnerInterfaceMethod() {
         
            InnerInterface innerInterface = new InnerInterface() {
         
                private String innerField = "field in inner interface";
                @Override
                public void getField() {
         
                    // 输出匿名内部类中的成员变量innerField
                    System.out.println(innerField);
                }
            };
    
            innerInterface.getField();
        }
    
    }
    
    class AnonymousOuterClassTest {
         
        public static void main(String[] args) {
         
            AnonymousOuterClass anonymousOuterClass = new AnonymousOuterClass();
            anonymousOuterClass.anonymousInnerInterfaceMethod();
        }
    }
    


七、lambda表达式内部类

使用lambda表达式内部类的理由很简单:当匿名内部类的父类或接口中只有一个方法时,其实现代码最少也得五六行,为了使代码简化,所以使用lambda表达式内部类

  • 编译后生成外部类.class。lambda表达式内部类不会生成单独的class文件。
  • 其余特性与匿名内部类相同。

除了实例化方式不同,lambda表达式内部类和匿名内部类其余使用限制完全一致。

public class LambdaOuterClass {
   

    interface InnerInterface {
   
        void sayHello();
    }

    public void sayHello() {
   
        // lambda表达式内部类
        InnerInterface innerInterface = () -> System.out.println("hello world");
        innerInterface.sayHello();
    }
}

class LambdaOuterClassTest {
   
    public static void main(String[] args) {
   
        LambdaOuterClass lambdaOuterClass = new LambdaOuterClass();
        lambdaOuterClass.sayHello();
    }
}


八、成员重名

无论是静态内部类还是非静态内部类,当内部类中与外部类具有相同名称的成员变量的情况下,当我们在内部类中使用该变量时,默认采用就近原则的方式访问该变量。如果需要访问外部类中的重名变量时,则需要通过OuterClass.this.field访问,如下所示

public class OuterClass {
   

    private String sameNameField = "the same name field in OuterClass";


    class InnerClass {
   

        private String sameNameField = "the same name field in InnerClass";

        public void testSameNameField() {
   
            // 访问内部类的重名变量
            System.out.println(sameNameField);
            // 访问外部类的重名变量
            System.out.println(OuterClass.this.sameNameField);
        }
    }
}


九、序列化

java强烈建议不要序列化内部类,包括局部内部类匿名类。当Java编译器编译某些结构时,比如内部类,它创建合成结构,这些是在源代码中没有相应构造的类、方法、字段和其他构造。合成结构使Java编译器能够在不改变JVM的情况下实现新的Java语言特性。然而,合成构造在不同的Java编译器实现中可能有所不同,这意味着在不同的实现中,类文件也可能不同。因此,如果我们序列化一个内部类,然后用不同的JRE实现反序列化它,可能会有兼容性问题


十、如何选择内部类

  • 非静态内部类

    当需要访问外部类实例的非public修饰的成员变量和方法时。

  • 静态内部类

    当不需要访问外部类实例的成员变量和方法时。

  • 局部内部类

    当我们需要创建一个类的多个实例,请访问其构造函数,或者引入一个新的命名类型(例如,因为您稍后需要调用其他方法)。

  • 匿名内部类

    当我们需要同时声明和实例化一个类,并且只需要使用一次局部内部类时。

  • lambda表达式内部类




纸上得来终觉浅,绝知此事要躬行。

————————我是万万岁,我们下期再见————————

相关文章
|
1月前
|
Java
java中,剩下的这两个内部类不太好理解!
java中,剩下的这两个内部类不太好理解!
12 0
|
1月前
|
Java 编译器
java中常见的几种内部类,你会几个?(未完)
java中常见的几种内部类,你会几个?(未完)
14 1
|
4月前
|
Java 数据安全/隐私保护
【零基础学Java】—内部类的概念与分类(三十)
【零基础学Java】—内部类的概念与分类(三十)
|
15天前
|
安全 Java 编译器
接口之美,内部之妙:深入解析Java的接口与内部类
接口之美,内部之妙:深入解析Java的接口与内部类
35 0
接口之美,内部之妙:深入解析Java的接口与内部类
|
17天前
|
Java API
Java基础—笔记—内部类、枚举、泛型篇
本文介绍了Java编程中的内部类、枚举和泛型概念。匿名内部类用于简化类的创建,常作为方法参数,其原理是生成一个隐含的子类。枚举用于表示有限的固定数量的值,常用于系统配置或switch语句中。泛型则用来在编译时增强类型安全性,接收特定数据类型,包括泛型类、泛型接口和泛型方法。
9 0
|
17天前
|
存储 Java
java接口和内部类
java接口和内部类
|
1月前
|
设计模式 Java
JAVA内部类
JAVA内部类
10 1
|
2月前
|
Java
JAVA基础--内部类和静态内部类、单例模式---静态内部类剖析
JAVA--内部类和静态内部类、单例模式---静态内部类剖析
38 9
|
4月前
|
Java
【Java基础】类名、抽象类、接口名的参数传递及成员内部类、局部内部类和匿名内部类的使用
【Java基础】类名、抽象类、接口名的参数传递及成员内部类、局部内部类和匿名内部类的使用
40 0
|
4月前
|
安全 Java
Java单例---静态内部类
Java单例---静态内部类
29 0