Java高级特性 - Java反射

第1关:了解 Class 对象

100

  • 任务要求
  • 参考答案
  • 评论40

任务描述

本关任务:实现获取Class对象的三种方式

相关知识

为了完成本关任务,你需要掌握:

  1. Class对象;

  2. 三种获取Class类型的实例的方法;

    1. 通过Object类中的getClass()方法;
    2. 通过静态方法Class.forName("全类名")
    3. 通过类字面常量Class.class
  3. 三种方法获取的Class对象之间的区别。

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

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

  3. Class对象就是用来创建类的所有的"常规"对象的。每个类都有一个Class对象,每当编写一个并且编译了一个新类,就会产生一个 Class对象(保存在体同名的 .class 文件中)。

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

三种获取Class类型的实例的方法

方法1:通过Object类中的getClass()方法返回一个Class类型的实例

示例如下:

 
  1. Person person = new Person();
  2. Class clazz = person.getClass();

方法2:通过静态方法Class.forName("全类名")获取类名对应的Class对象

Class.forName()方法原型:

 
  1. public static Class<?> forName(String className) throws ClassNotFoundException

若无法根据类路径className找到对应的 .class 文件会抛出 ClassNotFoundException异常,因此使用forName()方法需要捕获异常或向上抛出异常。

示例如下:

 
  1. Class clazz = null;
  2. String className = "step1.Person";
  3. try {
  4. clazz = Class.forName(className);
  5. } catch(ClassNotFoundException e) {
  6. }

方法3:通过类字面常量Class.class获取

示例如下:

 
  1. Class clazz = Person.class;

该方法不仅更简单,而且更安全,因为它在编译时就会受到检查(因此不需要置于try语句块中)。并且它根除了对forName()方法的调用,所以更高效。

三种方法获取的 Class 对象之间的区别

每个类都有一个Class对象,因此对于某个类使用三种方式获取的 Class对象都是相等。

编程要求

请仔细阅读右侧代码,结合相关知识,在 Begin-End 区域内进行代码补充,完成三个方法getPersonClass1()getPersonClass2()getPersonClass3()的代码编写,要求分别使用三种方式获取Person类的Class对象并返回。 注意:无需修改main()方法的输出内容。

测试说明

平台会对你编写的代码进行测试:

预期输出:

通过Object 类中的 getClass() 获取的 Class 对象为:class step1.Person 通过静态方法 Class.forName() 获取的 Class 对象为:class step1.Person 通过类字面常量获取 Class 的对象为:class step1.Person


开始你的任务吧,祝你成功!

package step1;

/**

 * 学员任务文件

 */

public class Reflect_stu {

    public static void main(String[] args) {

        System.out.println("通过Object 类中的 getClass() 获取的 Class 对象为:" + getPersonClass1());

        System.out.println("通过静态方法 Class.forName() 获取的 Class 对象为:" + getPersonClass2());

        System.out.println("通过类字面常量获取 Class 的对象为:" + getPersonClass3());

    }

    /**

     * 通过 Object 类中的 getClass() 获取的 Class 对象

     *

     * @return

     */

    public static Class getPersonClass1() {

        /********** Begin *********/

    Person person = new Person();

        Class clazz1 = person.getClass();

        return clazz1;

        /********** End *********/

    }

    /**

     * 通过静态方法 Class.forName() 获取的 Class 对象

     * <p>

     * 注意:Person 类的全路径为: step1.Person

     *

     * @return

     */

    public static Class getPersonClass2() {

        /********** Begin *********/

        String className = "step1.Person";

        Class clazz2 = null;

        try {

            clazz2 = Class.forName(className);

        } catch (ClassNotFoundException e) {

        }

        return clazz2;


 

        //return null;去掉这个傻逼null

        /********** End *********/

    }

    /**

     * 通过类字面常量获取 Class 的对象

     *

     * @return

     */

    public static Class getPersonClass3() {

        /********** Begin *********/

        Class clazz3 = Person.class;

        return clazz3;


 

        //return null;

        /********** End *********/

    }

}

第2关:利用反射分析类的能力

300

  • 任务要求
  • 参考答案
  • 评论40

