反射入门基础

一、反射基础

反射指的是可以于运行时加载、探知、使用编译期间完全未知的类 。 程序在运行状态中,可以动态加载一个只有名称的类,对于 任意一 个已加载的类, 都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性

1.1 获取Class的多种方式

Class类是Reflection(反射)的根源。针对任何您想动态加载、运行的类,唯有先获得相应的Class 对象

public final class Class<T>
extends Object
implements Serializable, GenericDeclaration, Type, AnnotatedElement

Class 类的实例表示正在运行的 Java 应用程序中的类和接口枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象

类型描述
接口.class 返回接口案例: interface java.util.Collection; 关键字 interface
注解.class 返回接口案例:Override.class; 关键字 interface
类.class 返回类案例:class java.util.Collections; 关键字 class
枚举.class 返回类案例:Enum.class; 关键字class

Class没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。

方式描述
static Class< ? >forName(String className)使用Class的一些静态方法,还有其他的重载方法
类.class如果是类,则直接.class即可。这种方式最简单,也不会出现ClassNotFoundException等异常
对象.getClass()如果是对象,则使用这种方式

注: 使用反射,应该保证该类有一个默认的构造方法,否则会报错


1.2 API

API在线


1.2.1 构造器、属性、方法的基本API

下面多个方法标记所有; 如果去掉Declared,则获取的只能是public修饰的,例如getMethods(),就只能获取公共方法

方法描述
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法
Constructor<?>[] getDeclaredConstructors()返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法
Field getDeclaredField(String name)返回一个 Field对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段
Field[] getDeclaredFields()返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段
Method getDeclaredMethod(String name, Class<?>... parameterTypes)返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法
Method[] getDeclaredMethods()返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法

构造器和方法都一个可变参数,这是为区别重载,所以在获取对应的构造器和方法的时候,必须要传入对应形参的Class; 而字段就不用。如果没有形参,则使用关键字null代替;或者不写也可以。

Constructor<Student> c = clazz.getDeclaredConstructor(int.class,int.class,String.class);
Student s1 =  c.newInstance(17,22,"一号人物")(传入参数)
Method method = clazz.getDeclaredMethod("setUname", String.class);//(方法名,参数.class)
method.invoke(s2, "二号人物");//s2表示调用方法的对象,反射使用方法是需要和具体的对象绑定
Student s3 = clazz.newInstance();  // 获得对象
Field f = clazz.getDeclaredField("uname");  // 属性,不能直接使用javabean中的private的属性的
// 需要通过添加下面方法(setAccessible(true))才可以使用
f.setAccessible(true);  //  这个属性不需要做安全检查,可以直接访问
f.set(s3, "三号人物");  //通过反射直接写属性

补充java.lang.reflect.Member接口

选项描述
java.lang.reflect.Member 接口所有已知实现类:Constructor, Field, Method
static int DECLARED 静态属性标识类或接口的所有已声明成员的集合。
static int PUBLIC 静态属性标识类或接口的所有公共成员(包括继承成员)的集合

1.2.2 细节理解

反射获取的是类相关信息,如果要具体到某一个对象上,都需要指定具体的对象

案例分析

class ReflectPoint {
    private int x;
    public int y;

    public ReflectPoint(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public ReflectPoint() {
    }
}

public class ReflectField {


    public static void main(String[] args) throws  Exception {
        ReflectPoint pt = new ReflectPoint(3, 5);
        Field fieldY = pt.getClass().getField("y");
        //请问 fieldY 的值时多少?
        System.out.println(fieldY);//public int reflectStudy.ReflectPoint.y
        System.out.println(fieldY.get(pt));

        Field fieldx = pt.getClass().getDeclaredField("x");
        fieldx.setAccessible(true);
        System.out.println(fieldx.get(pt));
    }
}

解释原因

  • pt.getClass().getField("Y")上的。它不知道是调用的那一个具体的对象。而通过fieldY.get(pt) 来确认到底是调用的哪一个。

