Java基础之反射

Java反射是指运行时获取类信息,进而在运行时动态构造对象、调用对象方法及修改对象属性的机制。百度百科的定义:“JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

一、反射的用途

Java的反射机制可以做3件事:运行时创建对象、运行时调用方法、运行时读写属性。进而实现以下功能:
调用一些私有方法,实现黑科技。比如双卡短信发送、设置状态栏颜色、自动挂电话等。

实现序列化与反序列化,比如PO的ORM,Json解析等。

实现跨平台兼容,比如JDK中的SocketImpl的实现。

通过xml或注解,实现依赖注入(DI),注解处理,动态代理,单元测试等功能。比如Retrofit、Spring或者Dagger。

二、Java反射的优缺点

优点: 

(1)能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。 
(2)与Java动态编译相结合,可以实现无比强大的功能 

缺点: 
(1)使用反射的性能较低 
(2)使用反射相对来说不安全 
(3)破坏了类的封装性,可以通过反射获取这个类的私有方法和属性 

三、Java反射的用法

主要反射API,用来生成在当前JAVA虚拟机中的类、接口或者对象的信息。

●Class类:反射的核心类,可以获取类的属性,方法等内容信息。

●Field类:Java.lang.reflect.表示类的属性,可以获取和设置类的中属性值。

●Method类:Java.lang.reflect。表示类的方法,它可以用来获取类中方法的信息或者执行方法

Construcor类:Java.lang.reflect。表示类的构造方法。

使用步骤:

●获得想操作类的Java.lang.Class对象
●调用Class的方法
●使用反射API来操作这些信息

Class类没有构造方法,获取其对象的三种方法:

对象.getClass()

类.class

Class.forName();其中,forName( )方法需要重点掌握,因为它可以在类不确定的情况下实例化Class,更具灵活性。

class Test {   

}   

public class Demo {   
    public static void main(String[] args) {   
        //方式一:   
        Test t = new Test();   
        Class<? extends Test> c1 = t.getClass();   
        System.out.println(c1);   

        //方式二:   
        //为了避免特殊性,这里不用Test类,而用java库中的String类   
        Class<String> c2 = String.class;   
        System.out.println(c2);   

        //方式三:   
        //forName()方法会抛出异常   
        Class<?> c3 = null;   
        try {   
            c3 = Class.forName("Test");   
        } catch (ClassNotFoundException e) {   
            e.printStackTrace();   
        }   
        System.out.println(c3);   
    }   
}

常用反射api示例:

public class Demo {   
    //下面的几个方法抛出来的异常太多,为了代码的紧凑性,这里就直接抛给虚拟机了   
    public static void main(String[] args) throws Exception {   
        Class<?> c = null;   
        try {   
            c = Class.forName("java.lang.String");   
        } catch (ClassNotFoundException e) {   
            e.printStackTrace();   
        }   
        char[] ch = {'h','e','l','l','o'};   
        String s = null;   
        //获得Class类对象的有参构造方法,括号里面参数的写法是:类型.class   
        Constructor<?> con = c.getConstructor(char[].class);   
        //用此构造方法构造一个新的字符串对象,参数为一个char数组   
        s = (String) con.newInstance(ch);   
        System.out.println("构造的字符串:" + s);   

//获得Class类对象的有参构造方法,括号里面参数的写法是:类型.class   
        Constructor<?> con = c.getConstructor(char[].class);   
        //用此构造方法构造一个新的字符串对象,参数为一个char数组   
        s = (String) con.newInstance(ch);   
        System.out.println("构造的字符串:" + s);   

//这里的getConstructors()方法返回的是一个Constructor数组   
        Constructor<?>[] cons = c.getConstructors();   
        System.out.println(Arrays.toString(cons));  
 
        Class<?>[] in = c.getInterfaces();   
        System.out.println(Arrays.toString(in)); 
//只有一个父类
        Class<?> su = c.getSuperclass();   
        System.out.println(su); 
//获取所有方法
    Method[] m = c.getMethods();    
        for (int i = 0; i < m.length; i++) {   
            System.out.println(m[i]);   
        }   
//获取所有属性
    Field[] f = c.getDeclaredFields();   
        for (int i = 0; i < f.length; i++) {   
            System.out.println(f[i]);   
        }   

    }   
}    
class Person {   
    private String name;   

    public Person(String name) {   
        this.name = name;   
    }   

    public String toString() {   
        return "姓名: " + this.name;   
    }   
}   

public class Demo {   
    public static void main(String[] args) throws Exception {   
        Person p = new Person("王二狗");   
        System.out.println(p);   
        Class<?> c = p.getClass();   

        //定义要修改的属性   
        Field f = c.getDeclaredField("name");   
        f.setAccessible(true);   
        //修改属性,传入要设置的对象和值   
        f.set(p, "张二蛋");   
        System.out.println(p);   

   //getMethod()方法需要传入方法名,和参数类型   
        Method m1 = c.getMethod("print", int.class);   
        //invoke()表示调用的意思,需要传入对象和参数   
        m1.invoke(p, 10);   

        Method m2 = c.getMethod("say", String.class);   
        //这里的null表示不由对象调用,也就是静态方法   
        m2.invoke(null, "你妹");   
    }   
}

四、相关问题:java中如何让一个方法不能被反射调用?

AccessibleObject的setAccessible方法可以使一个private方法(字段)能够被invoke方法调用;同时这种调用存在特例,如Class类的Constructord对象。那么:
1.我可以写一个方法(字段),使它被setAccessible(true)时抛出SecurityException吗?
2.存在如同Class类的Constructord对象这样可以抛出SecurityException的方法吗?
3.如果问题“1”的答案为否,那么Class类的Constructord对象是以何种方式成为特例的呢?


1.我可以写一个方法(字段),使它被setAccessible(true)时抛出SecurityException吗?
我的理解是“不能”,除非你改写jdk源代码。即使你使用了java的安全管理器,也只能管理自己代码的安全策略,当你的代码打成jar包被别人使用时,别人用反射可以访问你的一切,除了你说的类的构造函数。如果你使用了如同stackoverflow上那人写的safeMethod方法,会有一些效果。但那种情况不是阻止别人访问你的method,而是使用反射访问你的method时,该method会主动抛出异常。要想让setAccessible(true)时抛异常,不可能。

2.存在如同Class类的Constructord对象这样可以抛出SecurityException的方法吗?
存在。例如public Method getDeclaredMethod(String name, Class<?>... parameterTypes)方法,只要违反了安全策略,都可以抛出SecurityException方法。但这依然不能阻止别人使用反射访问你的资源。

3.如果问题“1”的答案为否,那么Class类的Constructord对象是以何种方式成为特例的呢?
不知道。猜测一下,是否java内置的安全策略就把Class类的Constructor方法设为不可使用反射访问的呢?反正我试了试,直接就抛出了java.lang.NoSuchMethodException异常,还轮不到抛出SecurityException异常。

链接:https://www.zhihu.com/question/47896687/answer/108160444

posted on 2017-08-21 22:17  时间朋友  阅读(637)  评论(0编辑  收藏  举报

导航