任务描述

本关任务:利用反射获取Apple类的的所有的方法和构造器签名,以及全部域名。

相关知识

为了完成本关任务,你需要回顾上节所学Class对象的相关知识, 以及需要掌握以下知识:

  1. 反射的基本概念;

  2. Class对象与反射之间的关系;

  3. Class的类结构;

  4. 利用反射分析类的能力。

反射的基本概念

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

Class 对象与反射之间的关系

Java 反射机制允许程序在运行时取得任何一个已知名称的Class 的内部信息,包括其 modifiers(修饰符)、fields(属性),methods(方法)等,并可于运行时改变 fields 内容或调用 methods。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等等;但是需要注意的是反射使用不当会造成很高的资源消耗!

Class 的类结构

java.lang.reflect包中有三个类FieldMethodConstructor分别用于描述类的域、方法和构造器。Class类中的getFields()getMethods()getConstructors()方法将分别返回类提供的 public 域、方法和构造器,其中包括超类的共有成员。Class类中的getDeclareFields()getDeclareMethods()getDeclareConstructors()方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护的成员,但不包括超类的成员。

如:

 
  1. public Field[] getFields() throws SecurityException
  2. public Method[] getMethods() throws SecurityException
  3. public Constructor<?>[] getConstructors() throws SecurityException
利用反射分析类的能力

通过获取某个类的Class对象,并通过Class类的 getFields()getMethods()getConstructors()获得所有域、方法和构造器。

示例:

 
  1. class Person {
  2. public String name;
  3. String sex;
  4. protected String height;
  5. private int age;
  6. public Person() {
  7. }
  8. private Person(String name) {
  9. this.name = name;
  10. }
  11. public Person(String name, String sex, String height, int age) {
  12. ...省略
  13. }
  14. }
  15. public static void main(String[] args) {
  16. Person person = new Person();
  17. printConstructors(person.getClass());
  18. }
  19. public static void printConstructors(Class clazz) {
  20. Constructor[] constructors = clazz.getConstructors();
  21. for (Constructor constructor : constructors) {
  22. String name = constructor.getName();
  23. System.out.print(" ");
  24. String modifiers = Modifier.toString(constructor.getModifiers());
  25. if (modifiers.length() > 0) {
  26. System.out.print(modifiers + " ");
  27. }
  28. System.out.print(name + "(");
  29. Class[] paramTypes = constructor.getParameterTypes();
  30. for (int j = 0; j < paramTypes.length; ++j) {
  31. if (j > 0) {
  32. System.out.print(",");
  33. }
  34. System.out.print(paramTypes[j].getName());
  35. }
  36. System.out.println(");");
  37. }
  38. }

输出结果: public Person(); public Person(java.lang.String,java.lang.String,java.lang.String,int);

编程要求

请仔细阅读右侧代码,结合相关知识,在 Begin-End 区域内进行代码补充,打印Apple类的所有public 域、方法和构造器。已分别提供了方法声明printConstructorsprintFieldsprintMethods,请将代码补充完整,且按照打印格式要求输出。

提示:

  1. Method.getReturnType()可以获得方法的返回类型。

  2. 打印方法或域的修饰符可以调用提供的printModifiers()方法

  3. 打印方法的参数可以调用提供的printParamTypes()方法

  4. FieldgetType方法可以获得域类型、getName方法可以获得域的名称

测试说明

预期输出: private java.lang.String name; public step2.Apple(); public step2.Apple(java.lang.String); public void setName(java.lang.String);

平台会对你编写的代码进行测试。


开始你的任务吧,祝你成功!

package step2;

import java.lang.reflect.Constructor;

import java.lang.reflect.Field;

import java.lang.reflect.Method;

import java.lang.reflect.Modifier;

class Apple {

    private String name;

    public Apple(){}

    public Apple(String name){}

    public void setName(String name) {

        this.name = name;

    }

}

public class Reflect_stu {

