Fork me on GitHub

反射

注意:所有文章除特别说明外,转载请注明出处.

反射

反射表示将类的属性和方法映射成相应的类。Java反射机制表示在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。对于任意一个对象都能够调用它的任意一个方法,这种动态获取的信息以及动态调用对象的方法功能是反射。

在java.lang.reflect包下有三个类:Field类 | Method类 | Constructor类,三者分别描述了一个类的域 | 方法 | 构造函数。三个类都有一个getName()方法。Field类有一个getType()方法,返回一个Class类型对象。三个类都有一个getModifiers()方法,返回一个整数,(0|1)描述方法所使用的修饰符。

Java反射框架提供的功能

1.在运行时判断任意一个对象所属的类
2.在运行时构造任意一个类的对象
3.在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法)
4.在运行时调用任意一个对象的方法
5.注意上面的所有情况都是在运行时,而不是在编译时

Class类和Object类

1.Class是一个类,一个描述类的类,封装描述方法的Method,描述字段的Filed,描述构造器的Constructor等属性。
2.对象经过反射后可以得到的属性:某类的数据成员变量名、方法、构造器,某个类实现的接口。
3.一个Class对象包含了特定某个类的有关信息。
4.Class对象只能由系统建立对象
5.一个类在JVM中只有一个Class实例。

1.反射的基本使用

1.获取Class类的三种方法:

  • 类名.class
  • 对象名.getClass()
  • Class.forName(“加载的类名”)

2.判断是否是某个类的实例:

通过 instanceof 关键字判断是否是某个类的实例

2.实现流程:

1.使用上面的Class类获取方法获取特定的Class类,即该类对应的字节码。
2.调用Class对象的getConstructor(Class<?>... parameterTypes)获取构造方法对象
3.调用是构造方法类Constructor的newInstance(Object... initargs)方法新建对象
4.调用Class对象的getMethod(String name, Class<?>... parameterTypes)获取方法对象
5.调用方法对象类Method的invoke(Object obj, Object... args)方法,调用对象上相应方法

注意:使用方法的参数唯一标识一个方法。依据:方法的重载。

2.枚举类成员

//案例:打印一个类的所有方法

...
Class<?> c1 = Class.forName(className);
while(c1 != null){
    for(Method m : c1.getDeclaredMethods()){
        System.out.println(
            Modifier.toString(m.getModifiers())+""+ 
            m.getReturnType().getCanonicalName()+""+
            m.getName()+
            Arrays.toString(m.getParameters())
        );

    }
    c1 = c1.getSuperclass();
}

3.对象的检查

注意:在使用私有的Field和Method对象之前,我们必须让它们是可以访问的。调用 setAccessible(true) 方法可以为反射 解锁 方法或域访问限制。但模块系统或者安全管理器会阻止请求,并以这种方式保护对象不被访问。所以在这种情况下可以调用 trySetAccessible() 方法,如果域或者方法是不可以被访问的,则该方法返回false。

4.方法的调用

注意:即使 clone() 方法是所有数组类型的公有方法,当有描述数组类的Class对象上调用getMethod()方法时,返回的方法数字也不包含 clone() 方法。

5.对象的构造

如果我们需要构造对象,需要先找到 Constructor 对象,然后调用它的 newInstance() 方法。

Constructor const = c1.getConstructor(int.class);
Object obj = const.newInstance(42);

2.数组的反射

public class ReflectTest {
    public static void main(String[] args) {
        int [] a1 = new int[]{1,2,3};
        int [] a2 = new int[5];
        int [][] a3 = new int[2][3];
        System.out.println(a1.getClass() == a2.getClass());//true
        System.out.println(a1.getClass());//class [I
        System.out.println(a3.getClass());//class [[I
        System.out.println(a1.getClass().getSuperclass() == a3.getClass().getSuperclass());//true
        System.out.println(a2.getClass().getSuperclass());//class java.lang.Object

        //下句编译不通过:Error:(15, 42) java: 不可比较的类型: java.lang.Class<capture#1, 共 ? extends int[]>和java.lang.Class<capture#2, 共 ? extends int[][]>
        //System.out.println(a1.getClass() == a3.getClass());

        Object []b3 = a3;//通过
        //下句编译不通过   Error:(17, 24) java: 不兼容的类型: int[]无法转换为java.lang.Object[]
        //Object [] b1 = a1;

        String s1 = "abc";
        System.out.println(Arrays.asList(a1));//[[I@1540e19d]
        System.out.println(Arrays.asList(s1));//[abc]
    }
}

