您的位置:首页 > 编程语言 > Java开发

JAVA学习笔记--反射机制

2016-08-04 08:47 295 查看

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反射中最重要的一个功能类,所有获取对象的信息(包括:方法/属性/构造方法/访问权限)都需要它来实现

   二,反射机制的作用:
              1,反编译:.class-->.java
              2,通过反射机制访问java对象的属性,方法,构造方法等;
             这样好像更容易理解一些,下边我们具体看怎么实现这些功能。

      三,在这里先看一下sun为我们提供了那些反射机制中的类:
java.lang.Class;                
java.lang.reflect.Constructor; java.lang.reflect.Field;        
java.lang.reflect.Method;
java.lang.reflect.Modifier;

            很多反射中的方法,属性等操作我们可以从这四个类中查询。还是哪句话要学着不断的查询API,那才是我们最好的老师

四,获取方法,和构造方法,不再详细描述,只来看一下关键字:
方法关键字
含义
getDeclaredMethods()
获取所有的方法
getReturnType()
获得方法的放回类型
getParameterTypes()
获得方法的传入参数类型
getDeclaredMethod("方法名",参数类型.class,……)
获得特定的方法
 
 
构造方法关键字
含义
getDeclaredConstructors()
获取所有的构造方法
getDeclaredConstructor(参数类型.class,……)
获取特定的构造方法
 
 
父类和父接口
含义
getSuperclass()
获取某类的父类
getInterfaces()
获取某类实现的接口
  

         这样我们就可以获得类的各种内容,进行了反编译。对于JAVA这种先编译再运行的语言来说,反射机制可以使代码更加灵活,更加容易实现面向对象。

 
  五,反射加配置文件,使我们的程序更加灵活:
             在设计模式学习当中,学习抽象工厂的时候就用到了反射来更加方便的读取数据库链接字符串等,当时不是太理解,就照着抄了。看一下.NET中的反射+配置文件的使用:
             当时用的配置文件是app.config文件,内容是XML格式的,里边填写链接数据库的内容:

[html]
view plain
copy
print?





      <configuration>  
lt;appSettings>  
<add     key=""  value=""/>  
lt;/appSettings>  
        </configuration>  



<configuration>
<appSettings>
<add     key=""  value=""/>
</appSettings>
</configuration>


 反射的写法:   

[csharp]
view plain
copy
print?





assembly.load("当前程序集的名称").CreateInstance("当前命名空间名称".要实例化的类名); 



assembly.load("当前程序集的名称").CreateInstance("当前命名空间名称".要实例化的类名);


          这样的好处是很容易的方便我们变换数据库,例如我们将系统的数据库从SQL Server升级到Oracle,那么我们写两份D层,在配置文件的内容改一下,或者加条件选择一下即可,带来了很大的方便。
            
         当然了,JAVA中其实也是一样,只不过这里的配置文件为.properties,称作属性文件。通过反射读取里边的内容。这样代码是固定的,但是配置文件的内容我们可以改,这样使我们的代码灵活了很多!

    综上为,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;

[java]
view plain
copy
print?

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);  
    }  
}  



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类的无参构造对象

[java]
view plain
copy
print?

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());  
    }  
}  



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类的有参构造对象

[java]
view plain
copy
print?

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);  
    }  
}  



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的都是获得所有的构造方法,包括私有

[java]
view plain
copy
print?

        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);  



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成员变量

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

[java]
view plain
copy
print?

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);  
    }  
}   



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) 获取任意指定方法

[java]
view plain
copy
print?

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



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


[java]
view plain
copy
print?

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, "你妹");  
    }  
}  



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 反射机制