Java——反射

news/2024/5/19 4:41:09 标签: java, 反射

什么是反射

答:能够分析类能力的程序称为反射(reflective)。

反射机制可以用来干嘛?

答:1.在运行时分析类的能力       2.在运行时查看对象,例如toString方法。 3.实现通用的数组操作代码。4.利用Method对象。

在程序运行期间,Java运行时系统始终为所有的对象维护一个称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。我们可以通过专门的Java类去访问这些信息,保存这些信息的类称为Class。

获取Class对象有三种方法:

1.通过对象.getClass()方法获得。 

java">Class c1 = new String("getClass方法").getClass();

2.通过静态方法forName(String className)获得className类名对应的Class对象。注意,只有在className是类名或接口名时才能使用这个方法,并且无论何时使用这个方法,都必须提供一个异常处理器。

java">String className = "java.util.Random";
try {
    Class c1 = Class.forName(className);
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

3.使用T.class,如果T是任意的Java类型(或void关键字),T.class将代表匹配的类对象。

java">Class c1 = Random.class;
Class c2 = int.class;
Class c3 = Double[].class;
Class c4 = void.class;

需要注意的是一个Class对象实际上表示的是一个类型,而这个类型不一定是一种类。例如,int,void不是类,但是int.class是一个Class对象。其实Class类实际上是一个泛型类。例如Random.class的类型是Class<Random>。

注意,获取类对象的时候,会导致类属性被初始化。无论什么途径获取类对象,都会导致静态属性被初始化,而且只会执行一次。(除了直接使用 Class c = T.class 这种方式,这种方式不会导致静态属性被初始化)

java">public class TestReflection {

    public static void main(String[] args) throws InterruptedException {
        Class c = Test.class;

    }
}

class Test {
    static String test;

    static {
        System.out.println("静态方法执行");
        test = "类属性初始化成功";
        System.out.println("test: " + test);
    }
}

public class TestReflection {

    public static void main(String[] args) throws InterruptedException {
        Class c = Test.class;
        Class c1 = new Test().getClass();
    }
}

class Test {
    static String test;

    static {
        System.out.println("静态方法执行");
        test = "类属性初始化成功";
        System.out.println("test: " + test);
    }
}

虚拟机为每个类型管理一个Class对象。因此可以使用"=="运算符来比较两个对象。例如:

public class TestReflection {

    public static void main(String[] args) throws InterruptedException, ClassNotFoundException {
        Class c = Test.class;
        Class c1 = new Test().getClass();
        if(c == c1){
            System.out.println("相等");
        }
    }
}

class Test {
    static String test;

    static {
        System.out.println("静态方法执行");
        test = "类属性初始化成功";
        System.out.println("test: " + test);
    }
}

Class对象中有一个很有用的方法---->newInstance(),这个方法可以用来动态地创建一个类的实例。例如,test.getClass().newInstance();创建了一个与test具有相同类型的实例。newInstace方法调用默认的构造器(没有参数的构造器)初始化新创建的对象。如果这个类没有默认的构造器,就会抛出一个异常。通常我们将forName与newInstance配合起来使用。

注意:如果希望向按名称创建的类的构造器提供参数,就必须使用Constructor类中的newInstance方法。

分割线---------------------------------------------------------------------------------------------------------------------------

下面简要的介绍一下反射机制最重要的内容——检查类的结构。

java.lang.reflect包中有三个类Filed、Method和Constructor分别用来描述类的域、方法和构造器。这三个类都有一个叫做getName的方法,用来返回项目的名称,一个getModifiers的方法,返回一个整形数值,用不同的位开关描述public和static这样的修饰符使用状况。Filed类有一个getType方法,用来返回描述域所属类型的Class对象。Method 和 Constructor 类有能够报告参数类型的方法,Method 类还有一个可以报告返回类型的方法。

Class类中的getFileds、getMethods 和 getConstructors方法将返回类提供的public域、方法和构造器数组,其中包括超类的公有成员。Class类的 getDeclareFields、getDeclareMethods 和 getDeclaredConstructors方法将返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。