  • 如果是私有属性,通过调用 getDeclaredField("X")来获取私有方法,通过设置访问权限fieldX.setAccessible(true)来设置访问权限。


1.2.3 通过反射修改对象的属性值。

 private static void changeStringValue(Object obj) throws Exception {
        Field[] fields = obj.getClass().getFields();
        for(Field field : fields){
            //if(field.getType().equals(String.class)){
            if(field.getType() == String.class){ // // 同一份字节码; 表单更加准确
                String oldValue = (String)field.get(obj);
                String newValue = oldValue.replace('b', 'a');
                field.set(obj, newValue);
            }
        }

    }

注意:只有Field可以有set方法,MethodConstructor 只有获取的方法。

1.2.4 成员方法的反射

方法与对象是没有关系的。可以在对象1上调用,也可以在对象2上调用,所以这是属于类的

  • 获取:第一个参数是方法名,第二个是参数列表,
  • 调用,第一个是哪个对象去调用这个方法,第二参数是传入哪些实参。
  • 如果Method的第一个参数是null,表明这个方法是静态方法,不需要对象。

(一)源码分析

/**
 *  反射方法
 */
public class ReflectMethod {

    public static void testMethd(String[] array) {
      System.out.print("invoke me!");
    }

    public static  void main(String[] args) throws Exception {

        Method method = ReflectMethod.class.getMethod("testMethd",String[].class);
        //静态方法,对象传null;
        method.invoke(null,new String[]{"111","222","333"});

    }
}

(二)打印结果

Exception in thread "main" java.lang.IllegalArgumentException: wrong number of arguments
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at reflectStudy.ReflectMethod.main(ReflectMethod.java:20)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

参数个数不匹配

(三) 原因

在1.4,没有可变参数之前,用数组来接收参数列表。为了向下兼容。会安装JDK1.4 语法进行处理,即,将字符串数组才分成单独的参数;所以会wrong number of arguments;

(四) 针对这种问题的处理方案

public class ReflectMethod {

    public static void testMethd(String[] array) {
      System.out.println("invoke me!");
    }

    public static  void main(String[] args) throws Exception {

        Method method = ReflectMethod.class.getMethod("testMethd",String[].class);
      //静态方法,对象传null;
      //兼容1.4版本,把这个数组拆包,变成三个参数,所以报参数错误
      //method.invoke(null,new String[]{"111","222","333"});

     //解决方案一:  把数组封装到Object数组中,拆包后是一个字符串型的数组。
        method.invoke(null, new Object[]{new String[]{"11","22"}});
     //解决方案二:  把字符串数组标示成一个Object对象,编译器就不会拆包了。
        method.invoke(null, (Object)new String[]{"11","22"});

    }
}

1.2.5 数组相关的反射

如果数组的类型纬度相同,则得到字节码是同一份

  int[] a1 = new int[3];
  int[] a2 = new int[4];
  int[][] a3 = new int[3][];
  int[][] a4 = new int[4][];
  int[][] a5 = new int[3][2];
  int[][] a6 = new int[3][5];
  //int[][] a7 = new int[][4];//编译出错
  //String[] s0 = new String[];编译出错
  String[] s1 = new String[]{};
  String[] s2 = new String[]{"a","b"};
  //String[] s2 = new String[2]{"a","b"};编译出错
  System.out.println(a1.getClass() == a2.getClass());//true
  //System.out.println(a1.getClass() == a3.getClass());//编译出错
  System.out.println(a3.getClass() == a4.getClass());//true
  System.out.println(a3.getClass() == a5.getClass());//true
  System.out.println(s1.getClass() == s2.getClass());//true

Arrays.asList

int[] a1 = new int[] {1,2,3};
String[] s1 = new String[]{"a","b","c"};
System.out.println(Arrays.asList(a1));//打印: [[I@1b6d3586]
System.out.println(Arrays.asList(s1));//打印: [a, b, c]

这里写图片描述

兼容1.4的API; int [] a1 = new int[]{1,2,3}; 在1.5以后,asList接收的是可变参数。另外int是基本类型。


1.3 安全检查

启用和禁用访问安全检查的开关,值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查

这里写图片描述

注意其实现了多个子类:Constructor、Field、Method


1.4 反射操作泛型

Java采用泛型擦除的机制来引入泛型。Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的麻烦。但是,一旦编译完成,所有的和泛型有关的类型全部擦除。但是会保留元数据。

1.4.1 API

Java就新增了ParameterizedTypeGenericArrayTypeTypeVariableWildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。

接口描述
ParameterizedType:表示一种参数化的类型,比如Collection<String>
GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
TypeVariable:是各种类型变量的公共父接口
WildcardType:代表一种通配符类型表达式,比如?, ? extends Number,? super Integer; wildcard(通配符的意思)

这里写图片描述


1.4.2 应用

测试一下接口,只用ParameterizedType做案例,其他可以自行实验

package reflectStudy;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

class User {

}
public class GenericDemo {