    public static void main(String[] args) {

        // 请根据提供的 classPath 获取 step2.Apple 的 Class 对象, 请使用 Class.forName() 方法, 注意捕获异常

        // 通关之后,你也可以修改 clasapath 为其他类路径,分析某个类的能力, 例如: java.util.Date

        String classPath = "step2.Apple";

        Class clazz = null;

        /********** Begin *********/

        try {

            clazz = Class.forName(classPath);

        } catch (ClassNotFoundException e) {

            // TODO 自动生成的 catch 块

            e.printStackTrace();

        }

        /********** End *********/

        printFields(clazz);

        printConstructors(clazz);

        printMethods(clazz);

    }

   

    /**

     * 请打印类的每个域,输出格式为:修饰符 类型 变量名;

     * @param clazz

     */

    public static void printFields(Class clazz) {

        /********** Begin *********/

        try {

            Field[] fields = clazz.getDeclaredFields();

            for (Field field : fields) {

                System.out.print(Modifier.toString(field.getModifiers()) + " ");

                System.out.print(field.getType().getTypeName() + " ");

                System.out.println(field.getName() + ";");

            }

        } catch (Exception e) {

            // TODO 自动生成的 catch 块

            e.printStackTrace();

        }

        /********** End *********/

    }

    /**

     * 打印构造函数,输出格式为:修饰符 方法名称(参数)

     * @param clazz

     */

    public static void printConstructors(Class clazz) {

        Constructor[] constructors = clazz.getDeclaredConstructors();

        for (Constructor constructor : constructors) {

            Class[] paramTypes = null;

            /********** Begin *********/

            paramTypes = constructor.getParameterTypes();

            System.out.print(Modifier.toString(constructor.getModifiers()) + " ");

            System.out.print(constructor.getName() + "(");

           

            /********** End *********/

            printParamTypes(paramTypes);

        }

    }

    /**

     * 请针对每个方法打印其签名,格式为:修饰符 返回值类型 方法名称(参数);

     * @param clazz

     */

    public static void printMethods(Class clazz) {

        Method[] methos = clazz.getDeclaredMethods();

        for (Method method : methos) {

            Class[] paramTypes = null;

            /********** Begin *********/

            paramTypes = method.getParameterTypes();

            System.out.print(Modifier.toString(method.getModifiers()) + " " + method.getReturnType().getName() + " "

                    + method.getName() + "(");

            /********** End *********/

            printParamTypes(paramTypes);

        }

    }


 

    /**

     * 打印方法参数

     * @param paramTypes

     */

    private static void printParamTypes(Class[] paramTypes) {

        for (int j = 0; j < paramTypes.length; ++j) {

            if (j > 0) {

                System.out.print(",");

            }

            System.out.print(paramTypes[j].getName());

        }

        System.out.println(");");

    }

   

}

第3关:在运行时使用反射分析对象

300

  • 任务要求
  • 参考答案
  • 评论40

任务描述

本关任务:完成一个可供任意类使用的通用toString(Object)方法。

相关知识

为了完成本任务,你需要掌握:

  1. 如何通过Field类的get方法获取对象域;

  2. 如何绕过 Java 安全访问控制获取私有对象域;

  3. 获取对象域时数值类型如何处理。

如何通过 Field 类的 get 方法获取对象域

利用反射机制可以查看在编译时还不清楚的对象域。而查看对象域的关键方法就是Field类中的get方法。

Objectget(Object obj)  返回指定对象上此Field表示字段的值。

如果f是一个Field类型的对象(例如,通过getDeclaredFields() 得到的对象),obj是一个包含f域的类的对象, 那么f.get(obj)将返回一个对象,其值为obj域的当前值。如此就可以在运行时获得对象的域。

示例:

 
  1. class Person {
  2. public Integer weight;
  3. private Integer age;
  4. private double height;
  5. // 省略构造器、setter 和 getter 方法
  6. }
  7. public void test() {
  8. Person person = new Person(123, 19);
  9. Class clazz = person.getClass();
  10. Field field = clazz.getDeclaredField("weight");
  11. Object name = field.get(person);
  12. System.out.println(name); // 其打印结果为: "123"
  13. }
如何绕过 Java 安全访问控制获取私有对象域

