Java基础-反射

Java基础-反射

一、类的加载、连接、初始化

1、jvm

当java执行某个程序时,其会先启动一个java虚拟机进程,程序将处于该java虚拟机进程里。也就是说同一个JVM的所有线程、所有变量都会处于同一个进程里,它们都使用该jvm进程的内存区。当系统出现以下情况的时候,jvm就会被终止。

  • 程序正常运行结束
  • 程序使用System.exit()结束程序
  • 程序执行过程中遇到未捕获的异常
  • 平台强制结束了jvm进程

**JVM进程在结束的时候,所有内存状态将丢失!!!!**比如说我们第一次运行程序的时候改了一个类中static变量的值,之后我们第二次运行程序,该修改就废弃了!

2、类的加载

​ 当程序使用一个类时,如果这个类没被加载到内存中,其要经过加载、连接、初始化三个步骤对类进行初始 化。

​ 类加载是指将类文件读入内存,然后为其创建一个java.lang.class对象。这个过程是用类加载完成的,类加载器主要由jvm提供,开发者也可以继承classloader自己编写相应类加载器。

3、类的连接

类被加载之后,系统为之生成一个对应的Class对象,接着其会进入连接阶段,他会把类的二进制数据合并到jre中,其中主要又包含以下三个阶段。

  • 验证(检查其是否有正确内部结构)
  • 准备(为类变量分配内存)
  • 解析(将其中符号引用替换成直接引用)
4、类的初始化

​ 在类的初始化阶段,虚拟机负责对类进行初始化,主要是对变量进行初始化,为其指定初始值。在类中为其指定初始值主要有两种方式

  1. 声明类变量时指定初始值
  2. 使用静态初始化块为指定变量赋值

在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);
            }
        }
    }

}

最后输出:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值