java反射机制

来源:互联网 发布:北大青鸟网络学费多少 编辑:程序博客网 时间:2024/05/16 08:11
1、什么是反射机制 

    简单的来说,反射机制指的是程序在运行时能够获取自身的信息。在java中,只要给定完整的类名(包名+类名)、或根据类的一个对象的引用,就可以通过反射机制来获得类的所有信息。反射是java语言的一个特性,它允程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作。例如它允许一个java的类获取他所有的成员变量和方法并且显示出来。这个能特定我们不常看到,但是在其他的比如C或者C++语言中很不就存在这个特性。一个常见的例子是在JavaBean中,一些组件可以通过一个构造器来操作。这个构造器就是用的反射在动态加载的时候来获取的java中类的属性的。


2、哪里用到反射机制 
    有些时候,我们用过一些知识,但是并不知道它的专业术语是什么,在刚刚学jdbc时用过一行码Class.forName("com.mysql.jdbc.Driver.class").newInstance();但是那时候只知道那行代码是生成 驱动对象实例,并不知道它的具体含义。学了反射机制,才知道,原来这就是反射,现在很多开 框架都用到反射机制,hibernate、struts都是用反射机制实现的。

反射应用场景:

    简单地说就是你想new一个对象,调一个方法,但是你还不确定要new哪个对象,调哪个方法,要程序跑起来再看情况而定,你就不能提前在程序里写好常规的方法代码。

    例:1、当你做一个软件可以安装插件的功能,你连插件的类型名称都不知道,你怎么实例化这个对象呢?因为程序是支持插件的(第三方的),在开发的时候并不知道 。所以,无法在代码中New出来 ,但反射可以,通过反射,动态加载程序集,然后读出类,检查标记之后再实例化对象,就可以获得正确的类实例。反射的目的就是为了扩展未知的应用。比如你写了一个程序,这个程序定义了一些接口,只要实现了这些接口的dll都可以作为插件来插入到这个程序中。那么怎么实现呢?就可以通过反射来实现。就是把dll加载进内存,然后通过反射的方式来调用dll中的方法。很多工厂模式就是使用的反射。

    2、在编码阶段不知道那个类名,要在运行期从配置文件读取类名, 这时候就没有办法硬编码new ClassName(),而必须用到反射才能创建这个对象。

      3、我需要加载一个类,但是此类的class文件不在类路径上,需要从其他地方获取,如网络或者其他文件,这个时候就需要自定义类加载器,以定制的方式把类的全限定名转换成一个Java class文件格式的字节数组,加载出来要访问该对象的方法怎么办,就通过加载得到的Class对象通过反射来访问了,这种情况下普通程序无法完成.


3、反射机制的优点与缺点 
    为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念

    静态编译:在编译时确定类型,绑定对象,即通过。

    动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。 

    一句话,反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。

    它的缺点是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。  1,丧失了编译时类型检查的好处 2,执行反射访问所需代码笨拙冗长 3,性能损失

    对于特定的复杂的系统编程任务,反射很有必要,如果编写的程序必须要与编译时未知的类一起工作,就可以用反射仅仅来实例化对象,但不应该通过反射来访问对象,应该通过被实例化对象的接口或者父类来访问对象的方法,这就是接口优先于反射机制的含义。


3.1、实现Java反射的类
  1)Class:它表示正在运行的Java应用程序中的类和接口
  2)Field:提供有关类或接口的属性信息,以及对它的动态访问权限
  3)Constructor:提供关于类的单个构造方法的信息以及对它的访问权限
  4)Method:提供关于类或接口中某个方法信息
  注意:Class类是Java反射中最重要的一个功能类,所有获取对象的信息(包括:方法/属性/构造方法/访问权限)都需要它来实现


4、反射的前传:类类型   Class Class 

   1、Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标识。这项信息纪录了每个对象所属的类。虚拟机通常使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的类是Class类。Class类封装一个对象和接口运行时的状态,当装载类时,Class类型的对象自动创建。

   2、Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。 很多解释说Class类没有构造器,其实是有的,只不过它的构造方法是private的。

   3、虚拟机为每种类型管理一个独一无二的Class对象。也就是说,每个类(型)都有一个Class对象。运行程序时,Java虚拟机(JVM)首先检查是否所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。

   4、基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。 

   5、每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。

   6、 一般某个类的Class对象被载入内存,它就用来创建这个类的所有对象。

   简记:Class类是封装类型信息的类,每个类都有一个Class对象,每个java类运行时都在JVM里表现为一个class对象,当装载类时,Class类型的对象自动创建。某个类的Class对象被载入内存,它就用来创建这个类的所有对象。


4、1程序中如何得到一个类的类型信息(即该类对应的Class对象)?

有三种方法可以的获取:

1、调用Object类的getClass()方法来得到Class对象,这也是最常见的产生Class对象的方法。例如:
    MyObject x;
    Class c1 = x.getClass();