那如果要获取对象的私有域呢,例如,要获取Perso小明的年龄呢,直接修改上面示例为getDeclaredField("age")可以吗。答案是,不行,因为age是一个私有域,所以get方法会抛出一个 IllegalAccessException异常。

只有利用get方法才能得到可访问域的值。除非拥有访问权限,Java 安全机制只允许查看任意对象有哪些域,而不允许读取它们的值。

反射机制的默认行为受限于 Java 的访问控制。然而,如果一个 Java程序没有收到安全管理器的控制,就可以覆盖访问控制。调用 FieldMethodConstructor对象的setAccessible 方法可以突破这种限制。

示例:

 
  1. public void test() {
  2. Person person = new Person(123, 19, 175);
  3. Class clazz = person.getClass();
  4. Field field = clazz.getDeclaredField("age");
  5. // 获取私有域的访问权限
  6. field.setAccessible(true);
  7. Object name = field.get(person);
  8. System.out.println(name); // 其打印结果为: "19"
  9. }
获取对象域时数值类型如何处理
Objectget(Object obj)  返回指定对象上此Field表示字段的值。

get方法还有一个要解决的问题。name域是一个String,因此可以作为Object返回。但是,如果要查看height域,它属于double类型,而 Java 中数值类型不是对象。因此要使用Field的其他 getXXX() 方法。反射机制会自动的将这个域值打包到相应的对象包装器中。

doublegetDouble(Object obj)  获取double类型或另一个通过扩展转换可以转换为double类型的基本类型的静态或实例字段的值。
floatgetFloat(Object obj)  获取float类型或另一个通过扩展转换可以转换为float类型的基本类型的静态或实例字段的值。
TypegetGenericType()  返回一个Tpye对象,它表示此Field对象所表示字段的声明类型。
intgetInt(Object obj)  获取int类型或另一个通过扩展转换可以转换为int类型的基本类型的静态或实例字段的值。
longgetLong(Object obj)  获取long类型或另一个通过扩展转换可以转换为long类型的基本类型的静态或实例字段的值。
编程要求

请仔细阅读右侧代码,结合相关知识,在 Begin-End 区域内进行代码补充,完成通用toString()方法。

提示:

 
  1. 快速设置访问权限: ``AccessibleObject.setAccessible(fields, true); ``
  2. 获得所有域:``Class.getDeclaredFields()``
测试说明

平台会对你编写的代码进行测试。

示例:

 
  1. public static void toString(Object obj) {
  2. // 请完成代码
  3. }
  4. public static void main(String[] args) {
  5. Person person = new Person(123, 19, 175);
  6. toString(person);
  7. }

预期输出: [weight=[value=123],age=[value=19],height=[value=175.0]]


开始你的任务吧,祝你成功!

package step3;

import java.lang.reflect.AccessibleObject;

import java.lang.reflect.Field;

import java.lang.reflect.Modifier;

public class Reflect_stu {

    public static String toString(Object obj) {

        Class cl = obj.getClass();

        String r = "";

        r += "[";

        // 请获取所有 Field 并设置访问权限为 true

        /********** Begin *********/

        Field[] fields = null;

        fields = cl.getDeclaredFields();

        AccessibleObject.setAccessible(fields, true);

        /********** End *********/

        for (Field f : fields) {

            // 此处 if,逻辑为判断 Field 域是否为非静态域

            if (!Modifier.isStatic(f.getModifiers())) {

                if (!r.endsWith("[")) r += ",";

                r += f.getName() + "=";

                try {

                    // 请获取域的类型及值

                    /********** Begin *********/

                    Class t = null;

                    Object val = null;

                    t = f.getType();

                    val = f.get(obj);//用get取值就好啦


 

                    /********** End *********/

                    // isPrimitive() 用于判断是否为基本数据类型,若为基础数据类型直接拼接,否则递归调用 toString 方法

                    if (t.isPrimitive()) r += val;

                    else r += toString(val);

                } catch (Exception e) {

                    e.printStackTrace();

                }

            }

        }

        r += "]";

        return r;

    }


 

    public static void main(String[] args) {

        Person person = new Person(88, 19, 175);

        System.out.println(toString(person));

    }

}

class Person {

