java学习-反射机制

出自:https://blog.csdn.net/sunhuaqiang1/article/details/45115651

一、反射的基础---Class

Class是所有java类的一个总称,就好像各式各样的人都可以用Person来称呼,每一个类被加载之后都会在内存中生存一个Class对象,这个对象我们通常称之为字节码,而我们通过调用一个类创造的对象其实都是字节码搞出来的,一个类只会产生一份字节码。

 那么我们怎么获得一个类的Class呢?有三种方式:

    1.调用某个类的class属性  

    2.使用ClassforName()静态方法  

    3.调用某个对象的getClass()方法。

 

下面我们通过一个实例来展示下上面两点:

ClassDemo1.java

 

package com.lyl.exercise;  

  

public class ClassDemo1 {  

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

        String str="iteye";  

        Class cl1=String.class;  

        Class cl2=str.getClass();  

        Class cl3=Class.forName("java.lang.String");  

        System.out.println("str对象与String是同一份字节码吗?"+(cl1==cl2));  

        System.out.println("通过Class.forName()获得的字节码与String.class一样吗?"+(cl1==cl3));  

    }  

}  

 

通过查看JDK文档,我们可以发现,Class有许多方法,通过这些方法我们可以得到java类的相关信息,Constructor,Method,Field等,具体的大家可以参考JDK文档。


二、反射的应用

那么什么是反射呢?曾经有人说过,反射就是把java类中的各种成分映射成相应的java类。为什么呢?从上一个讲解中大家是否发现,通过Class我们可以解析出一个java类的各种成分,他们返回的也是一个类,所以这句话还是很有道理的。

下面让我们来看看反射的一些应用:

    1.使用反射生成对象

     通过反射来生成对象有两种方式:

        a.使用Class对象的newInstance()方法

        b.使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建Class对应类的实例。

 

实例: ClassDemo3.java

 

package com.lyl.exercise;  

  

import java.lang.reflect.Constructor;  

  

public class ClassDemo3 {  

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

        //使用Class对象的newInstance()方法  

        String str=(String)Class.forName("java.lang.String").newInstance();  

        //使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建Class对应类的实例。  

        Constructor constructor=String.class.getConstructor(StringBuffer.class);  

        String str2=(String)constructor.newInstance(new StringBuffer("abc"));  

    }  

}  

 

     2.成员变量的反射

 

     接上实例大家看看:

 

ReflectDemo1.java

 

package com.lyl.exercise;  

  

import java.lang.reflect.Field;  

  

public class ReflectDemo1 {  

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

        ReflectHelper rh=new ReflectHelper("iteye", "javaeye");  

        Field fieldb=rh.getClass().getField("b");  

        //fieldb只是类的字段,不是对象的,所以要想获得对象上的值,要使用get()方法  

        System.out.println(fieldb.get(rh));  

        //如果我们使用上面方法来访问a的值呢?抛出java.lang.NoSuchFieldException  

        /*Field fielda=rh.getClass().getField("a"); 

        System.out.println(fieldb.get(rh)); 

        因为a是私有成员变量 

        */  

        //我们可以通过暴力反射来获取它的值  

        Field fielda=rh.getClass().getDeclaredField("a");  

        //设置为可以访问  

        fielda.setAccessible(true);  

        System.out.println(fielda.get(rh));  

    }  

}  

 

ReflectHelper.java

 

package com.lyl.exercise;  

 

public class ReflectHelper {  

    private String a;  

    public String b;  

    public ReflectHelper(String a,String b){  

        this.a=a;  

        this.b=b;  

    }  

}  

 

如果将上面的搞懂,那么我们不妨来做一道经典的题目:将一个类中所有String类型的成员变量,其中含有的‘a’替换为‘b’。

这个题目很明显通过反射可以轻易的完成,大家不妨来看看。

 

package com.lyl.exercise;  

  

import java.lang.reflect.Field;  

  

public class ReflectDemo2 {  

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

        ReflectHelper rh=new ReflectHelper("abc", "basketball");  

        changeStringValue(rh);  

