Java反射入门

一直听说反射,但在实际开发过程中却没怎么接触过,最近在学习代理模式时,发现动态代理就是用的反射,于是学习一下。

一、反射定义

Java反射是指在程序运行状态中,获取一个类所有属性和方法,调用一个对象的任意一个属性和方法;这种动态的获取信息和动态调用对象的功能就是Java的反射机制。也就是在程序运行时,通过class文件对象来使用该文件的变量、构造方法以及成员方法。

JavaAPI提供了以下几个类,一些常用的方法通可以通过这几个类获取:

java.lang.Class;相当于.class文件对象
java.lang.reflect.Constructor;相当于类中构造方法对象
java.lang.reflect.Field;相当于类中成员变量对象
java.lang.reflect.Method;相当于类中成员方法对象

二、利用反射获取信息

使用反射,必须先得到类的class文件对象,Java中获取class文件对象有三种方式:

(1)Student s = new Student();Class c = s.getClass();
(2)Class c = Student.class;
(3)Class c = Class.forName("cn.reflect.demo.Student");//这里必须使用全类名

常用的是2、3两种方式;下面是一个简单的Student类,供测试使用。

package proxy.reflect.demo;
public class Student {
    private String name ;
    Integer age ;
    public Double money;

    public Student() {}
    Student(String name){
        this.name = name;
    }
    private Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public static void learn(String name,String address){
        System.out.println(name+"在"+address+"学习");
    }
    private void learn(){
        System.out.println("学生正在学习");
    }
    public String learn(String name){
        return name+"正在学习";
    }

    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }
}

创建对象实例

Object obj = c.newInstance();//调用的是Student的public修饰的无参构造方法,相当于Object obj = new Student();

有了对象之后就可以获取想要的信息了。

2.1获取构造方法