    public Integer weight;

    private Integer age;

    private Double height;


 

    public Person(Integer weight, Integer age, double height) {

        this.weight = weight;

        this.age = age;

        this.height = height;

    }

}

第4关:利用反射进行方法调用

300

  • 任务要求
  • 参考答案
  • 评论40

任务描述

本关任务:利用反射创建对象并调用其方法。

相关知识

为了完成本关任务,你需要掌握:

  1. 如何通过反射创建对象;

  2. 如何通过反射调用对象方法。

如何通过反射创建对象

反射创建类对象主要有两种方式,通过Class对象的newInstance()方法、通过Constructor对象的 newInstance()方法。

  1. 第一种:通过Class对象的newInstance()方法。

     
      
    1. Class clazz = Apple.class;
    2. Apple apple = (Apple)clazz.newInstance();
  2. 第二种:通过Constructor对象的newInstance()方法

     
      
    1. Class clazz = Apple.class;
    2. Constructor constructor = clazz.getConstructor();
    3. Apple apple = (Apple)constructor.newInstance();

通过Constructor对象创建类对象可以选择特定构造方法,而通过 Class对象则只能使用默认的无参数构造方法。

示例:(调用有参构造方法进行类对象的初始化)

 
  1. Class clz = Apple.class;
  2. Constructor constructor = clz.getConstructor(String.class, int.class);
  3. Apple apple = (Apple)constructor.newInstance("红富士", 15);
如何通过反射调用对象方法

利用Methodinvoke方法可以调用执行对象obj的方法

Objectinvoke(Object obj,Object... args)  对带有指定参数的指定对象调用由此Method对象表示的底层方法。

参数: obj 表示要调用的Method方法对象。 args 表示要调用的方法的参数,是可变长参数类型。

示例:

 
  1. // 获取类的 Class 对象实例
  2. Class clz = Class.forName("Apple");
  3. // 根据 Class 对象实例获取 Constructor 对象
  4. Constructor appleConstructor = clz.getConstructor();
  5. // 使用 Constructor 对象的 newInstance 方法获取反射类对象
  6. Object appleObj = appleConstructor.newInstance();
  7. // 而如果要调用某一个方法,则需要经过下面的步骤:
  8. // 1、获取方法的 Method 对象
  9. Method setPriceMethod = clz.getMethod("setPrice", int.class);
  10. // 2、用 invoke 方法调用方法
  11. setPriceMethod.invoke(appleObj, 14);
  12. class Apple {
  13. public void setPrice(int price) {
  14. //省略
  15. }
  16. // 省略
  17. }
编程要求

请仔细阅读右侧代码,结合相关知识,在 Begin-End 区域内进行代码补充,使用反射调用 Apple 类的 setPrice()方法,设置苹果价格为 14,并打印价格。接着还要用反射去调用getTotal方法获取单价为 20,数量 24 的总金额并打印。

测试说明

预期输出: 14.0 480.0

平台会对你编写的代码进行测试。


开始你的任务吧,祝你成功!

package step4;

import java.lang.reflect.Constructor;

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;


 

public class Reflect_stu {

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

        //使用反射调用

        Class clazz = null;

        try {

            clazz = Class.forName("step4.Apple");

            /********** Begin *********/

            Constructor cons = clazz.getConstructor();

            Apple apple = (Apple) cons.newInstance();

            Method method = clazz.getMethod("setPrice", double.class);

            method.invoke(apple, 14);

            Method getPrice = clazz.getMethod("getPrice");

            System.out.println(getPrice.invoke(apple));

            Method getTotal = clazz.getMethod("getTotal", double.class, int.class);

            System.out.println(getTotal.invoke(apple, 20, 24));

            //去文心一言问ai解释代码吧

            /********** End *********/

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

}


 

class Apple {

    private double price;

    private int count;

    public Apple() {

    }

    public double getPrice() {

        return price;

    }

    public void setPrice(double price) {

        this.price = price;

    }

    public int getCount() {

        return count;

    }

    public void setCount(int count) {

        this.count = count;

    }

    public double getTotal(double price, int count) {

        return price * count;

    }

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值