反射代码

源地址 https://blog.csdn.net/wwwdc1012/article/details/85086698#commentBox

准备两个类Person   Employee

package com.reflect;

public class Person {
	public String name;
	protected String age;
	private String hobby;
	public Person(String name, String age, String hobby) {
		super();
		this.name = name;
		this.age = age;
		this.hobby = hobby;
	}
	public String getHobby() {
		return hobby;
	}
	
	public Person() {
		
	}
}
package com.reflect;

//public class Employee extends Person {
public class Employee extends Person {
	public static Integer totalNum = 0; // 员工数
    public int empNo;   // 员工编号 公有
    protected String position;  // 职位 保护
    private int salary; // 工资   私有

    public void sayHello() {
        System.out.println(String.format("Hello, 我是 %s, 今年 %s 岁, 爱好是%s, 我目前的工作是%s, 月入%s元\n", name, age, getHobby(), position, salary));
    }
    private void work() {
        System.out.println(String.format("My name is %s, 工作中勿扰.", name));
    }
    public Employee(String name, String age, String hobby, int empNo, String position, int salary) {
        super(name, age, hobby);
        this.empNo = empNo;
        this.position = position;
        this.salary = salary;
        Employee.totalNum++;
    }
    
    public Employee(){
    	
    }
}

  测试类:

功能一:获取反射类:(三种方式)

//验证获取的反射对象是否相同
	//方法有三种
	public static void main1(String[] args) throws ClassNotFoundException {
		Class c1 = Class.forName("com.reflect.Employee");
		Class c2 = Employee.class;
		Employee e = new Employee("小明", "18", "写代码", 1, "Java攻城狮", 100000);
		Class c3 = e.getClass();
		if(c1 == c2 && c2 ==c3 && c1 == c3) {
			System.out.println("c1、c2、c3 为同一个对象");
            System.out.println(c1);     // class reflect.Employee
		}
	}

功能二:根据反射类获取类的对象(两种方式)

//根据Class对象获取类的对象
	//方法有两种
	public static void main2(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		//根据class对象的newInstance()获取
		Class c = Employee.class;
		Employee employee = (Employee)c.newInstance();
		System.out.println(employee);
		System.out.println(employee.age);
		
		//根据创建constructor先获取构造函数,根据构造函数获取对象
		Constructor constructor = c.getConstructor(String.class,String.class,String.class,int.class,String.class,int.class);
		Employee employee2 = (Employee)constructor.newInstance("小明", "18", "写代码", 1, "Java攻城狮", 100000);
		System.out.println(employee2.age);
	}

功能三:根据反射对象获取类的: 构造函数、方法、字段

public static void main(String[] args) throws ClassNotFoundException {
        String name;
        if (args.length > 0) {
            name = args[0];
        } else {
            Scanner in = new Scanner(System.in);
            System.out.println("输入一个类名(e.g. java.util.Date):"); // reflect.Employee
            name = in.next();
        }
        try {
            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.println("\n{");

            printConstructors(cl); // 打印构造方法
            System.out.println();
            printMethods(cl);   // 打印方法
            System.out.println();
            printFields(cl);    // 打印属性
            System.out.println("}");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.exit(0);
    }
	
	/**
     * 打印Class对象的所有构造方法
     */
    public static void printConstructors(Class cl) {
        Constructor[] constructors = cl.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(");");
        }
    }

    /**
     * 打印Class的所有方法
     */
    public static void printMethods(Class cl) {
        Method[] methods = cl.getDeclaredMethods();
        //Method[] methods = cl.getMethods();
        for (Method m : methods) {
            Class retType = m.getReturnType();  // 返回类型
            System.out.print("  ");
            String modifiers = Modifier.toString(m.getModifiers());
            if (modifiers.length() > 0) {
                System.out.print(modifiers + " ");
            }
            System.out.print(retType.getName() + " " + m.getName() + "(");
            Class[] paramTypes = m.getParameterTypes();
            for (int i = 0; i < paramTypes.length; i++) {
                if (i > 0) {
                    System.out.print(", ");
                }
                System.out.print(paramTypes[i].getName());
            }
            System.out.println(");");
        }
    }

    /**
     * 打印Class的所有属性
     */
    public static void printFields(Class cl) {
        Field[] fields = cl.getDeclaredFields();
        for (Field f: fields) {
            Class type = f.getType();
            System.out.print("  ");
            String modifiers = Modifier.toString(f.getModifiers());
            if (modifiers.length()> 0) {
                System.out.print(modifiers + " ");
            }
            System.out.println(type.getName() + " " + f.getName() + ";");
        }
    }
    

 

运行时查看对象数据域的实际内容