/**
    Constructor[] getConstructors();只能获取public构造方法
    Constructor[] getDeclaredConstructors();获取所有构造方法(包括private修饰的)
    Constructor getConstructor(Class<?>... params);根据传递参数获取public修饰的构造方法对象
    Constructor getDeclaredConstructor(Class<?>... params);根据传递参数获取构造方法对象(包括private修饰的)
*/
public static void main(String[] args) throws Exception{
    //获取字节码文件对象
    Class c = Class.forName("proxy.reflect.demo.Student");

    //获取所有构造方法
    Constructor[] constructors = c.getDeclaredConstructors();
    for (Constructor constructor : constructors) {
        System.out.println(constructor);

    //带参数的构造方法,传递的参数类型对应构造方法的参数
    Constructor constructor = c.getDeclaredConstructor(String.class,Integer.class);
    //取消Java访问检查,只有设置为true后才可以调用private修饰的构造方法
    constructor.setAccessible(true);

    //相当于Object obj = new Student("小明",24);
    Object obj = constructor.newInstance("小明",24);

    //使用System的打印流时自动调用obj对象的toString()方法
    System.out.println(obj);
}

打印结果

private proxy.reflect.demo.Student(java.lang.String,java.lang.Integer)
proxy.reflect.demo.Student(java.lang.String)
public proxy.reflect.demo.Student()
Student [name=小明, age=24]

可以看到Student的所有构造方法都可以取到,还可以调用private修饰的构造方法;

2.2获取成员变量

获取成员变量和获取构造函数一样。首先获取到Constructor对象,然后使用Constructor创建新对象实例,然后调用get(),set()方法获取和修改与该Field对象关联的字段。

/**
    Field[] getFields();得到所有带public修饰的成员方法对象数组
    Field[] getDeclaredFields();得到所有带修饰的成员方法对象数组
    Field getField(String name);获取对应name的Field对象,必须是public修饰的
    Field getDeclaredField(String name);获取对应name的Field对象
    field.get(Object obj)返回指定对象obj在此Field对象表示的值
    field.set(Object obj, Object value)将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
*/
public static void main(String[] args) throws Exception{
    //获取字节码文件对象
    Class c = Class.forName("proxy.reflect.demo.Student");
    //创建对象实例,默认调用的是无参构造,相当于Object ojb = new Student();
    Object obj = c.newInstance();
    //获取属性为name成员变量的Field对象
    Field field = c.getDeclaredField("name");
    //取消Java访问检查
    field.setAccessible(true);
    //为obj对象的field属性赋值"小明",相当于ojb.name="小明";
    field.set(obj, "小明");
    //打印结果:小明
    System.out.println(field.get(obj)); 
}

2.3获取成员方法

使用Constructor创建新对象,然后用invoke()方法调用与Method对象关联的而方法。成员方法的获取和其他两个属性获取还是有一点区别的,看代码。

/**
    Method[] c.getMethods();得到本类和父类所有public修饰的方法Method对象数组
    Method[] c.getDeclaredMethods();得到所有方法Method对象数组
    Method getMethod(String name, Class<?>... params) 得到某一方法method对象(只能是public修饰的),name是方法名,params是参数.class
    Method getDeclaredMethod(String name, Class<?>... params) 得到某一方法method对象,name是方法名,params是参数.class
    Object invoke(Object obj, Object... args)调用obj对象的method方法传递参数是args
*/
public static void main(String[] args) throws Exception {
    Class c = Class.forName("proxy.reflect.demo.Student");
    Object object = c.newInstance();

    //可以获取本类和父类的public方法
    Method[] methods = c.getMethods();
    //只获取自己的所有的方法
    //Method[] methods = c.getDeclaredMethods();
    for (Method m : methods) {
        System.out.println(m);
    }

    //获取方法名为learn并且没有形参的方法对象
    Method m = c.getDeclaredMethod("learn"); 
    m.setAccessible(true);
    //调用object对象的与m对象关联方法
    m.invoke(object); 

    //拿到方法名为learn参数是String类型的方法对象
    Method m2 = c.getMethod("learn",String.class);
    //调用object对象的方法并给参数赋值为"小明",该方法有返回值
    Object result = m2.invoke(object, "小明"); 

    Method m3 = c.getMethod("learn",String.class,String.class);
    //调用静态方法,因为静态方法是随着类的加载而加载的,无需使用对象调用,所有这里传null,当然传对象也没有问题
    m3.invoke(null, "-----\n小明","教室");
}

2.4一些其他的常用方法

以下几种方法也是比较常用的,不再一一描述,可以通过JavaAPI详细了解,它才是我们最好的老师!

方法名含义
Class方法
getResourceAsStream(String name)查找具有给定名称的资源
getSimpleName()返回源代码中给出的底层类的简称
getClassLoader()返回该类的类加载器
getSuperclass()获取一个类的父类
Field方法
getType()获取对象表示字段的声明类型
getName()获取对象表示字段的名称
Method方法
getName()获取对象表示方法的名称

三、一些应用

反射的应用是非常广泛的,合理的利用反射可以很大程度上节省我们的开发时间。反射是在运行时期获取类信息的,我们利用这一特性,可以做很多事情。

3.1加载配置文件

在软件开发过程中,使用配置文件是必不可少的,通常把一些多变的数据写在配置文件,在修改时候只需需修改配置文件,增加了代码的可维护性。在学习jdbc的时候,将访问数据库的一些参数放到配置文件中,比如数据库链接、用户名、密码等,在更换数据库或者修改密码之后,只需修改配置文件即可。来看一下反射如何加载config.properties文件的。

dbName=jdbc:mysql://localhost:3306/reflect
username=jieke
password=rose

使用反射获取数据

public static void main(String[] args) throws Exception {
    //加载文件中的键值对数据
    Properties p = new Properties();
    /**
        JavaAPI:查找与给定类相关的资源的规则是通过定义类的 class loader 实现的,此方法委托此对象的类加载器,
         如果此对象通过引导类加载器加载,则此方法将委托给 ClassLoader.getSystemResourceAsStream(String)。
        通过以上说明此处可以填写任意类,因为这些类都会有由加载器加载,最后都是调用类加载器的方法;
        所以此处写Object.class、String.class、ReflectProperty.class等等都时可以的。
        不可以使用this(本类对象)、super(父类对象),在jvm加载一个类时,首先为static属性分配内存空间,而对象是在new时候才会被加载的
    */
    InputStream is = ReflectProperty.class.getResourceAsStream("/config.properties");
    p.load(is);
    is.close();
    System.out.println("数据库链接:"+p.getProperty("dbName")+"\n用户名:"+p.getProperty("username")+"\n密码:"+p.getProperty("password"));
}

打印结果

数据库链接:jdbc:mysql://localhost:3306/reflect
用户名:jieke
密码:rose

3.2泛型”擦除”

泛型”擦除”就是越过泛型检查,泛型是在程序编译期检查程序的,通过反射获取到程序运行期的class文件对象进行操作,即可越过泛型检查。不过该知识点在开发过程中基本上不可能用到,只是在一些面试题上会出现,简单了解即可。

public static void main(String[] args) {
    //泛型简写,Java7之后出现的
    List<Integer> list = new ArrayList<>();
    Class c = list.getClass();
    try {
        //通过class对象得到方法名为add参数类型是Object的方法对象
        //通过查询源码发现List的add()方法传递的就是Object
        Method method = c.getMethod("add",Object.class);
        method.invoke(list, "小明");
        System.out.println(list);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

四、总结

Java反射的灵活性已经体验到了,很多框架都有用到反射,Spring的AOP,代码自动生成工具、mybatis等等。不过在我们平时开发过程中,是很少使用反射的,它会使我们的程序性能变低,因为使用反射调用类的构造方法、变量、方法的效率远低于直接代码。总之,有利有弊,开发过程中应尽量避开反射,假如想自己写框架,那么反射是必不可少的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值