        System.out.println(rh);  

    }  

  

    private static void changeStringValue(Object object) throws Exception{  

        Field[] fields=object.getClass().getFields();  

        for(Field field:fields){  

            if(field.getType()==String.class){  

                String oldValue=(String)field.get(object);  

                String newValue=oldValue.replace('a','b');  

                //objectString类型的变量替换为newValue  

                field.set(object, newValue);  

            }  

        }  

    }  

}  

 

这样就搞定了,是不是很简单?

 

一、ClassLoader初步

类加载器负责加载所有的类,系统为所有被载入内存中的类生成一个java.lang.Class实例。一旦一个类被载入到JVM中,同一个类就不会再次被载入了,这是针对同一个加载器,不同的加载器还是可以加载同一个类的,不同加载器加载同一个类在JVM中是不同的。因为在JVM中用类的全限定类名加类加载器作为其唯一标识。

    在JVM启动时,会形成有三个类加载器组成的初始类加载器层次结构:

    -->Bootstrap ClassLoader:根类加载器

    -->Extension ClassLoader:扩展类加载器

    -->System ClassLoader:系统类加载器

 

Bootstrap ClassLoader是使用C++写的,我们是无法得到它的源码,它是java的核心Loader,比如我们通过String.class.getClassLoader()是获得不到它的名字的,返回的是空值。

如果父类加载器加载了某个类,子类就不会加载了。

   ClassLoader动态加载:

    a.并非一次性加载

    b.需要运行时才加载

    c.可以观察类的具体加载过程:java -verbose:class  eclipse中只要配置后面的-verbose:class

    d.static语句块在加载后执行一次。

    e.dynamic语句块每次new新的对象都会执行

        等同于构造方法中的语句。

        用的比较少。

具体看参见:http://gzcj.iteye.com/blog/394644

 

二、使用反射实现JDK动态代理

java中提供了Proxy类和一个InvocationHandler接口,通过他们俩我们就可以创建JDK动态代理,下面让我们通过一个实例来看看如何使用吧:

Person接口

  

package com.lyl.reflect;  

  

public interface Person {  

    void walk();  

    void sayHello(String name);  

}  

 

MyInvocationHandler.java  

  

package com.lyl.reflect;  

  

import java.lang.reflect.InvocationHandler;  

import java.lang.reflect.Method;  

  

public class MyInvocationHandler implements InvocationHandler {  

  

    @Override  

    public Object invoke(Object proxy, Method method, Object[] args)  

            throws Throwable {  

        System.out.println("正在运行的方法:"+method);  

        if(args!=null){  

            System.out.println("执行该方法传入的参数:");  

            for(Object arg:args){  

                System.out.println(arg);  

            }  

        }else {  

            System.out.println("该方法没有传入参数!");  

        }  

        return null;  

    }  

  

}  

 

ProxyTest.java  

  

package com.lyl.reflect;  

  

import java.lang.reflect.InvocationHandler;  

import java.lang.reflect.Proxy;  

  

public class ProxyTest {  

    public static void main(String[] args) {  

        InvocationHandler handler=new MyInvocationHandler();  

        //返回Person接口的代理类实例  

        Person person=(Person)Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, handler);  

        person.walk();  

        person.sayHello("ITEYE");  

    }  

}  

 通过Proxy类的newProxyInstance()方法我们可以获得一个指定接口的代理类实例,在MyInvocationHandler中会自动执行invoke方法,执行结果如下:

正在运行的方法:public abstract void com.lyl.reflect.Person.walk()  

该方法没有传入参数!  

正在运行的方法:public abstract void com.lyl.reflect.Person.sayHello(java.lang.String)  

执行该方法传入的参数:  

ITEYE  

 

首先必须明一点 Field类主要是用来辅助获取和操作类的属性的!

 

1.怎么通过反射获取类的属性

 

 先来看JDK提供的方法有如下几种:

 

  a)Class.getDeclaredField(String name);

 

返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。

 

  b)Class.getDeclaredFields();

 

返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。

 

  c)Class.getField(String name);

 

返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。

 

  d)Class.getField();

 

返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。

 

来一个例子来说明一下 :

实体类:

import java.util.Date;