上面我们已经获取到了类的信息,现在又有一个需求:在运行时查看对象的数据域的实际值。这个场景就像我们通过IDEA调试程序,设置断点拦截到程序后,查看某个对象的属性的值。

我们知道java反射机制提供了查看类信息的API,那么它应该也提供了查看Field域实际值和设置Field域实际值的API,没错,猜对了,确实有相关的API,但是有个疑问,有一些属性是private修饰的私有域,这种是否也能直接查看和设置呢?看完下面的API即可知道答案

运行时查看对象数据域实际内容的相关API


Class<?> getComponentType() 返回数组类里组件类型的 Class,如果不是数组类则返回null

boolean isArray() 返回这个类是否为数组,同类型的API还有 isAnnotation、isAsciiDigit、isEnum、isInstance、isInterface、isLocalClass、isPrimitive 等

int Array.getLength(obj) 返回数组对象obj的长度

Object Array.get(obj, i) 获取数组对象下标为i的元素

boolean isPrimitive() 返回这个类是否为8种基本类型之一,即是否为boolean, byte, char, short, int, long, float, 和double 等原始类型

Field getField(String name) 获取指定名称的域对象

AccessibleObject.setAccessible(fields, true) 当访问 Field、Method 和 Constructor 的时候Java会执行访问检查,如果访问者没有权限将抛出SecurityException,譬如访问者是无法访问private修饰的域的。通过设置 setAccessible(true) 可以取消Java的执行访问检查,这样访问者就获得了指定 Field、Method 或 Constructor 访问权限

Class<?> Field.getType() 返回一个Class 对象,它标识了此 Field 对象所表示字段的声明类型

Object Field.get(Object obj) 获取obj对象上当前域对象表示的属性的实际值,获取到的是一个Object对象,实际使用中还需要转换成实际的类型,或者可以通过 getByte()、getChar、getInt() 等直接获取具体类型的值

void Field.set(Object obj, Object value) 设置obj对象上当前域表示的属性的实际值

查看对象数据域实际内容程序


了解完上述相关API之后,我们敲出下面的程序来验证
 

public class ObjectAnalyzer {
    private ArrayList<Object> visited = new ArrayList<>();

