1 反射的出现背景
Java程序中,所有的对象都有两种类型:编译时类型和运行时类型,而很多时候对象的编译时类型和运行时类型不一致。 Object obj = new String("hello"); obj.getClass()
例如:某些变量或形参的声明类型是Object类型,但是程序却需要调用该对象运行时类型的方法,该方法不是Object中的方法,那么如何解决呢?
解决这个问题,有两种方案:
方案1:在编译和运行时都完全知道类型的具体信息,在这种情况下,我们可以直接先使用instanceof运算符进行判断,再利用强制类型转换符将其转换成运行时类型的变量即可。
方案2:编译时根本无法预知该对象和类的真实信息,程序只能依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。
2、Class的理解
一个 Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
(1)概念性的理解
- Class本身也是一个类
- Class 对象只能由系统建立对象
- 一个加载的类在 JVM 中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过Class可以完整地得到一个类中的所有被加载的结构(包括已经私有化的结构,可以通过Field类get获取set修改)
- Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象
针对于编写好的.java源文件进行编译,会生成一个或多个.class字节码文件。接着,我们使用java.exe命令对指定的.class解释运行,这个解释运行的过程中,需要将.class字节码文件加载到内存中。加载到内存中的.class文件对应的结构即为class 的一个实例
加载到内存中的Person类,作为Class 的一个实例。
Class clazz = Person.class
(2)获取Class实例的四种方法:
@Test
public void test1() throws ClassNotFoundException {
//获取Class实例的方式1:获取指定的类的class文件
Class clazz = User.class;
System.out.println(clazz);
//2、调用类的对象的getClass实例
User user = new User();
Class clazz1 = user.getClass();
System.out.println(clazz==clazz1);//true
//类会在内存中缓存起来,整个执行期间,只加载一次
//3、调用Class的静态方法forName
String name = "User";
Class<?> clazz2 = Class.forName(name);
System.out.println(clazz2);
//4、通过类的加载器得到Class的实例
Class clazz3 = ClassLoader.getSystemClassLoader().loadClass("User");
System.out.println(clazz3==clazz2);//true
}
运行时类在内存中会缓存起来,整个执行期间,只加载一次。
(3)关于class指向数组时
数组的维度与数据类型相同时,即认为是同一个Class,与数组的长度无关。
举例:
@Test
public void test2(){
int a[] = new int[10];
int b[] = new int[100];
Class clazz1 = a.getClass();
Class clazz2 = b.getClass();
System.out.println(clazz2==clazz1);//true
}
3、类的加载
(1)装载
将类的class文件读入内存,并位置闯将java.lang.Class独享,此过程由类加载器完成。
(2)链接
1、验证(verify):确保加载的类信息符合JVM规范,例如,以cafebabe开头,没有安全方面的问题。
2、准备(prepare):正式为类变量(static)分配内存并设置类变量默认初始值的阶段。这些内存都将在方法区中进行分配。
3、解析(revolve):虚拟机常绿内的符号引用
(3)初始化
执行类构造器<cliinit>()方法的过程
类构造器<clinit>()方法是有编译器自动收集类中多有的类变量的赋值。
关于类的加载器:
1、作用:负责类的加载,并对应于一个Class实例
2、分类:
(1)BoottrapClassLoader:启动类加载器、引导类加载器
>使用C/C++语言编写,不能通过Java代码获取实例。(无法获得,返回null)
>负责加载Java的核心库(JAVA_HOME/jre/lib/rt.jar或者sun.boot.class.path路径下的内容)
>加载扩展类和应用程序类加载器,并指定为他们的父类加载器。
(2)Extension ClasssLoader 扩展类加载器
负责加载java.ext.dirs系统属性所制定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库
(3)SystemClassLoaner/ApplicationClassLooader:系统类加载器、程序应用类加载器
>用户自定义的类、默认使用的类的加载器
- 应用程序中的类加载器默认是系统类加载器。
- 通过ClassLoader的getSystemClassLoader()方法可以获取到该类加载器
(4)用户自定义类的加载器
因为同一个加载器只能加载一次,想要对同一个类加载多次,需要用户自己定义类加载器。
意义;实现应用的隔离(同一个类在一个应用程序中可以加载多份);实现数据的加密
@Test
public void test1() {
//get 系统类加载器
ClassLoader classLoader1 = ClassLoader.getSystemClassLoader();
//jdk.internal.loader.ClassLoaders$AppClassLoader@63947c6b
System.out.println(classLoader1);
//getParent() 获取扩展类加载器
ClassLoader classLoader2 = classLoader1.getParent();
//jdk.internal.loader.ClassLoaders$PlatformClassLoader@1888ff2c
System.out.println(classLoader2);
//获取引导类加载器
ClassLoader classLoader3 = classLoader2.getParent();
System.out.println(classLoader3);//null
}
@Test
public void test2() throws ClassNotFoundException {
//用户自定义的类是系统类加载器得到的
Class clazz = User.class;
ClassLoader classLoader = clazz.getClassLoader();
System.out.println(classLoader);//jdk.internal.loader.ClassLoaders$AppClassLoader@63947c6b
//对于java核心api的使用,都使用引导类加载器
Class clazz2 = Class.forName("java.lang.String");
ClassLoader classLoader1 = clazz2.getClassLoader();
System.out.println(classLoader1);//null
}
@Test
public void test3()throws IOException {
Properties pros = new Properties();
//读取文件的默认路径是当前的module
FileInputStream fis = new FileInputStream(new File("D:\\idea\\javaCode\\Reflection\\Person"));
pros.load(fis);
String name = pros.getProperty("name");
String age = pros.getProperty("age");
System.out.println(name+":"+age);
}
@Test
public void test4()throws IOException{
Properties pros = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("PersonInfo");
pros.load(is);
String name = pros.getProperty("name");
String heitght = pros.getProperty("height");
System.out.println(name+":"+heitght); }
用户自定义类的加载器:
因为同一个加载器只能加载一次,想要对同一个类加载多次,需要用户自己定义类加载器。
意义;实现应用的隔离(同一个类在一个应用程序中可以加载多份);实现数据的加密