文章目录

  • Java的反射机制
  • 反射的优缺点
  • Class对象
  • 创建运行时类的对象
  • 创建类的对象
  • 使用类的方法
  • 操作类的属性
  • 反射操作泛型


Java的反射机制

众所周知,Java是一门静态语言,及运行时结构不可变的语言就是静态语言。但反射机制的引入使得Java可以变为准动态语言,我们可以利用反射机制获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!
Reflection(反射)是Java被视为动态语言的关键,就像是找镜子,从对象得到类的信息.反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
那么反射到底能提供什么样的功能呢?
Java反射机制提供的功能:

  1. 在运行时判断任意一个对象所属的类
  2. 在运行时构造任意一个类的对象
  3. 在运行时判断任意一个类所具有的成员变量和方法
  4. 在运行时获取泛型信息
  5. 在运行时调用任意一个对象的成员变量和方法
  6. 在运行时处理注解
  7. 生成动态代理

反射的优缺点

优点: 可以实现动态创建对象和编译,体现出很大的灵活性。

缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作。

Class对象

那么反射到底是如何形成的呢,首先我们看一下所有类的顶尖继承类—Object类,其内部有一个public final Class getClass()方法,这个方法在最顶尖的类中,因此所有的类都会包含这个方法,它的返回值是一个Class对象,我们可以通过对象反射求出类的名称。

对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
Class类到底有什么作用呢?

  1. Class 本身也是一个类
  2. Class 对象只能由系统建立对象
  3. 一个加载的类在 JVM 中只会有一个Class实例
  4. 一个Class对象对应的是一个加载到JVM中的一个.class文件
  5. 每个类的实例都会记得自己是由哪个 Class 实例所生成
  6. 通过Class可以完整地得到一个类中的所有被加载的结构
  7. Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象

方法名

功能说明

static ClassforName(String name)

返回指定类名name的Class对象

Object newInstance()

调用缺省构造函数,返回Class对象的一个实例

getName()

返回此Class对象所表示的实体(类,接口,数组类或void)的名称。

Class getSuperClass()

返回当前Class对象的父类的Class对象

Class[] getinterfaces()

获取当前Class对象的接口

ClassLoader getClassLoader()

返回该类的类加载器

Constructor[] getConstructors()

返回一个包含某些Constructor对象的数组

Method getMothed(String name,Class… T)

返回一个Method对象,此对象的形参类型为paramType

Field[] getDeclaredFields()

返回Field对象的一个数组

反射的基础:Class对象链接:JAVA反射的基础–Class对象、类加载器.

创建运行时类的对象

在我们获得了Class类的实例后,有了Class对象,通过反射获取运行时类的完整结构
Field(对象)、Method(方法)、Constructor(构造器)、Superclass(父类Class)、Interface(接口)、Annotation(注解)…

创建类的对象

  • 创建类的对象:调用Class对象的newInstance()方法
    1. 类必须有一个无参数的构造器。
    2. 类的构造器的访问权限需要足够

问题: 可以看到,上面的要求是必须是一个无参数构造器,难道没有无参的构造器就不能创建对象了吗?
答: 只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。也就是使用class对象来调用相应的构造器来创建对象。
步骤如下:

  1. 通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器
  2. 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
  3. 通过Constructor实例化对象