/**

 * @ClassName: Student

 * @Description: 学生实体

 * @author  ***  

 * @date 2014-3-18 下午5:17:35

 * @version V1.0 

 */

public class Student

{

  private Long id;

  private String name;

  private Date createdate;

  private String no;

  public String nickname;

public Long getId()

{

    return id;

}

public void setId(Long id)

{

    this.id = id;

}

public String getName()

{

    return name;

}

public void setName(String name)

{

    this.name = name;

}

................   

测试类:

 

import java.lang.reflect.Field;

import java.util.Date;

/**

 * @ClassName: ReflectFieldTest

 * @Description: 反射Field测试。

 * @author  ***  

 * @date 2014-3-18 下午5:16:17

 * @version V1.0 

 */

public class ReflectFieldTest

{

  

   public static void main(String[] args)

{    

Student stu=new Student();

      stu.setId(1L);

      stu.setName("Josean");

      stu.setNo("201403185203344");

      stu.setCreatedate(new Date());

     try

     {

         Field property1=stu.getClass().getDeclaredField("name");

//private java.lang.String com.cx.test.Student.name

        System.out.println(property1);

        Field property3=stu.getClass().getField("nickname");

//public java.lang.String com.cx.test.Student.nickname

        System.out.println(property3);

        //错误方法 getField系列方法只能获取公共字段

        //Field property2=stu.getClass().getField("name");

        //System.out.println(property2);

        //会抛java.lang.NoSuchFieldException

        

     } catch (SecurityException e)

     {

         e.printStackTrace();

     } catch (NoSuchFieldException e)

     {

         e.printStackTrace();

     }

}   

}

 

2.进行属性获取、更改

得到这个Field之后你就可以获取他的值或者设置他的值了。

 

获取它的值用get类型方法,针对常见类型提供了对应get方法,这里就不一一列举了。

 

值得注意的是获取私有属性的时候必须先设置Accessibletrue,然后才能获取。

 

同理设置的时候调用set类型方法,这里也不一一列举了,下面放代码。

 

import java.lang.reflect.Field;

import java.util.Date;

/**

 * @ClassName: ReflectFieldTest

 * @Description: 反射Field测试。

 * @author JoseanLuo  

 * @date 2014-3-18 下午5:16:17

 * @version V1.0 

 */

public class ReflectFieldTest

{

  

    public static void main(String[] args) throws Exception

{    Student stu=new Student();

     stu.setId(1L);

     stu.setName("Josean");

     stu.setNo("201403185203344");

     stu.setCreatedate(new Date());

     stu.setNickname("copyman");

        Field property1=stu.getClass().getDeclaredField("name");

        //System.out.println(property1);

//out:private java.lang.String com.cx.test.Student.name

        Field property3=stu.getClass().getField("nickname");

        System.out.println(property3.get(stu));

        //System.out.println(property3);

//out:public java.lang.String com.cx.test.Student.nickname

        //错误方法 getField系列方法只能获取公共字段

        //Field property2=stu.getClass().getField("name");

        //System.out.println(property2);

        //会抛java.lang.NoSuchFieldException

        Field [] prFields4=stu.getClass().getDeclaredFields();

        for(Field field:prFields4)

        {

            System.out.println(field);

            System.out.println(field.equals(property1));

            //私有变量必须先设置Accessibletrue

            field.setAccessible(true);

            //获取用get类方法。

            System.out.println(field.get(stu));

        }

        //设置用set类方法

        property3.set(stu, "名字被我改了,哈哈");

          

        System.out.println(stu.getNickname());        

}

     

}

这个是控制台输出:

copyman

private java.lang.Long com.cx.test.Student.id

false

1

private java.lang.String com.cx.test.Student.name

true

Josean

private java.util.Date com.cx.test.Student.createdate

false

Tue Mar 18 18:19:39 CST 2014

private java.lang.String com.cx.test.Student.no

false

201403185203344

public java.lang.String com.cx.test.Student.nickname

false

copyman

名字被我改了,哈哈


猜你喜欢

转载自blog.csdn.net/kangkang_hacker/article/details/80539444