    public String toString(Object obj) {
        if (obj == null) {
            return "null";
        }
        if (visited.contains(obj)) {    // 如果该对象已经处理过,则不再处理
            return "...";
        }
        visited.add(obj);

        Class cl = obj.getClass(); // 获取Class对象
        if (cl == String.class) {   // 如果是String类型则直接转为String
            return (String) obj;
        }
        if (cl.isArray()) {        // 如果是数组
            String r = cl.getComponentType() + "[]{\n";     // 数组的元素的类型
            for (int i = 0; i < Array.getLength(obj); i++) {
                if (i > 0) {   // 不是数组的第一个元素加逗号和换行,显示更加美观
                    r += ",\n";
                }
                r += "\t";
                Object val = Array.get(obj, i);
                if (cl.getComponentType().isPrimitive()) { // Class为8种基本类型的时候为 true,直接输出
                    r += val;
                } else {
                    r += toString(val); // 不是8中基本类型时,说明是类,递归调用toString
                }
            }
            return r + "\n}";
        }
        // 既不是String,也不是数组时,输出该对象的类型和属性值
        String r = cl.getName();
        do {
            r += "[";
            Field[] fields = cl.getDeclaredFields();    // 获取该类自己定义的所有域,包括私有的,不包括父类的
            AccessibleObject.setAccessible(fields, true); // 访问私有的属性,需要打开这个设置,否则会报非法访问异常
            for (Field f : fields) {
                if (!Modifier.isStatic(f.getModifiers())) { // 通过 Modifier 可获取该域的修饰符,这里判断是否为 static
                    if (!r.endsWith("[")) {
                        r += ",";
                    }
                    r += f.getName() + "=";     // 域名称
                    try {
                        Class t = f.getType();  // 域(属性)的类型
                        Object val = f.get(obj);   // 获取obj对象上该域的实际值
                        if (t.isPrimitive()) {     // 如果类型为8种基本类型,则直接输出
                            r += val;
                        } else {
                            r += toString(val);     // 不是8种基本类型,递归调用toString
                        }
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
            r += "]";
            cl = cl.getSuperclass(); // 继续打印超类的类信息
        } while (cl != null);
        return r;
    }
}

测试验证结果

接下来验证一下获取数据域实际值是否正确,分别打印数组、自定义类的对象的实际值

public class ObjectAnalyzerTest {
    public static void main(String[] args) {
        int size = 4;
        ArrayList<Integer> squares = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            squares.add(i * i);
        }
        ObjectAnalyzer objectAnalyzer = new ObjectAnalyzer(); // 创建一个上面定义的分析类ObjectAnalyzer的对象
        System.out.println(objectAnalyzer.toString(squares)); // 分析ArrayList<Integer>对象的实际值

        Employee employee = new Employee("小明", "18", "爱好写代码", 1, "Java攻城狮", 100); // 分析自定义类Employee的对象的实际值
        System.out.println(objectAnalyzer.toString(employee));
    }
}

输出如下

java.util.ArrayList[elementData=class java.lang.Object[]{
	java.lang.Integer[value=0][][],
	java.lang.Integer[value=1][][],
	java.lang.Integer[value=4][][],
	java.lang.Integer[value=9][][]
},size=4][modCount=4][][]
reflect.Employee[empNo=1,position=Java攻城狮,salary=100][name=小明,age=18,hobby=爱好写代码][]

 

其中ArrayList<Integer>打印了类名和5个元素的类型和值,Employee 打印了类名,自己定义的3个基本类型的属性的实际值,和父类Person的3个基本类型的属性的实际值

需要注意的是,position,age 是 protected 保护域,salary,hobby 是 private 私有域,Java的安全机制只允许查看任意对象有哪些域,但是不允许读取它们的值

程序中是通过 AccessibleObject.setAccessible(fields, true) 将域设置为了可访问,取消了Java的执行访问检查,因此可以访问,如果不加会报异常 IllegalAccessException

小结一下,我们通过 setAccessible(true) 绕过了Java执行访问检查,因此能够访问私有域,通过 Field.getType() 获得了属性的声明类型,通过了 Field.get(Object obj) 获得了该域属性的实际值,还有一个没用上的 Field.set(Object obj, Object value) 设置域属性的实际值

调用任意方法
上面我们已经获取了类的构造器,方法,域,查看和设置了域的实际值,那么是不是还可以在调用对象的方法呢?嘿嘿,又猜对了,机智,类的方法信息,获取都获取了,当然就要调用一下,来都来了

上面查看Field的实际值是通过 Field 类的 get() 方法,与之类似,Method 调用方法是通过 Method 类的 invoke 方法

调用任意方法相关的API
Method getMethod(String name, Class<?>... parameterTypes) 获取指定的 Method,参数 name 为要获取的方法名,parameterTypes 为指定方法的参数的 Class,由于可能存在多个同名的重载方法,所以只有提供正确的 parameterTypes 才能准确的获取到指定的 Method

Object invoke(Object obj, Object... args) 执行方法,第一个参数执行该方法的对象,如果是static修饰的类方法,则传null即可;后面是传给该方法执行的具体的参数值


调用任意方法程序

public class MethodTableTest {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Employee employee = new Employee("小明", "18", "写代码", 1, "Java攻城狮", 100000);
        Method sayHello = employee.getClass().getMethod("sayHello");
        System.out.println(sayHello);   // 打印 sayHello 的方法信息
        sayHello.invoke(employee);      // 让 employee 执行 sayHello 方法

        double x = 3.0;
        Method square = MethodTableTest.class.getMethod("square", double.class);  // 获取 MethodTableTest 的square方法
        double y1 = (double) square.invoke(null, x);    // 调用类方法 square 求平方,方法参数 x 
        System.out.printf("square    %-10.4f -> %10.4f%n", x, y1);

        Method sqrt = Math.class.getMethod("sqrt", double.class);   // 获取 Math 的 sqrt 方法
        double y2 = (double) sqrt.invoke(null, x);  // 调用类方法 sqrt 求根,方法参数 x 
        System.out.printf("sqrt      %-10.4f -> %10.4f%n", x, y2);
    }

    // static静态方法 计算乘方
    public static double square(double x) {
        return x * x;
    }
}

 

执行结果

public void reflect.Employee.sayHello()
Hello, 我是 小明, 今年 18 岁, 爱好是写代码, 我目前的工作是Java攻城狮, 月入100000元

square    3.0000     ->     9.0000
sqrt      3.0000     ->     1.7321

反射的优缺点


此段引用自 CyC2018/CS-Notes

反射的优点:


可扩展性 :应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类。

类浏览器和可视化开发环境 :一个类浏览器需要可以枚举类的成员。可视化开发环境(如 IDE)可以从利用反射中可用的类型信息中受益,以帮助程序员编写正确的代码。

调试器和测试工具 : 调试器需要能够检查一个类里的私有成员。测试工具可以利用反射来自动地调用类里定义的可被发现的 API 定义,以确保一组测试中有较高的代码覆盖率。

反射的缺点:


尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。在我们使用反射技术时,下面几条内容应该牢记于心。

性能开销 :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。

安全限制 :使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。

内部暴露 :由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值