2、使用Class类的中静态forName()方法获得与字符串对应的Class对象。例如: 
    Class c2=Class.forName("MyObject"),Employee必须是接口或者类的名字。

3、获取Class类型对象的第三个方法非常简单。如果T是一个Java类型,那么T.class(类字面常量)就代表了匹配的类对象。例如
    Class cl1 = Manager.class;
    Class cl2 = int.class;
    Class cl3 = Double[].class;

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);    }}
没有对象实例的时候,forName( )方法、类字面常量,可以在类不确定的情况下实例化Class,更具灵活性

    注意:Class对象实际上描述的只是类型,而这类型未必是类或者接口。例如上面的int.class是一个Class类型的对象。由于历史原因,数组类型的getName方法会返回奇怪的名字。当装载类时,Class类型的对象自动创建。某个类的Class对象被载入内存,它就用来创建这个类的所有对象。


4、2 获取反射类的实例

Class类中有一个方法叫做newInstance(),它可以用来创建一个Class类对象的新实例。Class对象包含的内容就是反射好的那个类,我们要构造那个类的新实例(新对象),

4.2.1 Class类的无参构造对象

public class Demo {    public static void main(String[] args) {        //实例化Class对象,forName()方法会抛异常        Class<?> c = null;        try {            //这里需要完整的包名和类名            c = Class.forName("java.lang.String");        } catch (ClassNotFoundException e) {            e.printStackTrace();        }                 //生成一个字符串的引用        String s = null;        try {            //将构造好的对象向下转型为String类            //newInstance()方法会抛异常            s = (String) c.newInstance();        } catch (InstantiationException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }        System.out.println("字符串长度: " + s.length());    }}

这样就通过无参数的形式构造了一个新的对象,如同正常模式中,通过无参构造方法来构造新对象一样。缺点是我们只能利用默认构造函数,因为Class的newInstance是不接受参数的。我们知道,类中除了有无参构造方法,还会存在有参数的构造方法。

4.2.2 Class类的有参构造对象

import java.lang.reflect.Constructor; 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类的newInstance()无参构造对象时,原类一定要有对应无参构造函数。


4、3 获取类的构造器  

首先介绍一下Constructor类,这个类用来封装反射得到的构造器,Class有四个方法来获得Constructor对象

1.public Constructor<?>[] getConstructors()      返回类中所有的public构造器集合,默认构造器的下标为0

2.public Constructor<T> getConstructor(Class<?>... parameterTypes)   返回指定public构造器,参数为构造器参数类型集合

3.public Constructor<?>[] getDeclaredConstructors()  返回类中所有的构造器,包括私有

4.public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 返回任意指定的构造器

从名字来看,还是很好懂的,带上Declared的都是获得所有的构造方法,包括私有

