由一个NPE问题引发的内部类概览

在实际工作中遇到这么一个序列化的问题,需要根据一个用户表的老用户进行拓展,每一个老用户需要派生几个不同业务线的新用户。方法是在部署之后,从传入的参数中读取Json数据构建Map,Map的 Key 值为老的用户Id,Value 值为老用户对应的新的用户 Id 表。 为此,在拓展的任务代码里加入了如下内部类用于接收 Json:

 public class SomeJob{ 
    public void deserialization(String paramStr){
     … 
    }

    class FlushPair{
            long oldUserId;
            List<long> newUserIds;
    
        //getter and setter and default constructor
        …
    
    }    
}
</long>

Json 的格式如下所示:

 [ 
    { "newUserIds": [119,120,121], 
       "oldUserId":1 
    }, 
    { "newUserIds":[122,123,124], 
      "oldUserId\":2 
    }, 
    { "newUserIds":[125,126,127], 
      "oldUserId":13 }, 
     … 
] 

Json 的字符串命名为 paramStr,在下面代码中被反序列化:

import com.alibaba.fastjson.JSON; 
List<flushpair> pairs = JSON.parseArray(paramStr,FlushPair.class);
</flushpair>

这样做结果抛出了令人尴尬的 NPE ,经过单独的测试,发现是内部类的构造的问题: 内部类在构造时必须要利用外部类的对象通过 new 来创造内部类,

OuterClass.InnerClass innerClass = outerClass.new InnerClass();

如果我们尝试利用 fastJson 中的反射,在构造 JavaBean 时执行如下代码:

Constructor< ?> constructor = beanInfo.defaultConstructor; if(beanInfo.defaultConstructorParameterSize == 0) {
    object=constructor.newInstance(); 
}else { 
    ParseContext context=parser.getContext(); 
    String parentName = context.object.getClass().getName();
    StringtypeName=""; 
    if(typeinstanceofClass) {
        typeName=((Class< ?>)type).getName(); 
    } 
    …
}

在内部类的 beanInfo 的结构大概如下:

默认的构造器依旧是 Main.FlushPair(Main) 的形式,Main为测试的公有类,构造器的默认参数需要传递外部类的 class 变量,所以在上面的逻辑中会去 parser 中的 context 寻找父类的构造器,但是 parser 的 context 一般为 null,于是就会产生 NPE。

如果我们将类移到 Main 的外部,会发现 beanInfo 的结构会变成:

默认构造方法会变成 public 且参数的为空。原因是因为同一个文件中的两个类,生成了逻辑上无关联的两个 class,初始化方式互不影响,同一个文件可以理解为类似于同一个包,但是是包下级的一个目录结构。

那么有没有办法不将内部类拿出来,依然可以正常初始化呢? 有,将内部类声明为 static 即可,声明完的 beanInfo 如下图所示:

依然是Main.FlushPair() 的构造函数,但是参数已经为空了。原因是因为非静态内部类在编译完依然隐含地保存着一个外部类的引用,该引用指向其外围类,在构造时会被使用。但是静态的内部类没有这个引用,意味着创建是不需要外部类的。

但是从更通用广泛的意义上来讲,设计内部类是为了干什么呢? 在这个工作中,为了方便任务模块能够接收 Json 数据,既不希望 Job 类本身实现 Serializable 也不希望重新写一个新的文件接收 Json 数据,于是设计了一个内部类专用与 Job 接收 Json。更多情况下,如果我们希望一个类实现多继承的功能,我们可以设计多个内部类分别去继承对应的基类。内部类是 interface,abstract class 的一种补充。除此之外,内部类还有一些特性可以使用:

public class Father{
    private String name;
    class Baby{
        public Baby(){
            name="James";
        }
        public void cry(){
            System.out.println("I want"+name);
        }
    }
    public static void main(){
        Father james = new Father();
        Father.Baby harry = james.new Baby();
        harry.cry();
    }
}

在上面的例子中,虽然 name 为 private 属性,但是通过 Baby 的显式调用可以直接获取到,这是因为内部类的实例存在一个外部类的引用,只要通过内部类访问外部类的成员时,利用这个引用就可以拿到外部类的成员。内部类除了这种直接声明之外,还可以局部中声明,例如函数内部,但在作用域外面会失效。除此之外,还可以在声明为匿名类,例如一个典型的IoC过程:

button.addActionListener(
    new ActionListener(){
        @Override
        public void actionPerformed(){
            //do something
        }
    }
);

关于匿名类的内容,之前的文章也有涉及,细节不再多说,总的来说,匿名类没有修饰符,没有构造函数,当所在方法的形参需要被匿名内部类使用,那么这个形参就必须为final。

关于静态内部类,请看下面示例即可(来源)。

public class OuterClass {
    private String sex;
    public static String name = "rancho";
    
    /**
     *静态内部类
     */
    static class InnerClass1{
        /* 在静态内部类中可以存在静态成员 */
        public static String _name1 = "rancho_static";
        
        public void display(){
            /* 
             * 静态内部类只能访问外围类的静态成员变量和方法
             * 不能访问外围类的非静态成员变量和方法
             */
            System.out.println("OutClass name :" + name);
        }
    }
    
    /**
     * 非静态内部类
     */
    class InnerClass2{
        /* 非静态内部类中不能存在静态成员 */
        public String _name2 = "rancho_inner";
        /* 非静态内部类中可以调用外围类的任何成员,不管是静态的还是非静态的 */
        public void display(){
            System.out.println("OuterClass name:" + name);
        }
    }
    
    public void display(){
        /* 外围类访问静态内部类:内部类. */
        System.out.println(InnerClass1._name1);
        /* 静态内部类 可以直接创建实例不需要依赖于外围类 */
        new InnerClass1().display();
        
        /* 非静态内部的创建需要依赖于外围类 */
        OuterClass.InnerClass2 inner2 = new OuterClass().new InnerClass2();
        /* 方位非静态内部类的成员需要使用非静态内部类的实例 */
        System.out.println(inner2._name2);
        inner2.display();
    }
    
    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        outer.display();
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.