Java反射

1.什么是反射

  • 反射就是运行时类信息(包含构造器、属性、方法)。
  • new是静态(编译时)加载类,而反射是动态加载类(运行时),

2.获取Class对象

 获取Class对象有三种方法,第一种方法最常用;

  • 通过Class.forName获取;
Class clazz = Class.forName("java.lang.String");
  • 通过类获取;
Class clazz = String.class;
  • 通过对象获取;
String str = new String("Hello");
Class clazz = str.getClass();

3.获取构造器并创建对象

3.1 获取构造器

  • 获取所有构造器
public Constructor[] getConstructors();//获取所有公有构造器
public Constructor[] getDeclaredConstructors();//获取所有的构造器
  • 获取某个构造器
//获取某个指定参数类型的公有构造函数
public Constructor getConstructor(Class... parameterTypes);
//获取某个指定参数类型的构造函数
public Constructor getDeclaredConstructor(Class... parameterTypes);

3.2 创建对象

 创建对象有下面两种方式,第二种更常用;

  • 通过 Class 对象的 newInstance() 方法:
//clazz.newInstance()方式使用的是无参构造方法;
Class clazz = Student.class;
Student student = (Student)clazz.newInstance();
  • 通过 Constructor 对象的 newInstance() 方法;
//constructor既可以使用无参构造方法也可以使用有参构造方法
Class clazz = Class.forName("com.example.reflect.Student");
Constructor con1 = clazz.getConstructor();
Constructor con2=clazz.getDeclaredConstructor(String.class,String.class,int.class);
Object object1 = con1.newInstance();
Object object2 = con2.newInstance("2017101","David",22);

4.获取属性并赋值

  • 获取所有属性
public Field[] getFields();//获取所有公有属性
public Field[] getDeclaredFields();//获取所有显示声明的属性,不包含继承属性
  • 获取某个属性
//获取某个指定属性名的公有属性
public Field getField(String fieldName);
//获取某个指定属性名的显示声明属性
public Field getDeclaredField(String fieldName);
  • 给属性赋值
//给指定对象的指定属性设置值
Field - > public void set(Object obj,Object value);
//获取指定对象的指定属性的值
Field - > public Object get(Object obj)

5.获取方法并调用

  • 获取所有方法
public Method[] getMethods();//获取所有公有方法
public Method[] getDeclaredMethods();//获取所有显示声明的方法,不包含继承方法
  • 获取某个方法
//获取某个指定公有方法,methodName-方法名,parameterTypes变长参数类型
public Method getMethod(String methodName,Class<?>... parameterTypes)//获取某个指定显示声明的方法
public Method getDeclaredMethod(String name,Class<?>... parameterTypes);
  • 调用方法
//obj-对象,args-实参
Method -> public Object invoke(Object obj,Object... args);

6.获取main方法

  • 获取Student类的main方法,不要与当前main方法混淆,与获取指定公有方法实现类似。详见下面的Demo。

  • 值得注意的是,调用私有成员要通过setAccessible(true)方法强制给权限。

7.反射Demo

 以下代码实现了以下需求:

  • 获取Class对象;
  • 获取构造器并创建对象;
  • 获取属性并赋值;
  • 获取方法并调用;
  • 获取main方法;

Student类

package com.example.reflect;
public class Student {
    public String stuNum;
    private String name;
    private int age;

    public Student() {
        System.out.println("这是一个无参的构造方法!");
    }

    public Student(String stuNum, String name, int age) {
        this.stuNum = stuNum;
        this.name = name;
        this.age = age;
        System.out.println("这是一个带参的构造方法!");
    }

    public String getName() {
        System.out.println("执行了getName方法!");
        return name;
    }

    private void setAge(int age){
        System.out.println("执行了setAge方法!");
        this.age=age;
    }

    public void setName(String name){
        System.out.println("执行了setName方法!");
        this.name=name;
    }

