正文
记录一:使用初始化块初始化会导致用的是新的类
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 ,转载请保留原文链接.