使用类的方法

  1. 通过Class类的getMethod(String name,Class…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
  2. 之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传
    递要设置的obj对象的参数信息。

注意:

  • Object 对应原方法的返回值,若原方法无返回值,此时返回null
  • 若原方法若为静态方法,此时形参Object obj可为null
  • 若原方法形参列表为空,则Object[] args为null
  • 若原方法声明为private , 则需要在调用此invoke()方法前, 显式调用方法对象的setAccessible(true)方法,将可访问private的方法。

setAccessible: 作用是启动和禁用访问安全检查的开关。Method和Field、Constructor对象都有setAccessible()方法。

  1. 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
  1. 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
  2. 使得原本无法访问的私有成员也可以访问。
  1. 参数值为false则指示反射的对象应该实施Java语言访问检查

操作类的属性

依据上面的内容,我们可以获得类的class对象,通过反射当然也可以操作类的属性,对于类的属性,我们可以根据对应的方法得到Field对象,再通过set()方法进行赋值以达到操作类属性的目的,对于某些私有的属性,也可以用到上面提到的setAccessible(true)方法关闭Java语言访问检查来进行访问。

代码实例:

public static void main(String[] args){
 		Class<?> c1 = Class.forName("User");//forName("")中为实体类所在的位置,及URL,例如在com.PZY.reflection包下有一个User实体类,那么forName("com.PZY.reflection.User")
 		//forName()会抛出ClassNotFoundException异常
        //1.通过动态调用构造方法,构造对象
        System.out.println("*****************************************************");
        User user = (User) c1.newInstance();// newInstance()会抛出IllegalAccessException, InstantiationException异常
        //因为调用newInstance()会返回一个Object类对象,因此这里使用(User)对Object对象进行了强转
        System.out.println(user);

        //2.通过有参构造创建对象
        System.out.println("*****************************************************");
        Constructor<User> constructor = (Constructor<User>) c1.getDeclaredConstructor(String.class, int.class, int.class);//getDeclaredConstructor()会抛出NoSuchMethodException异常
        User user2 = constructor.newInstance("钢铁侠", 54, 40);//newInstance(参数)会抛出InvocationTargetException异常
        System.out.println(user2);
        System.out.println("*****************************************************");
        //3.调用普通方法
        User user3=(User)c1.newInstance();
        //获得指定的方法
        Method method=c1.getDeclaredMethod("setName",String.class);
        //调用invoke(方法执行的对象,方法参数的值)
        method.invoke(user3,"钢铁侠");
        System.out.println(user3.getName());
        
		System.out.println("*****************************************************");
        //4.操作属性(针对于private对象进行操作赋值)
        User user4 = (User) c1.newInstance();
        Field field = c1.getDeclaredField("name");
        field.setAccessible(true);  // 关闭权限访问检测 , 默认是false[打开的权限访问检测
        //字段设置值 (对象 , 值)
        field.set(user4,"钢铁侠");
        System.out.println(user4.getName());
}
//User实体类
class User extends Object{

    private String name;
    private int id;
    private int age;

    public User() {
    }

    public User(String name, int id, int age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    private void test(){

    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", age=" + age +
                '}';
    }
}

反射操作泛型

(需要自己下去再进行了解巩固)
Java采用泛型擦除的机制来引入泛型 , Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题 , 但是 , 一旦编译完成 , 所有和泛型有关的类型全部擦除。
为了通过反射操作这些类型 , Java新增了 ParameterizedType , GenericArrayType ,
TypeVariableWildcardType 几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。

类型名

解释

ParameterizedType

表示一种参数化类型,比如Collection

GenericArrayType

表示一种元素类型是参数化类型或者类型变量的数组类型

TypeVariable

是各种类型变量的公共父接口

WildcardType

代表一种通配符类型表达式

public static void main(String[] args) throws Exception {
        //获得指定方法的泛型信息
        Method method = Test08.class.getDeclaredMethod("test01", Map.class, List.class);

        //获得泛型参数类型信息
        Type[] t = method.getGenericParameterTypes();

        for (Type type : t) {
            System.out.println("#"+type);
            if (type instanceof ParameterizedType){
                //getActualTypeArguments 获得真实的类型参数
                Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println("真实的泛型类型:"+actualTypeArgument);
                }
            }
        }

        //获得返回值泛型信息

        Method method2 = Test08.class.getMethod("test2", null);

        //获得泛型参数类型信息
        //getGenericReturnType获得泛型返回值信息
        Type genericReturnType = method2.getGenericReturnType();
        if (genericReturnType instanceof ParameterizedType){
            //getActualTypeArguments 获得真实的类型参数
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println("真实的返回值泛型类型:"+actualTypeArgument);
            }
        }
}

//测试反射获取泛型
@SuppressWarnings("all")//取消警告
public class Test08 {
    //带有泛型参数的方法
    public void test01(Map<String,User> map,List<User> list){
        System.out.println("test01");
    }
    //带有泛型返回值的方法
    public Map<Integer,User> test2(){
        System.out.println("test02");
        return null;
    }