一、反射概述
二、理解Class类
1.类加载的过程
- 首先程序通过javac命令,编译成字节码文件.class
- 然后使用java命令运行.class文件,相当于把字节码文件加载进内存。这个过程就是类加载的过程。
- 加载到内存中的类,我们叫做运行时类,运行时类是Class类的对象。(体现了万物皆对象,类也是对象)
- 换句话说,Class类的对象就是运行时类
三、获取Class类的实例的四种方式
——Class类的实例其实就是运行时类
public class HTreflection {
public static void main(String[] args) throws ClassNotFoundException {
//方式一:通过运行时类的属性.class
Class clazz1 = persons.class;
System.out.println(clazz1); // class persons
//方式二:通过运行时类的对象的getClass()方法
persons p = new persons();
Class clazz2 = p.getClass();
System.out.println(clazz2); // class persons
//方式三:通过Class的静态方法——forName(String className)
Class clazz3 = Class.forName("persons");
System.out.println(clazz3); //class persons
Class clazz4 = Class.forName("java.lang.String");//这里如果写"String"就会报错,要写全
System.out.println(clazz4); // class java.lang.String
//方式四:使用类加载器ClassLoader()
ClassLoader classLoader = persons.class.getClassLoader();
Class clazz5 = classLoader.loadClass("persons");
System.out.println(clazz5);
//说明person类加载进内存的时候只会加载一个(加载多了也没必要)。因此上面四种方式叫如何获取Class类的实例,而不是如何创建Class类的实例
System.out.println(clazz1==clazz2);//true
System.out.println(clazz1==clazz3);//true
System.out.println(clazz1==clazz5);//true
}
}
class persons{
private String name;
public int age;
@Override
public String toString() {
return "name:"+name+"_age:"+age;
}
public persons(){}
public persons(String name,int age){
this.name = name;
this.age = age;
}
private persons(String name){
this.name = name;
}
private void show(){
System.out.println("我要进大厂!");
}
public String getName(){
return this.name;
}
}
四、类的加载与ClassLoader的理解
- 每个类或者接口等在编译后都会有一个.class字节码文件
- 类加载器会把.class文件加载进内存,并将这些静态数据转换成方法区的运行时数据结构。
-
然后在堆中会生成一个个Class类的对象(Class类的对象也就是一个一个的类和接口等,而且每个类只会加载一个),作为方法区中类数据的访问入口
-
类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
五、通过反射的方式创建运行时类的对象
——以前我们创建类的对象都是通过new的方式,这里用反射的方式
——运行时类也就是之前我所说的类的概念,只不过被加载进内存后,就变成了运行时类,也是Class类的对象
六、newInstance()方法
public class HTreflection {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
// //如果不适用泛型,clazz.newInstance()返回的是一个Object类型,后面还需要强转
// Class clazz = persons.class;
// Object obj = clazz.newInstance();
// persons p = (persons)obj;
Class<persons> clazz = persons.class;//反射都要从Class开始
/*
Class的对象.newInstance():返回对应的运行时类的对象,其实内部调用了空参构造器persons(){}来创建对象
因此要想使用Class的对象.newInstance(),必须满足以下条件:
1.此persons类要有空参构造器,否则InstantiationException
2.此persons类的空参构造器的的权限要够用(通常设置public),否则非法访问异常IllegalAccessException
在javabean中要求提供一个public的空参构造器,原因:
1.便于通过反射,创建运行时类的对象
2.便于子类继承此运行时类,默认用用super()时,保证父类有此构造器
*/
persons p = clazz.newInstance();
System.out.println(p);
}
}
class persons{
private String name;
public int age;
@Override
public String toString() {
return "name:"+name+" age:"+age;
}
public persons(){}
public persons(String name,int age){
this.name = name;
this.age = age;
}
private persons(String name){
this.name = name;
}
private void show(){
System.out.println("我要进大厂!");
}
public String getName(){
return this.name;
}
}
七、通过举例体会反射的动态性
//体会反射的动态性
@Test
public void test2(){
for(int i = 0;i < 100;i++){
int num = new Random().nextInt(3);//产生一个随机数:0,1,2
String classPath = "";
switch(num){
case 0:
classPath = "java.util.Date";
break;
case 1:
classPath = "java.lang.Object";
break;
case 2:
classPath = "com.atguigu.java.Person";
break;
}
try {
Object obj = getInstance(classPath);
System.out.println(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*
创建一个指定类的对象。
classPath:指定类的全类名
*/
public Object getInstance(String classPath) throws Exception {
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
- 怎么从上面代码来理解通过反射可以让java语言具有动态性?
- 上面的程序通过随机数来决定造哪个对象,在程序运行前我们不知道程序会造哪个对象,在这里体现了动态性。
- 到哪还会有人说,这个动态性难道不是因为随机数才让java具有动态性吗?如果用new的方式造对象,也通过随机数和switch-case来决定到底造哪个对象,也可以体现动态性啊?