        Class cls1 = Role.class;//指定参数列表获取特定的方法         Constructor con = cls1.getDeclaredConstructor(new Class[]{String.class});         con.setAccessible(true); //设置可访问的权限         Object obj = con.newInstance(new Object[]{"liyang"});         System.out.println(obj);  //打印一下这个对象的信息         //获取所有的构造方法集合        Constructor con1[] = cls1.getDeclaredConstructors();         con1[1].setAccessible(true);         Object obj1 = con1[1].newInstance(new Object[]{"tom"});         System.out.println(obj1);

解释一下:第一个是获得一个指定的方法,我们指定了参数是一个String类型,第二段我们获得了所有的构造方法集合,并选取了其中一个创建了新的对象。
注意,以上的四个方法全部需要抛出异常,当我们获得私有的方法的时候,要用setAccessible设置一下可访问的权限,例子中没有演示获取共有方法,那个比较简单,就不做介绍了,其实掌握了上面两个,其他就好理解了。


4、5 获取类的成员变量

了解了构造器,其实你可以猜到成员变量的获取方法了,成员变量用Field类进行封装。

主要的方法非常的类似:
1.public Field getDeclaredField(String name)  获取任意指定名字的成员

2.public Field[] getDeclaredFields()             获取所有的成员变量

3.public Field getField(String name)           获取任意public成员变量

4.public Field[] getFields()                   获取所有的public成员变量

可以看出这些方法都是异曲同工的,好了直接看一下例子吧

import java.lang.reflect.Field; class Person {    public String name;    private int age;         public Person(String name, int age) {        this.name = name;        this.age = age;    }} public class Demo {    public static void main(String[] args) throws Exception {        Person p = new Person("zhangsan",12);         Class<?> c = p.getClass();                 //获取公共属性的值        Field f1 = c.getField("name");        //get(p)表明要获取是哪个对象的值        String str = (String) f1.get(p);        System.out.println("姓名: " + str);                 //获取私有属性的值        Field f2 = c.getDeclaredField("age");        //age是私有属性,所以要设置安全检查为true        f2.setAccessible(true);        int age = (int) f2.get(p);        System.out.println("年龄: " + age);    }} 


4、6 获取类的方法                                                                                                                               

封装类的方法的类是Method.获取method也有四个方法

1.public Method[] getMethods()    获取所有的共有方法的集合

2.public Method getMethod(String name,Class<?>... parameterTypes) 获取指定公有方法 参数1:方法名 参数2:参数类型集合  

3.public Method[] getDeclaredMethods()  获取所有的方法

4.public Method getDeclaredMethod(String name,Class<?>... parameterTypes) 获取任意指定方法

Method f = cls1.getMethod("getName", null);Object name = f.invoke(obj, null);System.out.println("we invoke method : "+ name);


import java.lang.reflect.Method; class Person {    public void print(int i) {        System.out.println("我在写数字: " + i);    }         public static void say(String str) {        System.out.println("我在说: " + str);    }} public class Demo {    public static void main(String[] args) throws Exception {        Person p = new Person();        Class<?> c = p.getClass();             //getMethod()方法需要传入方法名,和参数类型        Method m1 = c.getMethod("print", int.class);        //invoke()表示调用的意思,需要传入对象和参数        m1.invoke(p, 10);                 Method m2 = c.getMethod("say", String.class);        //这里的null表示不由对象调用,也就是静态方法        m2.invoke(null, "你妹");    }}


//反射调用方法,可以通过Method类的invoke方法实现动态方法的调用
//public Object invoke(Object obj, Object... args)
//第一个参数代表对象
//第二个参数代表执行方法上的参数
//若反射要调用类的某个私有方法,可以在这个私有方法对应的Mehtod对象上先 调用setAccessible(true)


class类的其它方法:

取得类所实现的接口     getInterfaces()
取得父类               getSuperclass()
返回该类的类加载器     getClassLoader() 
返回表示数组组件类型的 Class getComponentType() 
判定此 Class 对象是否表示一个数组类 isArray()  


5、java反射机制可以调用到私有方法,是不是就破坏了JAVA的卦装性呢。

    这是一个很值得探讨的问题,许多人接触反射时,对反射功能之强大都会抱有怀疑,感觉严重破坏了封装的性质。可是,什么是封装,什么是安全呢?
    封装,是将具体的实现细节隐藏,而把功能作为整体提供给类的外部使用,也就是说,公有方法能够完成类所具有的功能。当别人使用这个类时,如果通过反射直接调用私有方法,可能根本实现不了类的功能,甚至可能会出错,因此通过反射调用私有方法可以说是没有任何用处的,开发人员没有必要故意去破坏封装好的类。从这点上看,封装性并没有被破坏。
   所谓安全,如果意思是保护实现源码不被别人看见,那没有作用。不用反射也能轻易获取源码。
所以我以为反射机制只是提供了一种强大的功能,使得开发者能在封装之外,按照特定的需要实现一些功能。就好比核技术,虽然造核弹很危险吧,但造核电站还是很有用处的(这个比喻似乎不是很恰当,将就将就)。 

    Java语言是一个严谨的编程语言,语言本身是静态的。为了能让语言具有动态编程的特性,必须要有反射机制。而反射机制本身就是底层的处理,不可能按表层的封转特性来处理。也就是说不给调用私有方法的能力,很多程序受到局限,那么实现起来就麻烦了。
以上说的,有肯能你没明白。举一个生活的例子,你家的电视机是要由外壳的,目的是不让普通人接触到电视中的电路。那么Java语言的基本面向对象特征正是这个层次的应用。也就是对于普通程序员的程序,是通过遥控器来操作电视的。但是,如果你是一个专业的电工的话,那么可以打开电视机的后盖,调整电视中的电路和结构,甚至如果是电工的话,那么调台可能都不使用遥控器,而是通过调整内部电路的电阻的阻值来实现。Java中的反射机制正是提供更高要求的编程来使用的,不需要考虑基本面向对象的特征,而是要考虑能否得到和控制代码中的一切,这样反射机制编程才有意义。

来源:点击打开链接


6、用反射机制能干什么事 
    刚开始在使用jdbc时侯,在编写访问数据库时写到想吐,有八个表,每个表都有增删改查中操作 那时候还不知道有反射机制这个概念,所以就对不同的表创建不同的dao类,这样不仅开发速率地,而且代码冗余的厉害,最要命的是看着差不多的,然后直接复制修改,由于容易犯各种低级的错误(大小写啊,多一 个或少一个字母啊……),一个错误就可以让你找半天。 有了java反射机制,什么都好办了,只需要写一个dao类,四个方法,增删改查,传入不同的对象,就OK啦,无需为每一个表都创建dao类,反射机制会自动帮我们完成剩下的事情,这就是它的好处。说白了,反射机制就是专门帮我们做那些重复的有规则的事情,所以现在很多的自动生成代码的软件就是运用反射机制来完成的,只要你按照规则 输入相关的参数.
  



参考来源:

深入研究java.lang.Class类

java反射机制初探

java中的反射总结

Java反射机制





0 0