    public void print(){
        System.out.println(this.stuNum+" "+this.name+" "+this.age);
    }

    public static void main(String[] args) {
        System.out.println("这是Student类的主函数!");
    }
}

Test类

//注意:调用私有成员前必须强制给权限
public class Test {
    public static void main(String[] args) {
        try {
            //获取Class对象
            Class clazz = Class.forName("com.example.reflect.Student");

            //获取所有公有构造器
            System.out.println("------------------->所有公有构造器:");
            Constructor[] cons = clazz.getConstructors();
            for(Constructor con : cons){
                System.out.println(con);
            }
            //获取所有构造器
            System.out.println("------------------->所有构造器:");
            cons = clazz.getDeclaredConstructors();
            for(Constructor con : cons){
                System.out.println(con);
            }
            //获取无参构造器
            System.out.println("------------------->无参构造器:");
            Constructor con1 = clazz.getConstructor();
            System.out.println(con1);
            //获取无参构造器
            System.out.println("------------------->带参构造器:");
            Constructor con2=clazz.getDeclaredConstructor(String.class,String.class,int.class);
            System.out.println(con2);

            //创建对象
            Object object1 = con1.newInstance();
            Object object2 = con2.newInstance("2017101","David",22);

            //获取所有公有属性
            System.out.println("------------------->所有公有属性:");
            Field [] fields=clazz.getFields();
            for (Field field :fields){
                System.out.println(field);
            }
            //获取所有属性
            System.out.println("------------------->所有属性:");
            fields=clazz.getDeclaredFields();
            for (Field field :fields){
                field.setAccessible(true);
                System.out.println(field);
            }
            //获取指定公有属性
            System.out.println("------------------->指定公有属性:");
            Field field1=clazz.getField("stuNum");
            System.out.println(field1.getName());
            field1.set(object1,"2017102");//设置属性的值
            //获取指定属性
            System.out.println("------------------->指定私有属性:");
            Field field2=clazz.getDeclaredField("name");
            field2.setAccessible(true);
            System.out.println(field2.getName());

            //获取所有公有方法,包含从父类(包括Object类)继承的方法
            System.out.println("------------------->所有公有方法:");
            Method [] methods=clazz.getMethods();
            for(Method method :methods){
                System.out.println(method);
            }
            //获取所有显示声明方法,不包括继承的方法
            System.out.println("------------------->所有方法:");
            methods=clazz.getDeclaredMethods();
            for(Method method :methods){
                method.setAccessible(true);//强制给访问权限
                System.out.println(method);
            }
            //获取指定公有方法
            System.out.println("------------------->指定公有方法:");
            Method method1=clazz.getMethod("setName",String.class);
            System.out.println(method1.getName());
            method1.invoke(object1,"Mary");//调用方法,相当于object1.setName("Mary");
            //获取指定方法
            System.out.println("------------------->指定方法:");
            Method method2=clazz.getDeclaredMethod("setAge",int.class);
            System.out.println(method2.getName());
            method2.setAccessible(true);//强制给访问权限,在调用私有方法前一定要加上
            method2.invoke(object1,21);
            //打印对象信息
            Method method3=clazz.getDeclaredMethod("print");
            method3.invoke(object1);
            method3.invoke(object2);

            //获取Student类的main方法,不要与当前main方法混淆,与获取指定公有方法实现类似
            Method main = clazz.getMethod("main", String[].class);
            //因为是静态方法,所以调用对象为空;main方法一个String数组参数,实参为new String[]{"a","b","c"},
            // 会有歧义(可能是一个数组参数,也可能是多个字符串参数),所以强转型消除歧义
            main.invoke(null, (Object) new String[]{"a","b","c"});//一个参数
//            main.invoke(null, (Object[]) new String[]{"a","b","c"});//多个参数

        } catch (ClassNotFoundException
                | NoSuchMethodException
                | IllegalAccessException | InstantiationException | InvocationTargetException
                | NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

------------------->所有公有构造器:
public com.example.reflect.Student()
public com.example.reflect.Student(java.lang.String,java.lang.String,int)
------------------->所有构造器:
public com.example.reflect.Student()
public com.example.reflect.Student(java.lang.String,java.lang.String,int)
------------------->无参构造器:
public com.example.reflect.Student()
------------------->带参构造器:
public com.example.reflect.Student(java.lang.String,java.lang.String,int)
这是一个无参的构造方法!
这是一个带参的构造方法!
------------------->所有公有属性:
public java.lang.String com.example.reflect.Student.stuNum
------------------->所有属性:
public java.lang.String com.example.reflect.Student.stuNum
private java.lang.String com.example.reflect.Student.name
private int com.example.reflect.Student.age
------------------->指定公有属性:
stuNum
------------------->指定私有属性:
name
------------------->所有公有方法:
public static void com.example.reflect.Student.main(java.lang.String[])
public java.lang.String com.example.reflect.Student.getName()
public void com.example.reflect.Student.setName(java.lang.String)
public void com.example.reflect.Student.print()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
------------------->所有方法:
public static void com.example.reflect.Student.main(java.lang.String[])
public java.lang.String com.example.reflect.Student.getName()
public void com.example.reflect.Student.setName(java.lang.String)
public void com.example.reflect.Student.print()
private void com.example.reflect.Student.setAge(int)
------------------->指定公有方法:
setName
执行了setName方法!
------------------->指定方法:
setAge
执行了setAge方法!
------------------->打印对象信息:
2017102 Mary 21
2017101 David 22
------------------->获取Student类的main方法:
这是Student类的主函数!

8.反射的应用

  • 通过反射运行配置文件内容,比如说JDBC;
  • 通过反射逃避泛型类型安全检查;
  • 通过反射提取注解;
  • 实现ORM框架,比如说Litepal;
  • 实现RPC框架;
  • 拷贝属性值;

8.1 通过反射运行配置文件内容

 现有以下需求:

  • 1.当系统升级时,无需修改源码,只需将修改内容作为一个新类和修改配置文件即可。
  • 2.开发一个框架,该框架中有大量用户类操作(实例化或方法操作)的代码;如何实现(也必须实现)不修改框架源码前提下可以随意向使用该框架的模块中添加用户类?
  • 其实这两个需求是同一个意思:不修改框架源码,通过修改配置文件,实现向使用了框架的模块中任意添加用户类。
//一个用户类
public class Student1 {
    public void show1(){
        System.out.println("运行了show1方法");
    }
}
<!--配置文件,内容是key value形式-->
className = com.example.reflect.Student1
methodName = show1
/**
 * 通过反射运行配置文件内容,不修改框架源码
 * 把这个类当成是一个框架
 */
public class TestRunPropertiesFile {
    public static void main(String[] args) throws Exception {
        //通过反射获取Class对象
        Class stuClass = Class.forName(getValue("className"));
        //2获取show()方法
        Method m = stuClass.getMethod(getValue("methodName"));
        //3.调用show()方法
        m.invoke(stuClass.getConstructor().newInstance());
    }

    //此方法接收一个key,在配置文件中获取相应的value
    public static String getValue(String key) throws IOException {
        Properties pro = new Properties();//获取配置文件的对象
        FileReader in = new FileReader("pro.txt");//获取输入流,配置文件必须放到工程根目录下
        pro.load(in);//将流加载到配置文件对象中
        in.close();
        return pro.getProperty(key);//返回根据key获取的value值
    }
}
//运行结果:
运行了show1方法
//新增一个用户类
public class Student2 {
    public void show2(){
        System.out.println("运行了show2方法");
    }
}

<!--修改配置文件-->
className = com.example.reflect.Student2
methodName = show2
//再次运行结果
运行了show2方法

通过反射运行配置文件内容,并且修改配置文件,实现了在不修改框架源码的前提下向使用了框架的模块中任意添加用户类。

  这也就体现了反射的优点:提高了程序的灵活性和扩展性,降低了耦合度,避免将代码写死.

9.反射的优点和缺点

9.1 反射的优点

  • 提高了程序的灵活性和扩展性,降低了耦合度,避免将代码写死;

9.2 反射的缺点

  • :反射效率比较低,所以尽量少用反射;反射在底层框架中用的比较多,业务层面的开发过程中尽量少用。
  • 破坏了封装性:field.setAccessible(true);强制给访问权限使得私有成员暴露给外部,破坏了类的封装性;
  • 模糊了程序逻辑:程序员希望在源代码中看到程序的逻辑,反射绕过了源代码,可能会带来维护问题。反射代码比相应的直接代码更复杂。

9.3 反射为什么慢?

 至于反射为什么慢,仅作了解:

  • Class.forName会调用本地方法,很慢;
  • Class.getMethod该方法里有个轮询,会去遍历该类的所有公有方法,耗时;当然,我们应该避免使用getMethods()后自己去写这个轮询代码,这样会更慢;
  • method.invoke(null,8)(1)这个方法的第二个参数是一个可变长Object参数,可变长Object参数以Object数组的方式传参,所以编译器会在调用invoke时根据8生成一个Object数组作为实参传入,这个过程有比较多的时间消耗;(2)Object数组只能接受引用类型的元素,所以自动装箱和拆箱也是耗时的;(3)方法内联耗时;

 至于如何提高反射效率,仅作了解:

  • 缓存

  Class.forName是真的慢,要缓存得到的Class对象;

Map<String,Object> cache= new HashMap<>();
Class cachedClass;
// 1. 没有缓存
void createInstance(String className){
    return Class.forName(className).newInstance();
}

// 2. 缓存forName的结果
void createInstance(String className){
    cachedClass = cache.get(className);
    if (cachedClass == null){
        cachedClass = Class.forName(className);
        cache.set(className, cachedClass);
    }
    return cachedClass.newInstance();
}
  • 善用反射API

  不管是Jdk最新反射API,还是第三方高性能反射API;比如尽量不要getMethods()后自己实现遍历筛选,而直接用getMethod(methodName)来根据方法名获取方法;

10 反射常见面试题

(1) 什么是反射,实例化和new的区别;
  • 反射就是运行时类信息(包含构造器、属性、方法);

  • new是静态(编译时)加载类,而反射是动态加载类(运行时加载类),

(2) 反射的应用场景?
  • 通过反射运行配置文件内容,比如说JDBC;
  • 通过反射逃避泛型类型安全检查;
  • 通过反射提取注解;
  • 实现ORM框架,比如说Litepal;
  • 实现RPC框架;
  • 拷贝属性值;
(3) 反射有什么作用(优点)?
  • 提高了程序的灵活性和扩展性,降低了耦合度,避免将代码写死;

 解释:通过反射运行配置文件内容,并且修改配置文件,实现了在不修改框架源码的前提下向使用了框架的模块中任意添加用户类。

(4)反射的缺点
  • 破坏了封装性
  • 模糊了程序逻辑
(5) 介绍下泛型的API;
  • 获取获取Class对象;
Class clazz = Class.forName("java.lang.String");
  • 获取构造器Constructor并创建对象;
Constructor constructor=clazz.getConstructor(String.class,String.class,int.class);
Object obj = constructor.newInstance("2017101","David",22);
  • 获取属性Field并赋值;
Field field=clazz.getDeclaredField("name");
field.setAccessible(true);//给权限
field.set(obj,"Marry");//设置属性值
field.get(obj);//获取属性值
  • 获取方法Method并调用;
//Method method=clazz.getMethod("setName",String.class);
//method.invoke(obj,"Tom");
clazz.getMethod("setName",String.class).invoke(obj,"Tom");
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值