侵删JAVA——反射
与传统的通过new 来获取对象的方式不同
反射机制,会先拿到Hero的“类对象”,然后通过类对象获取“构造器对象”
再通过构造器对象创建一个对象
1 : 创建一个对象
通过反射机制创建一个对象。
public class TestReflection {
public static void main(String[] args) {
//传统的使用new的方式创建对象
Hero h1 =new Hero();
h1.name = "teemo";
System.out.println(h1);
try {
//使用反射的方式创建对象
String className = "charactor.Hero";
//类对象
Class pClass=Class.forName(className);
//构造器
Constructor c= pClass.getConstructor();
//通过构造器实例化
Hero h2= (Hero) c.newInstance();
h2.name="gareen";
System.out.println(h2);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
2 : 练习-通过配置文件获取对象
首先准备一个文本文件:hero.config。 在这个文件中保存类的全名称,可以是charactor.APHero 或者是charactor.ADHero
接着设计一个方法叫做:
public static Hero getHero()
在这个方法中,读取hero.config的数据,取出其中的类名,根据类名实例化出对象,然后返回对象。
3 : 答案-通过配置文件获取对象
Hero h = getHero();
System.out.println(h);
通过打印h,可以发现,当配置文件里的内容发生变化的时候,就会得到不同的对象。
源代码不需要发生任何变化,只需要修改配置文件,就可以导致程序的逻辑发生变化, 这是一种基于配置的编程思想。
Spring框架中的IOC和DI的底层就是基于这样的机制实现的。
public class TestReflection {
public static void main(String[] args) throws InterruptedException {
Hero h = getHero();
System.out.println(h);
}
public static Hero getHero() {
File f = new File("E:/project/j2se/hero.config");
try (FileReader fr = new FileReader(f)) {
String className = null;
char[] all = new char[(int) f.length()];
fr.read(all);
className = new String(all);
Class clazz=Class.forName(className);
Constructor c= clazz.getConstructor();
Hero h= (Hero) c.newInstance();
return h;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
Java中new创建对象
图来自知乎
如果我们在代码中如果写了一段
1 | A a = new A() |
在JVM中会帮你做的事情有以下:
- JVM把类的.java文件编译为一个.class的字节码文件
- 类加载器把.class文件加载进jvm的内存中,一个Class对象生成,并放入方法区中,这Class对象对于任何类都是唯一一个。
做完这些之后,才是new字段的工作:
- 判断内存中是否已经有那个唯一的Class对象
- 如果没有,则进行上述操作。
- 如果有,则在内存中申请空间开辟,即根据Class对象获取有关的构造器进行实例化,在这里我们假设是一个无参数构造,那么只需要newInstance()。
Java中使用反射创建对象
依然是上面这一幅图,但是我们这次的代码是我们最常见的反射代码
1 | Class c = Class.forName("A的全类名"); |
当JVM编译到这段代码的时候,他的步骤是:
1、使用类加载,将对应.class加载入内存的方法区中,并返回Class对象。
这时候,我们可以查看这个类对象里面的构造器方法,并使用无参数构造器进行构造实例,即如下代码
1 | Constructor constructor = c.getConstructor(); |
用同样的图,我们可以画出来。到这里,我们几乎可以知道无论是反射,还是New,其实都是通过类加载器对.class文件加载进内存中,创建了Class对象。‘’
那么,在其他博客中提到的动态编译和静态编译就好理解了。
Java中反射属于动态编译,而new属于静态编译。
粗俗解释:
1、静态编译相当于把所有需要的东西都在初始化的时候加载了,如果程序一大,就很有可能会跑得慢。
2、动态编译,在编译的时候,需要的模块都没有编译进去,启动程序的时候,模块不会被加载而是在运行的时候,需要哪个模块就调用哪个模块。
上面的过程告诉我们,我们如果用new,那么我们要创建的类都是已经“写死”在.class文件里面了,我们无法控制JVM帮我们加载类的这一项工作。
但是如果我们用反射创建类对象,我们是相当于亲自“指导”JVM,我们“按需加载”.class文件,如果内存里面没有这个类的.class文件,那么我们用Class.forName()去叫类加载器帮忙加载就行了,而不是把程序停下来,再打一段代码,再让类加载器进行加载,从而体现出了Java的“动态性”。