关于Java里面的嵌套类,你了解多少?

前言

最近在看《Core Java for the Impatient》这本书,当然为了方便我看的是英文电子版的PDF格式(有需要的朋友,可以后台留言给我),期间又重新认识或升级了不少Java相关的知识,今天我们来聊一聊Java里面的内嵌类,又称嵌套类。

什么是嵌套类?

简单的说,就是把一个类定义在另外一个类里面,使两者拥有更亲密的关系。比如A类里面可以包含B,B类里面可以包含C,C类里面可以包含D,至于能嵌套多少层,在Java语言里面并没有明确的限制嵌套的层级,你可以无限的嵌套,但大多数情况下,嵌套的层级超过2层会是一个糟糕的设计。

嵌套类的意义

在Oracle官网文档里面,有如下的描述:

(1)是一种逻辑分组的方法,仅仅只在一个地方使用。也就是说这个嵌套类存在仅仅只为它的外部类服务。比如各种”Helper Class”

(2)它增加了封装性。我们都知道继承,封装,抽象,多态是Java语言最重要的四大特点。嵌套类对相对于其他外部的类是隐藏的。

(3)增加了可读性和可维护性。把相关的类定义在一个类文件里面在阅读和维护方法变得更加有利。

嵌套类的分类

(1)静态嵌套类(Static nested classes)

(2)非静态嵌套类(Non-static nested classes)又称内部类(Inner Class)

(3)本地类(Local classes)

(4)匿名类(Anonymous classes)

不同嵌套类的使用方法和特点

(1)静态嵌套类

特点:

1.1:作为静态的嵌套类,它属于它的外部类(enclosing class),但它不是外部类的实例

1.2: 类声明可以使用所有的访问修饰符

1.3:它仅仅可以访问外部类的静态字段和静态方法,私有的也可以

1.4:它可以定义静态和非静态的成员

示例如下:

public class Enclosing {

    private static int x = 1;

    public static class StaticNested {

        private void run() {
            // method implementation
        }
    }

    @Test
    public void test() {
        Enclosing.StaticNested nested = new Enclosing.StaticNested();
        nested.run();
    }
}


(2)非静态嵌套类

特点:

2.1: 它们通常也称为内部类(inner class)

2.2: 类声明可以使用所有的访问修饰符

2.3: 像外部类的成员变量和成员方法一样,内部类也可以认为是外部类的一个成员属性

2.4: 可以访问外部类所有的成员,包括静态和非静态的

2.5: 除了static final修饰的编译时常量成员外,内部只能定义非静态成员

示例如下:

public class Outer {

     static final int a=3; // 正确
     static int b=5; // 错误
     static final Object obj=null //错误,非编译时常量
     String c;//正确
     double d;//正确


    public class Inner {
        // ...
    }

    public static Inner getInner1(){

        return new Inner(); //错误,静态方法,不能访问非静态成员
    }

    public Inner getInner2(){

        return new Inner(); //正确
    }

}

外部如何实例化:

Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();

或者

Outer.Inner inner = new Outer().new Inner();


或者(如果内部类是私有的情况下)

Outer.Inner inner = new Outer().getInner2();

(3)本地类(Local classes)

特点:

3.1: 可以定义在一个方法里面或者{}代码块里面。 (是的,你没听错,可以在方法里面定义类)

3.2: 类声明不能使用任何访问修饰符

3.3: 可以访问外部类所有的成员,包括静态和非静态的

3.4: 除了static final修饰的编译时常量成员外,内部只能定义非静态成员

3.5: 仅仅在所在的方法块或者代码内有效。

示例如下:

public class NewEnclosing {

    {
        class BlockLocal{

            void print(){
                //....
            }
        }

    }

    void run() {
        class Local {

            static final int a=3; // 正确
            static int b=5; // 错误
            static final Object obj=null //错误,非编译时常量
            String c;//正确
            double d;//正确

            void run() {
                // method implementation
            }
        }
        Local local = new Local();
        local.run();
    }

    @Test
    public void test() {
        NewEnclosing newEnclosing = new NewEnclosing();
        newEnclosing.run();
    }
}

(4)匿名类(Anonymous classes)

特点:

4.1: 匿名类是一次性使用的类,可以用于实现一个接口或者抽象类仅仅临时用一次,不能复用。

4.2: 可以访问外部类所有的成员,包括静态和非静态的

4.3: 除了static final修饰的编译时常量成员外,内部只能定义非静态成员

4.4: 是唯一一种不能定义构造方法不能使用继承功能不能使用实现接口功能的类。

示例如下:

先定义一个抽象类:

abstract class SimpleAbstractClass {
    abstract void run();
}

然后看如何定义匿名类:

public class AnonymousInnerTest {

    @Test
    public void whenRunAnonymousClass_thenCorrect() {
        SimpleAbstractClass simpleAbstractClass = new SimpleAbstractClass() {
            void run() {
                // method implementation
            }
        };
        simpleAbstractClass.run();
    }
}

关于this冲突

通过前面的介绍,我们知道一个类里面可以再次定义一个类,那么问题来了,如果在嵌套类里面使用this关键词 究竟引用的是谁?

答案是嵌套类的this是引用的本身,如果想在嵌套类里面使用外部类的this,那么必须使用外部类的类名类引用this:

示例如下:

public class NewOuter {

    int a = 1;
    static int b = 2;

    public class InnerClass {
        int a = 3;
        static final int b = 4;

        public void run() {
            //内部类引用this
            System.out.println("a = " + this.a);
            System.out.println("b = " + b);
            //外部类引用this
            System.out.println("NewOuterTest.this.a = " + NewOuter.this.a);
            System.out.println("NewOuterTest.b = " + NewOuter.b);
            System.out.println("NewOuterTest.this.b = " + NewOuter.this.b);
        }
    }

    @Test
    public void test() {
        NewOuter outer = new NewOuter();
        NewOuter.InnerClass inner = outer.new InnerClass();
        inner.run();

    }
}

关于序列化

为了避免内部类发生序列化异常:

java.io.NotSerializableException

有如下两种方法避免:

(1)把内部类声明成静态类型,在Java里面静态变量是不会被序列化的,其属于类信息,保存在全局区域,在实例还没初始化前就已经存在了,所以不需要担心序列化的问题。

(2)确保每一个非静态嵌套类都要实现Serializable接口。

结论

本篇文章详细的介绍了Java里面有关嵌套类的知识,包括嵌套类的定义,意义,分类,特点和一些使用方法,关于嵌套类使用最多的场景当属于Java里面GUI开发的源码包里面包括AWT和SWING,当然Socket类,HashMap的源码中也都有嵌套类的影子,了解这些知识将更有助于我们开发中合理的使用它们。

最后,给大家留一个思考问题:

为什么非静态嵌套类里面(Inner Class)不能定义静态成员呢?

下篇文章中,我们再聊一聊。

如果觉得本文不错,欢迎转发,让更多的小伙伴可以看到和学习。

参考链接:

https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html

http://www.baeldung.com/java-nested-classes

https://stackoverflow.com/questions/1953530/why-does-java-prohibit-static-fields-in-inner-classes

https://stackoverflow.com/questions/70324/java-inner-class-and-static-nested-class

https://stackoverflow.com/questions/20252727/is-not-an-enclosing-class-java

Top