    public void test01(Map<String, User> map, List<User> list) {

     System.out.println("new GenericDemo().test01()");

    }
    public Map<Integer, User> test02() {
        System.out.println("new GenericDemo().test02()");
        return null;
    }
    public static void main(String[] args) throws Exception {

        //获取指定方法的类型变量
        Method method1 = GenericDemo.class.getMethod("test01", Map.class, List.class);
        Type[] types = method1.getParameterTypes();
        for(Type type:types) {
            System.out.println(type.getTypeName());// 打印 java.util.Map <br> java.util.List
            System.out.println(type);//interface java.util.Map;interface java.util.List
        }
        System.out.println("===========================");


        //获取指定方法的返回值类型变量
        Method method2 = GenericDemo.class.getMethod("test02");
        Type type2 = method2.getGenericReturnType();
        System.out.println(type2.getTypeName());//java.util.Map<java.lang.Integer, reflectStudy.User>
        System.out.println(type2);//java.util.Map<java.lang.Integer, reflectStudy.User>
        System.out.println("===========================");


        // 获取泛型信息
        Type[] types3 = method1.getGenericParameterTypes();
        for(Type type:types3) {
            System.out.println(type);
            // Type[]   getActualTypeArguments() 返回表示此类型实际类型参数的 Type 对象的数组。
            if(type instanceof  ParameterizedType) {
                Type[] genericTypes = ((ParameterizedType) type).getActualTypeArguments();
                    for (Type genericType : genericTypes) {
                          System.out.println("参数-泛型类型:"+genericType.getTypeName());
                        //  System.out.println("参数-泛型类型:"+genericType);//会判断是 class or interface
                    }
            }
        }
        System.out.println("===========================");
        //返回值的泛型信息

        Type type4 = method2.getGenericReturnType();
        System.out.println(type4);
        if(type4 instanceof  ParameterizedType) {
            Type[] genericTypes = ((ParameterizedType) type4).getActualTypeArguments();
            for (Type genericType : genericTypes) {
                System.out.println("参数-泛型类型:"+genericType.getTypeName());
                //  System.out.println("参数-泛型类型:"+genericType);//会判断是 class or interface
            }
        }
    }

}

1.5 反射操作注解

1.5.1 API

方法描述
<T extends Annotation>T getAnnotation(Class<T> annotationClass)如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null
Annotation[] getAnnotations()返回此元素上存在的所有注释。
Annotation[] getDeclaredAnnotations()返回直接存在于此元素上的所有注释。
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)如果指定类型的注释存在于此元素上,则返回 true,否则返回 false。

1.5.2 应用

可以参考注解那一篇博客


1.6 数组相关反射

数组相关API

java.lang.reflect.Array这个类有多个静态方法。可以获取数组中的元素等。Array 类提供了动态创建和访问 Java 数组的方法;Array 允许在执行 get 或 set 操作期间进行扩展转换,但如果发生收缩转换,则抛出IllegalArgumentException。

1.7小节

注意: 如果使用这个方法来获取一个实例对象,就需要在这个被反射的类中提供一个无参构造方法,否则会报错。


反射降低运行效率, 也因此使用缓存;反射在框架中大量被使用


参看

Java反射在JVM的实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值