Java基础-反射
一、类的加载、连接、初始化
1、jvm
当java执行某个程序时,其会先启动一个java虚拟机进程,程序将处于该java虚拟机进程里。也就是说同一个JVM的所有线程、所有变量都会处于同一个进程里,它们都使用该jvm进程的内存区。当系统出现以下情况的时候,jvm就会被终止。
- 程序正常运行结束
- 程序使用System.exit()结束程序
- 程序执行过程中遇到未捕获的异常
- 平台强制结束了jvm进程
**JVM进程在结束的时候,所有内存状态将丢失!!!!**比如说我们第一次运行程序的时候改了一个类中static变量的值,之后我们第二次运行程序,该修改就废弃了!
2、类的加载
当程序使用一个类时,如果这个类没被加载到内存中,其要经过加载、连接、初始化三个步骤对类进行初始 化。
类加载是指将类文件读入内存,然后为其创建一个java.lang.class对象。这个过程是用类加载完成的,类加载器主要由jvm提供,开发者也可以继承classloader自己编写相应类加载器。
3、类的连接
类被加载之后,系统为之生成一个对应的Class对象,接着其会进入连接阶段,他会把类的二进制数据合并到jre中,其中主要又包含以下三个阶段。
- 验证(检查其是否有正确内部结构)
- 准备(为类变量分配内存)
- 解析(将其中符号引用替换成直接引用)
4、类的初始化
在类的初始化阶段,虚拟机负责对类进行初始化,主要是对变量进行初始化,为其指定初始值。在类中为其指定初始值主要有两种方式
- 声明类变量时指定初始值
- 使用静态初始化块为指定变量赋值
在jvm赋值时,jvm会按这些语句在程序中的排列顺序依次执行他们!
其初始化的时机主要有以下情况
- 当创建此类实例时会对此类进行初始化
- 调用其静态方法
- 访问其变量
- 通过反射创建其对应Class对象时(使用forname方法)
- 初始化其子类
- 直接使用java.exe命令运行某个主类时。
二、类的加载器
在jvm中,一个载入的类有一个唯一的标识,其使用全限定类名及其加载器作为唯一标识。比如说pg包下有一个person类,被类加载器k1加载,则其标识为(pg,person,k1),所以说一个类如果被两个不同的加载器加载,则其加载完毕生成的class对象也是完全不同的。
当jvm启动时,会形成三个类加载器组成的初始类加载层次结构。
- BootStrap ClassLoader:根类加载器
- Extension ClassLoader:扩展类加载器
- System ClassLoader:系统类加载器
jvm的类加载机制主要由三种:
- 全盘负责:加载某一个类时,把其所依赖或是引用的其他类顺便也载入。
- 父类委托:先让父类试图加载该class,只有在父类加载器没法加载该类时才从自己的类路径中加载该类
- 缓存机制:先把所有加载过的class全都缓存一下,当程序中需要使用某个类时,现在缓存区找,如果没有再重新加载到缓存区。
URLClassLoader
java中为ClassLoader提供了一个实现类,该类也是系统类加载器和扩展加载器的父类。一旦得到了URLClassLoader的对象后,就可以调用对象的loadClass方法来加载指定类。
三、通过反射查看信息
1、获得class对象
获得class对象主要有以下三种方式:
- 通过Class类的forname方法,该方法需要传入字符串参数(某个类的全限定类名)
- 调用某个类的class对象
- 调用某个对象的getClass方法
下面我们来获取一个我们写好的Person类
public static void main(String[] args) throws ClassNotFoundException {
//第一种
System.out.println(Class.forName("fanshe.Person").toString());
//第二种
System.out.println(Person.class);
//第三种
Person p = new Person();
System.out.println(p.getClass());
}
输出三项都一样。
使用第二种方式相比于第一种方式代码更安全,程序的性能也更好一点。
2、查看class中的各种信息
首先我们把person类改一改
package fanshe;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(value = RetentionPolicy.RUNTIME)
@interface jj{
}
@SuppressWarnings(value = "unchecked")
@jj
public class Person {
private int age;
private String name;
private Person(){
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public void test(){
System.out.println("这是person类");
}
public void test(String str){
System.out.println("这是Person类,其参数为"+str);
}
public void test(String str,int a ){
System.out.println("这是Person类,其参数为"+str+a);
}
}
用反射拿点东西
package fanshe;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
public class GetClassInformation {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class<?> aClass = Class.forName("fanshe.Person");
//拿全部构造器
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
//拿所有公有方法
Method[] method = aClass.getMethods();
for (Method meth : method) {
System.out.println(meth);
}
//拿其中一个指定方法,参数为String类型和int类型的test方法
aClass.getMethod("test",String.class,int.class);
//拿所有注解
Annotation[] annotations = aClass.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//拿suppresswarnings注解
System.out.println(Arrays.toString(aClass.getAnnotationsByType(SuppressWarnings.class)));
//拿包,拿父类
System.out.println(aClass.getPackage());
System.out.println(aClass.getSuperclass());
}
}
这里需要注意的是,当获取suppresswarnings注解时,由于其是源代码注解(Retention = “SOURCE”),所以我们压根访问不到,在输出时就会显示如下信息。
空数组而已。
4、使用反射生成对象
1、创建对象
通过反射创建相关对象需要首先获取对应的constructor对象,然后调用constructor中的newinstance方法即可。
下面是一个例子
public class ModifyOB {
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
System.out.println(create("fanshe.Person"));
}
public static Object create(String classname) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> aClass = Class.forName(classname);
//拿到其构造器创造新的实例
Object o = aClass.getConstructor().newInstance();
return o;
}
}
如果需要创建有参的对象,costructor内部写上参数的类名,然后newInstance里写上相应参数的值。
public static Object create(String classname) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> aClass = Class.forName(classname);
Object o = aClass.getConstructor(int.class,String.class).newInstance(10,"zjj");
return o;
}
2、调用方法
当获取到某类的Class对象后,就可以通过对象的getMethods方法或者getMethod方法获取全部方法或指定方法,然后通过方法中的invoke方法调用。
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
Person p = (Person)create("fanshe.Person");
Method test = Person.class.getMethod("test");
//第一个参数是执行方法的实例,这里使用p作为实例,参数搁在后面。
test.invoke(p);
}
3、访问成员变量值
通过getsFields或getField方法可以获取该类所包括的成员变量或指定成员变量。
拿到变量后可以使用getXxx(xxx为类型)或get方法获取参数值(引用类型)
通过setXxx或set方法可以设置val值。
下面我们改写下上面的例子康康
我们先在person类中定义一个public的ts的整形属性,如果是要设置私有类型的值可以通过setAccessible(true)来取消权限检查。
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, NoSuchFieldException {
Person p = (Person)create("fanshe.Person");
Method test = Person.class.getMethod("test");
//第一个参数是执行方法的实例,这里使用p作为实例
test.invoke(p);
Field age = Person.class.getField("ts");
//拿一个值
System.out.println(age.getInt(p));
//把p设置成15
age.setInt(p,15);
System.out.println(age.getInt(p));
}
最后输出的ok了!
4、操作数组
反射也可以操作数组!直接上例子!
public class ModifyArray {
public static void main(String[] args) {
//创建一个长度15的整形数组
Object arr = Array.newInstance(int.class, 15);
//将arr的第0个元素设置为15
Array.set(arr,0,15);
int anInt = Array.getInt(arr, 0);
System.out.println(anInt);
}
}
最后输出15!
5、反射和泛型
当我们使用反射获取到一个类的实例时首先拿到的是Object类对象,然后要进行强制转换对其进行类型转换,但是有时候这种方法如果操作不当会抛出异常,如何避免这种方法呢?
使用class泛型可以避免强制类型转换
public class fx {
public static <T> T getInstance(Class<T> cls) throws IllegalAccessException, InstantiationException {
return cls.newInstance();
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
//以下代码不用强转
Date d = fx.getInstance(Date.class);
}
}
我们也可以通过反射获取相关泛型的类型,这里我们编写一个例子看看怎么获得?
package fanshe;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
//拿泛型的种类
public class getFXtype {
private Map<String,Integer> jj;
public static void main(String[] args) throws NoSuchFieldException {
Class<getFXtype> FXtypeClass = getFXtype.class;
Field jj = FXtypeClass.getDeclaredField("jj");
//只能看见map
System.out.println(jj.getType());
//拿该变量的泛型类型
Type genericType = jj.getGenericType();
//看看是不是参被泛型限制的类型?
if(genericType instanceof ParameterizedType){
ParameterizedType pt = (ParameterizedType)genericType;
//获取原始类型
System.out.println(pt.getRawType());
//获取里面的类型,输出String和Integer
Type[] actualTypeArguments = pt.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
}
最后输出: