java中的反射机制详解

一、什么是Java反射机制
Java反射机制是指在运行状态中,对于任意一个类,都能够通过这个类本身获取这个类的所有属性和方法信息;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。也就是说java反射可以实现在运行时可以知道任意一个类的属性和方法。
那么我们为什么要用反射机制?直接通过new关键字创建对象不就可以了吗?这就涉及到了动态与静态的概念。
静态编译:在编译时确定类型,绑定了具体对象。
动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。
java反射机制的优点:
通过java反射机制可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。例如spring 的 IOC/DI、javaBean和jsp之间调用、struts的 FormBean 和页面之间的调用、JDBC 的 classForName()的使用、hibernate的 find(Class clazz)的使用等。 
java反射机制的缺点:
大量使用反射会使系统性能大打折扣。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这种操作总是慢于只直接执行相同的操作。
Java反射机制的主要功能:
1、确定一个对象的类。
2、取出类的数据成员,方法,构造器,和超类等相关信息。
3、找出某个接口里定义的常量和方法说明。
4、创建一个类实例,这个实例在运行时刻才有名字(运行时间才生成的对象)。
5、在运行时刻调用动态对象的方法.
6、创建数组,数组大小和类型在运行时刻才确定,也能更改数组成员的值.


二、Class类
Class类是Java反射的基础,Class类表示正在运行的java应用程序中的类和接口。Class只有私有的构造函数。Class类在加载类时由Java虚拟机以及调用类加载器中的 defineClass方法自动创建的。只在内存中存在一份加载类的Class类。 所有的类都是java.lang.Class类的实例对象。
1、如何获取一个Class类的实例
对于普通的对象,我们一般都会这样创建和表示:
MyClass myClass = new MyClass();
上面说了,所有类都是Class的对象,那么如何获取一个Class的对象呢?可不可以通过如下方式呢:
Class c = new Class();
查看Class的源码:
private  Class(ClassLoader loader) { 
    classLoader = loader; 
}
可以看到Class的构造器是私有的,只有JVM可以创建Class的对象,因此不可以像普通类一样new一个Class对象,虽然我们不能new一个Class对象,但是却可以通过已有的类得到一个Class对象,共有三种方式,如下:
(1)Class c1 = MyClass.class;
这说明任何一个类都有一个隐含的静态成员变量class,这种方式是通过获取类的静态成员变量class得到的。
(2)Class c2 = myClass.getClass();
myClass是MyClass的一个对象,这种方式是通过一个类的对象的getClass()方法获得的。
Class c3 = Class.forName("com.kang.test.MyClass");
这种方法是Class类调用forName方法,通过一个类的全类名(com.kang.test.MyClass是类MyClass的全类名)获得。
这里的c1、c2、c3都是Class的对象,他们是完全一样的,而且有个学名,叫做MyClass的类类型(class type)。顾名思义,类类型就是类的类型,也就是描述一个类是什么,都有哪些东西,所以我们可以通过类类型知道一个类的属性和方法,并且可以调用一个类的属性和方法,这就是反射的基础。
这里用String类来做个测试:
  String s = "myTest";  
  Class cls1 = s.getClass();  
  Class cls2 = String.class;  
  Class cls3 = Class.forName("java.lang.String");  
  if (cls1 == cls2) {  
   System.out.println("cls1 == cls2");  
  }  
  if (cls2 == cls3) {  
   System.out.println("cls2 == cls3");  
  }   
上述代码输出是两个true。因为他们描述的都是同一个类java.lang.String类。

2、Java反射相关操作
通过java的反射获取到Class的对象后,可以进行相关操作:
(1)获取成员方法信息
单独获取某一个方法是通过Class类的以下方法获得的:
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 得到该类所有的方法,不包括父类的
public Method getMethod(String name, Class<?>... parameterTypes) // 得到该类所有的public方法,包括父类的
两个参数分别是方法名name和方法参数类的类类型列表parameterTypes。
示例:
public class Person {
    private String name;
    private int age;
    private String msg="hello wrold";
 public String getName() {
        return name;
  }


    public void setName(String name) {
        this.name = name;
  }


    public int getAge() {
        return age;
  }


    public void setAge(int age) {
        this.age = age;
  }


    public Person() {
    }


    private Person(String name) {
        this.name = name;
        System.out.println(name);
  }


    public void fun() {
        System.out.println("fun");
  }


    public void fun(String name,int age) {
        System.out.println("我叫"+name+",今年"+age+"岁");
  }
}


public class MyTestDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.kang.test.Person");
            Object o = c.newInstance();
            Method method = c.getMethod("fun", String.class, int.class);
            method.invoke(o, "kang", 24);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果:
我叫kang,今年24岁
可以看到我们没有通过new关键字创建对象,但是我们仍然执行了类中的方法。

我们还可以一次性获取类中的所有自定义方法。
public class GetMethodAllDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.kang.test.Person");
            Method[] methods = c.getDeclaredMethods();
            for(Method m:methods){
                String  methodName= m.getName();
                System.out.println(methodName);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果:
getName
setName
getAge
setAge
fun
fun

这里如果把c.getDeclaredMethods();改成c.getMethods();执行结果如下,多了很多方法,把Object里面的方法也打印出来了,因为Object是所有类的父类:
getName
setName
getAge
setAge
fun
fun
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

(2)获取成员变量信息
成员变量的信息包括成员变量类型和成员变量名两部分。
类的成员变量也是一个对象,具体来说它是java.lang.reflect.Field的一个对象,所以我们通过java.lang.reflect.Field里面封装的方法来获取这些信息。
如果要单独获取某个成员变量,通过Class类的以下方法实现:
public Field getDeclaredField(String name) // 获得该类自身声明的所有变量,不包括其父类的变量
public Field getField(String name) // 获得该类自所有的public成员变量,包括其父类变量
上述两个方法中的输入参数name是对应的成员变量的名字。
示例:
public class TestGetParamDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.kang.test.Person");
            //获取成员变量
            Field field = c.getDeclaredField("msg"); //因为msg变量是private的,所以不能用getField方法
            Object o = c.newInstance();
            field.setAccessible(true);//设置是否允许访问,因为该变量是private的,所以要手动设置允许访问,如果msg是public的就不需要这行了。
            Object msg = field.get(o);
            System.out.println(msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
执行结果:
hello wrold

获取所有成员变量信息:
public class TestGetParamAllDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.kang.test.Person");
            Field[] fields = c.getDeclaredFields();
            for(Field field :fields){
                System.out.println(field.getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果:
name
age
msg

(3)获取构造函数Constructor
事实上类的构造函数也是一个对象,它是java.lang.reflect.Constructor的一个对象,所以我们通过java.lang.reflect.Constructor里面封装的方法来获取关于构造函数的信息。
单独获取某个构造函数,通过Class类的以下方法实现:
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) //  获得该类所有的构造器,不包括其父类的构造器
public Constructor<T> getConstructor(Class<?>... parameterTypes) // 获得该类所以public构造器,包括父类
上述两个方法的输入参数为构造函数参数类的类类型列表。
若A类中有如下构造函数
public A(String a, int b) {
    // code body
}
那么就可以通过:
Constructor constructor = a.getDeclaredConstructor(String.class, int.class);
来获取这个构造函数。

示例:
public class TestGetCstorDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.kang.test.Person");
            //获取构造函数
            Constructor constructor = c.getDeclaredConstructor(String.class);
            constructor.setAccessible(true);//设置是否允许访问,因为该构造器是private的,所以要手动设置允许访问,如果构造器是public的就不需要这行了。
            constructor.newInstance("kang");//这里Person的构造器中有一个打印操作
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果:
kang
需要注意的是,Class中也有一个newInstance方法,但这个方法只能创建内部包含一个无参数的构造函数的类。如果某类只有带参数的构造函数,而没有不带参数的构造器,那么就要使用另外一种方式:即先调用Class类的getConstructor(String.class,int.class)方法获取一个指定的构造函数(这里的构造函数有两个输入参数,一个String类型,一个int类型),然后再调用Constructor类的newInstance("张三",20)方法创建对象。

获取所有的构造函数,可以通过以下步骤实现:
public class TestGetCstorAllDemo {
    public static void main(String[] args){
            Constructor[] constructors = c.getDeclaredConstructors();
            for(Constructor constructor:constructors){
                System.out.println(constructor);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
执行结果:
public com.kang.test.Person()
public com.kang.test.Person(java.lang.String)

三、java反射机制与集合的泛型
Java中集合的泛型,是用来防止错误输入的。只在编译阶段有效,绕过编译到了运行期就无效了。
测试:
public class Test {
    public static void main(String[] args) {
        List list1 = new ArrayList(); // 没有泛型 
        List<String> list2 = new ArrayList<String>(); // 有泛型




        /*
         * 1.首先观察正常添加元素方式,在编译期间就检查泛型,
         * 所以这个时候如果list2添加int类型会报错(编译时报错)。
         */
        list2.add("hello");
//      list2.add(20); // 报错!list2有泛型限制,只能添加String,添加int报错
        System.out.println("list2的长度是:" + list2.size()); // 此时list2长度为1




        /*
         * 2.然后通过反射添加元素方式,在运行期动态加载类,首先可以得到list1和list2
         * 的类类型,通过比较发现两者相同。然后再通过方法反射绕过编译器来调用add方法,         * 来插入int型的元素,发现可以添加成功。(编译器并没有提示错误)
         * 
         */
        Class c1 = list1.getClass();
        Class c2 = list2.getClass();
        System.out.println(c1 == c2); // 结果:true,说明类类型完全相同


        // 验证:我们可以通过方法的反射来给list2添加元素,这样可以绕过编译检查
        try {
            Method m = c2.getMethod("add", Object.class); // 通过方法反射得到add方法
            m.invoke(list2, 20); // 给list2添加一个int型的,这时编译器并没有提示错误。
            System.out.println("list2的长度是:" + list2.size()); 
            // 结果:2,说明list2长度增加了,并没有泛型检查
        } catch (Exception e) {
            e.printStackTrace();
        }


        /*
         * 综上可以看出,在编译器的时候,泛型会限制集合内元素类型保持一致,但是编译器结束进入
         * 运行期以后,泛型就不再起作用了,即使是不同类型的元素也可以插入集合。
         */
    }
}

运行结果:
list2的长度是:1
true
list2的长度是:2



你可能感兴趣的:(java,反射,对象)