output:
    true
    class [I
    class [[I
    true
    class java.lang.Object
    [[I@1540e19d]
    [abc]

注意:上面的例子说明,1.对于元素同类型的数组,同维数组,它们的class一样。2.当数组不同维时,它们的class不一样。3.当不同维时,父类都是Object,所以得到的结果都一样。4.基本类型一维数组不能直接转换为Object[]。

注意:HashCode与内存泄漏问题:1.hashcode一旦确定就不要变,否则容易出错。2.如果对象equals之后返回true,则它们的hashcode值相同。3.但是如果equals方法返回false,不一定表示两者的hashcode值不同。

3.配置文件的加载

  • 类加载器加载只读配置文件
    类名.class.getClassLoader().getResourceAsStream(str);

  • 类名.class.getResourceAsStream(str);是指还是调用类加载器。

//源码
public InputStream getResourceAsStream(String name) {
    name = resolveName(name);
    ClassLoader cl = getClassLoader0();
    if (cl==null) {
        // A system class.
        return ClassLoader.getSystemResourceAsStream(name);
    }
    return cl.getResourceAsStream(name);
}

注意:参数str的写法:1.不加斜杠,相对路径:str=”config.properties”; 2.加斜杠,从classpath的根路径找:str=”cn/edu/pku/config.properties”;

4.内省(Instropector)和JavaBean

我们使用JavaBean获取属性值的流程大致是:变大写->补前缀->获取方法。

  • 1.使用内省操作

简单实现: 使用java.beans.PropertyDescriptor类

麻烦实现: 使用java.beans.Introspector类,遍历getBeanInfo方法的返回值

JavaBean必须有一个不带参数的构造函数

  • 使用BeanUtils工具包
1.字符串和整数转换(对比(PropertyUtils)
2.属性级联操作
3.操作map

Method类 java.lang.reflect.Method

Method类位于java.lang.reflect.Method包下,在Java反射中Method类描述的是类的方法信息。在Method类的实例描述了方法的全部信息(如:方法修饰符、方法名称、参数列表等)。

1.获取Method类对象

1.getMethods() 获取类的public方法

2.getMethod(String name, Class[] params) 获取类的特定方法,name参数指定方法的名称,params参数指定方法的参数类型

3.getDeclaredMethods() 获取类中所有的方法(public protected default private)

4.getDeclaredMethod(String name, Class[] params) 获得类的特定方法,name参数指定方法的名字,params参数指定方法的参数类型

2.Method类常用方法

public class MethodTest{
    public static void main(String[] args){
        //1.获取操作类的所对应的Class对象
        Class<?> class = Class.forName("com.xidian.edu.cn.entity.User");
        //2.使用该类的class对象生成实例
        Object obj = class.newInstance();

        //3.使用方法
        Method addMethod = class.getMethod("addResult", new Class[]{int.class});
        ...

    }
}

注意:Method类的invoke(Object object, Object args[]) 方法作用是:调用该对象描述的方法,传递给定的参数,返回被调用方法的返回值。如果是静态方法,则将null传递给object。接收的参数必须是对象,如果参数为基本类型数据,必须转换为包装类型的对象。invoke()方法的返回值总是对象。如果实际被调用的方法的返回类型是基本类型数据,那么invoke()方法会把它转换为相应的包装类型的对象,再将其返回。

代理

Proxy 类可以在运行时创建实现了给定接口或者接口集的新类。代理类包括了特定接口所要求的所有方法,并且这些方法在Object类中定义的。调用处理器:是一个实现了InvocationHandler 接口的类对象。InvocationHandler 接口只有一个方法:

Object invoke(Object proxy, Method method, Object[] args)

创建一个代理对象使用 Proxy 类的 newProxyInstance() 方法。该方法有三个参数:1.类加载器。2.Class对象数组。3.调用处理器。

本文标题:反射

文章作者:Bangjin-Hu

发布时间:2019年10月15日 - 09:22:26

最后更新:2020年03月30日 - 08:11:39

原始链接:http://bangjinhu.github.io/undefined/Java 反射/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

Bangjin-Hu wechat
欢迎扫码关注微信公众号,订阅我的微信公众号.
坚持原创技术分享,您的支持是我创作的动力.