下面程序显示了如何打印一个类的全部信息的方法。输入要打印的类名即可,注意如果前面有包名的话,也要输入。

java">package 反射;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Scanner;

/**
 * @ClassName: TestReflection
 * @Description: 打印类的全部信息
 * @author: GGBOY
 * @date 2019/10/15 21:19
 * @Version: 1.0
 **/
public class TestReflection {

    public static void main(String[] args) {
        // read class name from command line args user input
        String name;
        Scanner in = new Scanner(System.in);
        System.out.println("Enter class name (e.g java.util,Date):");
        name = in.next();

        try {
            // print class name and superclass name (if != object)
            Class cl = Class.forName(name);
            Class superCl = cl.getSuperclass();
            String modifiers = Modifier.toString(cl.getModifiers());
            if (modifiers.length() > 0) {
                // 打印修饰符
                System.out.print(modifiers + " ");
            }
            // 打印类名
            System.out.print("class " + name);
            // 如果有继承关系,则把父类名也给打印出来
            if (superCl != null && superCl != Object.class) {
                System.out.print(" extends " + superCl.getName());
            }
            System.out.print("\n{\n");
            printConstructors(cl);
            System.out.println();
            printMethods(cl);
            System.out.println();
            printFields(cl);
            System.out.println("}");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 打印类的所有构造器
     *
     * @param c1 a class
     * @return void
     * @author GGBOY
     * @date 2019/10/15
     */
    public static void printConstructors(Class c1) {
        // getDeclaredConstructors方法返回这个类的所有构造器。
        // getConstructors方法返回这个类的公有构造器。
        Constructor[] constructors = c1.getDeclaredConstructors();

        for (Constructor c :
                constructors) {
            String name = c.getName();
            System.out.print(" ");
            String modifiers = Modifier.toString(c.getModifiers());
            if (modifiers.length() > 0) {
                System.out.print(modifiers + " ");
            }
            System.out.print(name + "(");


            // 获取构造器中的参数类型
            Class[] paramTypes = c.getParameterTypes();
            for (int i = 0; i < paramTypes.length; i++) {
                if (i > 0) {
                    System.out.print(", ");
                }
                // 打印构造器中的参数类型
                System.out.print(paramTypes[i].getName());
            }
            System.out.println(");");
        }
    }

    /**
     * 打印类的所有方法,包括实现接口的全部方法,但不包括由父类继承的方法
     *
     * @param cl 一个类
     * @return void
     * @author GGBOY
     * @date 2019/10/16
     */
    public static void printMethods(Class cl) {
        // getDeclaredMethods 返回这个类或接口的全部方法,但不包括由超类继承了的方法。
        // getMethods会返回所有的公有方法,包括从超类继承来的方法。
        Method[] methods = cl.getDeclaredMethods();

        for (Method method :
                methods) {
            // 获取方法的返回类型
            Class retType = method.getReturnType();
            // 获取方法名
            String name = method.getName();

            System.out.print(" ");
            String modifiers = Modifier.toString(method.getModifiers());
            if (modifiers.length() > 0) {
                // 打印修饰符
                System.out.print(modifiers + " ");
            }
            // 打印返回类型和方法名
            System.out.print(retType.getName() + " " + name + "(");

            // 获取方法的参数
            Class[] paramTypes = method.getParameterTypes();
            for (int i = 0; i < paramTypes.length; i++) {
                if (i > 0) {
                    System.out.print(", ");
                }
                System.out.print(paramTypes[i].getName());
            }
            System.out.println(");");
        }
    }

    /**
     * 打印类的所有属性,包括父类的公有属性
     *
     * @param cl 一个类
     * @return void
     * @author GGBOY
     * @date 2019/10/16
     */
    public static void printFields(Class cl) {
        // getDeclaredFields方法返回包含Field对象的数组,这些对象记录了类的全部域。
        // getFields方法将返回一个包含Field对象的数组,这些对象记录了这个类或其超类的公有域。
        // 如果类中没有域,或者Class对象描述的是基本类型或数组类型,这些方法将返回一个长度为0的数组
        Field[] fields = cl.getDeclaredFields();

        for (Field field :
                fields) {
            // 获取属性类型
            Class type = field.getType();
            // 获取属性名
            String name = field.getName();
            System.out.print(" ");
            String modifiers = Modifier.toString(field.getModifiers());
            if (modifiers.length() > 0) {
                System.out.print(modifiers + " ");
            }
            System.out.println(type.getName() + " " + name + ";");
        }
    }
}


通过前面的讲解,知道了如何查看任意对象的数据域名称和类型:通过获得对于的Class对象,再通过Class对象调用getDeclaredFieds。

那么如何查看数据域中的内容呢

答:查看对象域的关键方法是Field类中的get方法。如果f是一个Field类型的对象(例如,通过getDeclaredFields得到的对象),obj是某个包含f域的类的对象,f.get(obj)将返回一个对象,其值为obj域的当前值。例如:

public class ReflectionTest {

    public static void main(String[] args) {
        Test t = new Test("xxx", 222.22);
        Class cl = t.getClass();
        Field f = null;
        try {
            // 获得Test类的name域
            f = cl.getDeclaredField("name");
            // 取出name域中的值
            Object v = f.get(t);
            // 打印
            System.out.println(v.toString());
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

    }

}

class Test {
    private String name;
    private double salary;

    public Test(String name, double salary) {
        this.salary = salary;
        this.name = name;
    }

}

实际上,上述代码有一个问题。由于name是一个私有域,所以get 方法会抛出一个IllegalAccessException异常。只有利用get方法才能得到可访问域的值。除非拥有访问权限,否则Java安全机制只允许查看任意对象有哪些域,而不允许读取它们的值。

我们可以通过 f.setAccessible(true)方法来得到访问权限,这样我们就可以通过f.get(t)获得name域的值了。注意:get方法还有一个需要解决的问题。name域是一个String类型,所以把它作为Object返回没有什么问题。但是,假定我们想要查看的是salary域,它属于double类型,而Java中数值类型不是对象。要想要解决这个问题,可以使用Field类中的getDouble方法,这样反射机制将会自动的将这个域值打包到相应的对象包装器中,即打包成Double.

当然,可以获得就可以设置。通过调用f.set(obj, value)可以将obj对象的f域设置成新的value值。

分割线---------------------------------------------------------------------------------------------------------------------------

使用反射编写泛型数组:

java.lang.reflect包中的Array类允许动态的创建数组,例如:

Test[] t = new Test[100];
...
// t 数组已经满了
t = Arrays.copyOf(t, 2 * t.length);

copyOf方法可以用于扩展已经填满的数组。那么如何编写一个方法,正好能将Test[]数组转换为Object[]数组呢,下面做一个尝试。

java">    public static Object[] badCopyOf(Object[] a, int newLength) {
        Object[] newArray = new Object[newLength];
        System.arraycopy(a, 0, newArray, 0,
                Math.min(a.length, newLength));
        return newArray;
    }

这个方法咋一看好像是可以的,但是实际上存在一个问题,因为这个方法返回的是对象数组类型,这是由于使用了 new Object[newLength]代码创建的数组。一个对象数组不能转换成Test数组,如果这样做的话,在运行时会抛出ClassCastException异常。将一个Test[]临时转换成Object[]数组,再把它转换回来是可以的,但是一个从一开始就是Object[]的数组却永远不能转换成Test[]数组。

为了编写通用的代码,需要能够创建与原数组类型相同的新数组,此时我们就可以使用Array类中的静态方法newInstance,它能构造新数组。在调用它时必须提供两个参数,一个是数组的元素类型,一个是数组的长度.

Object newArray = Array.newInstance(componentType, newLength);

我们可以通过调用Array.getLength(a)获得数组的长度。想要获得新数组元素类型时,就需要这样做了:1.首先获得数组a的类对象 2.确认它是一个数组。3.使用Class类的getComponentType方法确定数组对应的类型。下面是新的拷贝方法:

public static Object goodCopyOf(Object a, int newLength) {
        Class cl = a.getClass();
        // 如果传进来的参数不是数组类型
        if (!cl.isArray()) {
            return null;
        }
        Class componentType = cl.getComponentType();
        int length = Array.getLength(a);
        Object newArray = Array.newInstance(componentType, newLength);
        System.arraycopy(a, 0, newArray, 0,
                Math.min(length, newLength));
        return newArray;
    }

这个CopyOf方法可以用来扩展任意类型的数组,而不仅仅是对象数组。在这个方法中,将参数声明为Object类型是因为,数组类型xx[]可以转换为Object,而不能转换成对象数组。下面是两个扩展数组方法的测试程序,需要注意的是badCopyOf的返回值进行类型转换时会抛出一个异常。

java">package 反射;

import java.lang.reflect.Array;
import java.util.Arrays;

/**
 * @ClassName: CopyOfTest
 * @Description: 扩展任意类型数组方法的测试类
 * @author: GGBOY
 * @date 2019/10/16 14:16
 * @Version: 1.0
 **/
public class CopyOfTest {

    public static void main(String[] args) {
        int[] a = {1, 2, 3};
        a = (int[]) goodCopyOf(a, 10);
        System.out.println(Arrays.toString(a));

        String[] b = {"小米", "红米", "华为"};
        b = (String[]) goodCopyOf(b, 10);
        System.out.println(Arrays.toString(b));

        System.out.println("下面的扩展方法在进行类型转换时会报错");
        b = (String[]) badCopyOf(b, 10);
    }

    /**
     * 扩充数组
     *
     * @param a         需要扩展的数组
     * @param newLength 新数组的长度
     * @return java.lang.Object[]
     * @author GGBOY
     * @date 2019/10/16
     */
    public static Object[] badCopyOf(Object[] a, int newLength) {
        Object[] newArray = new Object[newLength];
        System.arraycopy(a, 0, newArray, 0,
                Math.min(a.length, newLength));
        return newArray;
    }

    /**
     * 通过创建相同类型的数组来扩充数组
     *
     * @param a         需要扩充的原数组
     * @param newLength 新数组的长度
     * @return java.lang.Object
     * @author GGBOY
     * @date 2019/10/16
     */
    public static Object goodCopyOf(Object a, int newLength) {
        Class cl = a.getClass();
        // 如果传进来的参数不是数组类型
        if (!cl.isArray()) {
            return null;
        }
        Class componentType = cl.getComponentType();
        int length = Array.getLength(a);
        // newInstance(Class componentType, int[] lengths) 返回一个具有给定类型、给定维数的新数组
        // newInstance(Class componentType, int length)
        Object newArray = Array.newInstance(componentType, newLength);
        System.arraycopy(a, 0, newArray, 0,
                Math.min(length, newLength));
        return newArray;
    }
}

分割线---------------------------------------------------------------------------------------------------------------------------

使用反射调用类中的任意方法

在Method类中有一个invoke方法,它允许调用包装在当前Method对象中的方法。invoke方法的声明是:Object invoke(Object obj, Object... args) 第一个参数是隐士参数,其余的对象提供了显示参数。

对于静态方法而言,第一个参数可以被忽略,即可以将它设置为null。例如,假设用tl 代码Test类的getName方法,下面这条语句显示了如何调用这个方法:

java">String n = (String) tl.invoke(t);

我们可以通过调用getDeclareMethods方法,对返回的Method对象数组进行查找,搜索我们想要的方法。当然也可以通过调用Class类中的getMethod方法得到想要的方法。getMethod方法声明如下:

Method getMethod(String name, Class... parameterType)

如果类中有同名方法的话,使用getMethod方法时,要注意parameterType参数的匹配。例如:

public class Test {

    public static void main(String[] args) throws Exception {
        Hero hero = new Hero();
        Class cl = hero.getClass();
        Method method = cl.getMethod("method", 
                String.class);
        method.invoke(hero, "this");
    }

}

class Hero {

    public void method() {
        System.out.println("这是无参方法一");
    }

    public void method(String s) {
        System.out.println("这是有参数方法,参数是: " + s);
    }
}

 

注意:如果返回的是基本类型,invoke方法会返回其包装器类型。invoke的参数和返回值必须是Object类型,所以必须进行多次类型转换,并且这样做的话,编译器无法检查代码,要等到运行测试阶段才会发现这些错误。

 

反射介绍到这里就完了,如果有什么错误,请指出来,不胜感激。----菜鸡程序员

 

参考资料:

   《java核心技术 卷I》


http://www.niftyadmin.cn/n/1060931.html

相关文章

【复变函数与积分变换】06. 拉普拉斯变换

Contents6 拉普拉斯变换6.1 基本概念6.2 基本性质6.3 卷积6.4 拉普拉斯逆变换6.5 微分方程的拉氏变换解法6 拉普拉斯变换 6.1 基本概念 拉普拉斯变换的定义 拉普拉斯变换 LT F(s)L[f(s)]∫0∞f(t)e−stdtF(s)L[f(s)]\int_0^\infty f(t)e^{-st}dt F(s)L[f(s)]∫0∞​f(t)e−s…

我的2014年和2015年

总结2014&#xff1a; 我看别人都写了年度总结&#xff0c;我也写写吧。 1&#xff0c;我从年前3月份开始正式从.net转向了web前端&#xff0c;刚开始&#xff0c;我是一点也不会&#xff0c;只会一点薄弱的基础&#xff0c;但是&#xff0c;我看好以后移动应用所有就毅然决然的…

Java并发编程实战——学习笔记(一)

一、线程安全性 在线程安全性中&#xff0c;最核心的概念是正确性&#xff0c;而正确性的含义是&#xff1a;某个类的行为与其规范完全一致。这里的规范可以粗略理解为在各种限定条件下&#xff0c;类对象的结果与预期一致。在单线程中&#xff0c;正确性可以近似的定义为“所见…

Java Web——基于Servlet、JSP(无框架版)电影网站项目总结(一)

这学期在学习Java Web&#xff0c;于是花了大概一个礼拜的时间把书看了一遍&#xff0c;动手敲了一遍。自己一个人做了电影网站小项目作为练手&#xff0c;项目地址如下&#xff1a;http://120.27.192.30/movie/。访问最好使用谷歌浏览器&#xff08;因为没考虑兼容&#xff0c…

使用Windows Form 制作一个简易资源管理器

自制一个简易资源管理器----TreeView控件 第一步、新建project&#xff0c;进行基本设置&#xff1b;&#xff08;Set as StartUp Project&#xff1b;View/Toolbox/TreeView&#xff09; 第二步、开始添加节点 添加命名空间using System.IO;     1 using System;2 using S…

【计量经济学导论】08. 平稳时间序列

文章目录平稳时间序列平稳时间序列伪回归现象白噪声序列随机游走过程自相关函数 ACF偏相关函数 PACF平稳性的单位根检验AR(1){\rm AR}(1)AR(1) 序列Dickey-Fuller 检验Augmented Dickey-Fuller 检验单整时间序列平稳时间序列 平稳时间序列 在时间序列分析中&#xff0c;平稳时…

集群、分布式、负载均衡区别

集群、分布式、负载均衡区别参考&#xff1a;http://virtualadc.blog.51cto.com/3027116/615836” 集群 集群的概念 计算机集群通过一组松散集成的计算机软件和/或硬件连接起来高度紧密地协作完成计算工作。在某种意义上&#xff0c;他们可以被看作是一台计算机。集群系统中的单…

ubuntu 终端只显示当前目录名称

修改.bashrc文件&#xff1a; 原来&#xff1a; 59 #修改终端提示颜色60 color_promptyes61 62 if [ "$color_prompt" yes ]; then63 PS1${debian_chroot:($debian_chroot)}\[\033[01;32m\]\u\h\[\033[00m\]:\[\033[01;32m\]\w\[\033[00m\]\$ 64 else65 PS1…