Java 内部类日常记录

日常记录

Posted by fyypumpkin on February 26, 2019

正文

记录一:使用初始化块初始化会导致用的是新的类


    new HashMap()

上面就是一个初始化的写法,也叫双括号初始化,对于第一个括号很容易理解,就是创建了一个匿名的内部类,对于第二个括号,实际上就是类内部的初始化块,我用下面的写法可能更容易看懂

// 匿名内部类重写 put 方法
    HashMap<String, String> hashMap = new HashMap<String, String>() {
        @Override
        public String put(String key, String value) {
            return "1";
        }

        {
            System.out.println("匿名内部类初始化块");
            System.out.println("内部类存在外部引用 " + problem1.this);
        }
    };

在这个内部类中可以重写相关的方法,实际上,在对于变量不多的情况下,直接使用这种方式代码会比较简单,但是也会有一些问题,看下面代码

private void test2() {
    HashMap hashMap1 = new HashMap();
    HashMap hashMap2 = new HashMap();

    System.out.println(hashMap1.getClass() + " " + hashMap2.getClass());
    System.out.println(hashMap1.getClass() ==  hashMap2.getClass());
}

看一下输出

class java.util.HashMap class inner_class_pro.problem1$4
false

可以看到,他们两个的 class 并不是同一个,第二个 hashMap 通过匿名内部类来初始化,实际上就会改变其类,所以 hashMap2.getClass() 返回的是其内部类,所以正式环境中使用一定要注意这个

记录二:内部类反射构造对象

反射是一个比较重要的工具,可以通过反射获取到相应的实例,那么请来看一下下面的代码

public class problem2 {
    class InnerClass {
        public InnerClass(Integer integer) {
        }
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Class clazz = InnerClass.class;

        Constructor constructor2 = clazz.getConstructor(new Class[]{Integer.class});
    }
}

上面这段代码看似没有问题,但是实际运行是会报错的

Exception in thread "main" java.lang.NoSuchMethodException: inner_class_pro.problem2$InnerClass.<init>(java.lang.Integer)
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getConstructor(Class.java:1825)
	at inner_class_pro.problem2.main(problem2.java:20)

可以从错误中看到,实际上并不能找到相应的构造器,很奇怪吧,命名在代码中指定了,但却告诉你找不到,这是为什么呢?实际上是因为内部类会持有外部类的引用,那么通过什么传递呢?答案就是构造器,内部类会默认将构造器第一个参数,实际上上面的内部类的构造器类似于下面这样

public class problem2 {
    class InnerClass {
        public InnerClass(problem2 problems, Integer integer) {
        }
    }
}

既然是这样的,那我们用反射传两个参数试试看

public class problem2 {
    class InnerClass {
        Integer integer ;
        public InnerClass(Integer integer) {
            this.integer = integer;
        }

        public void test() {
            System.out.println("inner class and integer = " + integer);
        }
    }

    public static void main(String[] args) throws Exception {
        Class clazz = InnerClass.class;
        Constructor constructor = clazz.getConstructor(new Class[]{problem2.class, Integer.class});


        // 内部类会默认将第一个构造入参设置为外部类的引用
        
        ((InnerClass)constructor.newInstance(new problem2(),1)).test();
    }
}

输出

inner class and integer = 1

这个输出也证实了我们的猜想

记录三:匿名内部类获取泛型

泛型大家一定很熟悉,Map<String, Integer> 类似的写法代码中很常见,相信写业务的应该也会经常接触 fastjson, 类似 JSON.parseObject(obj, new TypeReference<Map<String, Integer»() {}); 那么有个问题,fastjson 是如何获取到我们刚才定义的 Map<String, Integer> 的呢? 有人会说,我们不是在 parseObject 方法中第二个参数给他指定了么?是的的确是这样,但是 java 原生可没有为我们提供获取当前类的泛型的方法,那么 fastjson 又是如何做的呢? 细心的人一定发现了,fastjson 第二个参数实际上并不是 new 了一个对象,而是一个匿名内部类,java 虽然没有提供本类泛型获取的方法,但是提供了父类泛型提供的方法,新建的匿名内部类会默认继承真实的类(如果是接口就实现),下面我简单的写了一个 demo 来验证一下


class certTest {
    public static void main(String[] args) {
        CertTestI<String> certTestI = new CertTestI<String>() {
        };
        CertTestC<String> certTestC = new CertTestC<String>() {
        };
    }
}

interface CertTestI<T> {

}

class CertTestC<T> {

}

我写了一个接口和一个类,然后创建这两个类的匿名内部类,看一下反编译后的 class 长什么样

  • 对于接口的匿名内部类

final class certTest$1 implements CertTestI<String> {
    certTest$1() {
    }
}

  • 对于类的匿名内部类

final class certTest$2 extends CertTestC<String> {
    certTest$2() {
    }
}

主要看在 main 方法中生成的两个匿名内部类的源码,可以看到,他们都继承或者实现了真实的类,这个时候,我们便可以通过获取到的匿名内部类使用 java 提供的获取父类的泛型的方法拿到相应的泛型类型 (Class 下的 getGenericSuperclass 方法)

本文首次发布于 fyypumpkin Blog, 作者 @fyypumpkin ,转载请保留原文链接.