【反射】Java反射机制

   Class

 1、Class是一个类,一个描述类的类(也就是描述类本身),封装了描述方法的Method,描述字段的Filed,描述构造器的Constructor等属性
    2、对象照镜子后(反射)可以得到的信息:某个类的数据成员名、方法和构造器、某个类到底实现了哪些接口。
    3、对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。
        一个 Class 对象包含了特定某个类的有关信息。
    4、Class 对象只能由系统建立对象
    5、一个类在 JVM 中只会有一个Class实例

 

反射之中包含了一个“反”的概念,所以要想解释反射就必须先从“正”开始解释,一般而言,当用户使用一个类的时候,应该先知道这个类,而后通过这个类产生实例化对象,但是“反”指的是通过对象找到类。

package reflection_demo1;

class Person {}
public class Demo{
    public static void main(String[] args)  {
        Person per = new Person() ; // 正着操作
        System.out.println(per.getClass().getName()); // 反着来   输出: reflection_demo1.Person
    }
}

以上的代码使用了一个getClass()方法,而后就可以得到对象所在的“包.类”名称,这就属于“反”了,但是在这个“反”的操作之中有一个getClass()就作为发起一切反射操作的开端。

Person的父类是Object类,而上面所使用getClass()方法就是Object类之中所定义的方法。

·取得Class对象:public final Class<?> getClass(),反射之中的所有泛型都定义为?,返回值都是Object。

而这个getClass()方法返回的对象是Class类的对象,所以这个Class类就是所有反射操作的源头。但是在讲解其真正使用之前还有一个需要先解释的问题,既然Class是所有反射操作的源头,那么这个类肯定是最为重要的,而如果要想取得这个类的实例化对象,Java中定义了三种方式:

一 获取Class类的实例化对象

方式一:通过Object类的getClass()方法取得,基本不用:

package reflection_demo1;

class Person {}
public class Demo{
    public static void main(String[] args)  {
        Person per = new Person() ; // 正着操作
        Class<?> cls = per.getClass() ; // 反着来 取得Class对象
        System.out.println(cls.getName()); 
    }
}

方式二:使用“类.class”取得

package reflection_demo1;

class Person {}
public class Demo{
    public static void main(String[] args)  {
        Class<?> cls = Person.class; // 反着来  取得Class对象
        System.out.println(cls.getName()); 
    }
}

方式三:使用Class类内部定义的一个static方法,主要使用

·取得Class类对象:public static Class<?>  forName(String className)  throws ClassNotFoundException;

package reflection_demo1;

class Person {}
public class Demo{
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("reflection_demo1.Person") ; //反着来  取得Class对象
        System.out.println(cls.getName());         
    }
}

二 通过反射实例化对象

那么现在一个新的问题又来了,取得了Class类的对象有什么用处呢?对于对象的实例化操作之前一直依靠构造方法和关键字new完成,可是有了Class类对象之后,现在又提供了另外一种对象的实例化方法:

通过反射实例化对象:public T newInstance() throws InstantiationException, IllegalAccessException;

范例:通过反射实例化对象

package reflection_demo1;

class Person {
    @Override
    public String toString() {
        return"Person Class Instance.";
    }
}
public class Demo{
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("reflection_demo1.Person") ; // 取得Class对象
        Object obj = cls.newInstance() ; // 实例化对象,和使用关键字new一样
        Person per = (Person) obj ; // 向下转型
        System.out.println(per);      // Person Class Instance.
    }
}

那么现在可以发现,对于对象的实例化操作,除了使用关键字new之外又多了一个反射机制操作,而且这个操作要比之前使用的new复杂一些,可是有什么用?

三 应用举例

对于程序的开发模式之前一直强调:尽量减少耦合,而减少耦合的最好做法是使用接口,但是就算使用了接口也逃不出关键字new,所以实际上new是造成耦合的关键元凶。

范例1:工厂设计模式

package Factory_demo;

interface Fruit {
    public void eat() ;
}
class Apple implements Fruit {
    public void eat() {
        System.out.println("吃苹果。");
    };
}
class Factory {
    public static Fruit getInstance(String className) {
        if("apple".equals(className)){
            return new Apple() ;
        }
        return null;
    }
}
public class Demo {
    public static void main(String[] args) {
        Fruit f = Factory.getInstance("apple") ;
        f.eat() ;
    }
}

以上为之前所编写最简单的工厂设计模式,但是在这个工厂设计模式之中有一个最大的问题:如果现在接口的子类增加了,那么工厂类肯定需要修改,这是它所面临的最大问题,而这个最大问题造成的关键性的病因是new,那么如果说现在不使用关键字new了,变为了反射机制呢?

反射机制实例化对象的时候实际上只需要“包.类”就可以,于是根据此操作,修改工厂设计模式。

package reflection_demo1;

interface Fruit {
    public void eat() ;
}
class Apple implements Fruit {
    public void eat() {
        System.out.println("吃苹果。");
    };
}
class Orange implements Fruit {
    public void eat() {
        System.out.println("吃橘子。");
    };
}

class Factory {
    public static Fruit getInstance(String className) {
        Fruit f = null;
        try{
            f = (Fruit) Class.forName(className).newInstance() ;
        } catch(Exception e) {
            e.printStackTrace();
        }
        return f ;
    }
}

public class Demo {
    public static void main(String[] args) {
        Fruit f = Factory.getInstance("reflection_demo1.Orange") ;   //传参数 包名.类名
        f.eat() ;
    }
}

发现,这个时候即使增加了接口的子类,工厂类照样可以完成对象的实例化操作,这个才是真正的工厂类,可以应对于所有的变化。如果单独从开发角度而言,与开发者关系不大,但是对于日后学习的一些框架技术这个就是它实现的命脉,在日后的程序开发上,如果发现操作的过程之中需要传递了一个完整的“包.类”名称的时候几乎都是反射机制作用。

范例2:

首先准备两个业务类,这两个业务类很简单,就是各自都有一个业务方法,分别打印不同的字符串

Service1.java

package reflection;
 
public class Service1 {
 
    public void doService1(){
        System.out.println("业务方法1");
    }
}

Service2.java

package reflection;
 
public class Service2 {
 
    public void doService2(){
        System.out.println("业务方法2");
    }
}

非反射方式

当需要从第一个业务方法切换到第二个业务方法的时候,使用非反射方式,必须修改代码,并且重新编译运行,才可以达到效果

package reflection;
 
public class Test {
 
    public static void main(String[] args) {
//      new Service1().doService1();
        new Service2().doService2();
    }
}

反射方式

使用反射方式,首先准备一个配置文件,就叫做spring.txt吧, 放在src目录下。 里面存放的是类的名称,和要调用的方法名。
在测试类Test中,首先取出类名称和方法名,然后通过反射去调用这个方法。

当需要从调用第一个业务方法,切换到调用第二个业务方法的时候,不需要修改一行代码,也不需要重新编译,只需要修改配置文件spring.txt,再运行即可。

这也是Spring框架的最基本的原理,只是它做的更丰富,安全,健壮。

Test.java

package reflection;
 
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Properties;
 
public class Test {
 
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void main(String[] args) throws Exception {
 
        //从spring.txt中获取类名称和方法名称
        File springConfigFile = new File("C:\\application\\eclipse_workspace\\Thinking in Java\\src\\spring.txt");
        Properties springConfig= new Properties();
        springConfig.load(new FileInputStream(springConfigFile));
        String className = (String) springConfig.get("class");
        String methodName = (String) springConfig.get("method");
         
        //根据类名称创建类对象
        Class clazz = Class.forName(className);
        //根据方面名称,获取方法
        Method m = clazz.getMethod(methodName);
        //获取构造器
        Constructor c = clazz.getConstructor();
        //根据构造器,实例化出对象
        Object service = c.newInstance();
        //调用对象的指定方法
        m.invoke(service);
         
    }
}

spring.txt

class=reflection.Service1
method=doService1

四 反射的更多用法

以上只是利用了Class类作为了反射实例化对象的基本应用,但是对于一个实例化对象而言,它需要调用类之中的构造方法、普通方法、属性,而这些操作都可以通过反射机制完成。

4.1 调用构造

使用反射机制也可以取得类之中的构造方法,这个方法在Class类之中已经明确定义了:

以下两个方法

取得一个类的全部构造:

public Constructor<?>[] getConstructors() throws SecurityException

取得一个类的指定参数构造:

public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException

现在发现以上的两个方法返回的都是java.lang.reflect.Constructor类的对象。

范例:取得一个类之中的全部构造

package reflection_demo1;

import java.lang.reflect.Constructor;

class Person {
   public Person() {}
    public Person(String name) {}
    public Person(String name,int age) {}
}
public class Demo{
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("reflection_demo1.Person") ; // 取得Class对象
        Constructor<?> cons [] = cls.getConstructors() ; // 取得全部构造
        for(int i = 0; i < cons.length; i++) {
            System.out.println(cons[i]);
        }
                //输出:public reflection_demo1.Person(java.lang.String)
                 // public reflection_demo1.Person(java.lang.String,int)
    }
}

验证:在之前强调的一个简单Java类必须存在一个无参构造方法

范例:观察没有无参构造的情况

package reflection_demo1;

import java.lang.reflect.Constructor;

class Person {
    private String name;
    private int age;    
    
    public Person(String name) {}
    public Person(String name,int age) {
        this.name= name ;
        this.age= age ;
    }
}
public class Demo{
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("reflection_demo1.Person") ; // 取得Class对象
        Object obj = cls.newInstance(); // 实例化对象
        System.out.println(obj);    
    }
}

此时程序运行的时候出现了错误提示“java.lang.InstantiationException”,因为以上的方式使用反射实例化对象时需要的是类之中要提供无参构造方法,但是现在既然没有了无参构造方法,那么就必须明确的找到一个构造方法,而后利用Constructor类之中的新方法实例化对象:

·实例化对象:public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException

package reflection_demo1;

import java.lang.reflect.Constructor;

class Person {
    private String name;
    private int age;    
    
    public Person(String name) {}
    public Person(String name,int age) {
        this.name= name ;
        this.age= age ;
    }
}
public class Demo{
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("reflection_demo1.Person") ; // 取得Class对象
        //取得指定参数类型的构造方法
        Constructor<?> cons = cls.getConstructor(String.class,int.class) ;
        Object obj = cons.newInstance("张三", 20); // 为构造方法传递参数
        System.out.println(obj);
    }
}

很明显,调用无参构造方法实例化对象要比调用有参构造的更加简单、方便,所以在日后的所有开发之中,凡是有简单Java类出现的地方,都一定要提供无参构造。

4.2 调用普通方法

当取得了一个类实例化对象之后,下面最需要调用的肯定是类之中的方法,所以可以继续使用Class类取得一个类中所定义的方法定义:

·取得全部方法:public Method[] getMethods() throws SecurityException;

·取得指定方法:public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException

发现以上的方法返回的都是java.lang.reflect.Method类的对象。

范例:取得一个类之中所定义的全部方法

package reflection_demo1;

import java.lang.reflect.Method;

class Person {
    private String name;
    public void setName(String name) {
        this.name= name;
    }
    public String getName() {
        return name;
    }
}
public class Demo{
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("reflection_demo1.Person") ; // 取得Class对象
        Method met [] = cls.getMethods() ; // 取得全部方法
        for(int x = 0; x < met.length; x++) {
            System.out.println(met[x]);
        }    
    }
}

但是取得了Method类对象最大的作用不再于方法的列出(方法的列出都在开发工具上使用了),但是对于取得了Method类对象之后还有一个最大的功能,就是可以利用反射调用类中的方法:

·调用方法:public Object invoke(Object obj, Object... args) throws IllegalAccessException,IllegalArgumentException, InvocationTargetException

之前调用类中方法的时候使用的都是“对象.方法”,但是现在有了反射之后,可以直接利用Object类调用指定子类的操作方法。(同时解释一下,为什么setter、getter方法的命名要求如此严格)。

范例:利用反射调用Person类之中的setName()、getName()方法

package reflection_demo1;

import java.lang.reflect.Method;

class Person {
    private String name;
    public void setName(String name) {
        this.name= name;
    }
    public String getName() {
        return name;
    }
}
public class Demo{
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("reflection_demo1.Person") ;       // 取得Class对象
        Object obj = cls.newInstance();               // 实例化对象,没有向Person转型
        String attribute = "name";                   // 要调用类之中的属性
        Method setMet = cls.getMethod("set"+ initcap(attribute), String.class);   // setName()
        Method getMet = cls.getMethod("get"+ initcap(attribute));    // getName()
        setMet.invoke(obj, "张三") ;                  // 等价于:Person对象.setName("张三")
        System.out.println(getMet.invoke(obj));      // 等价于:Person对象.getName()
    //输出: 张三
} public static String initcap(String str) { return str.substring(0,1).toUpperCase().concat(str.substring(1)) ; } }

在日后的所有框架技术开发之中,简单Java类都是如此应用的,所以必须按照标准进行。

4.3 调用成员

类之中最后一个组成部分就是成员(Field,也可以称为属性),如果要通过反射取得类的成员可以使用方法如下:

·取得本类的全部成员:public Field[] getDeclaredFields() throws SecurityException;

·取得指定的成员:public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException;

这两个方法的返回值类型是java.lang.reflect.Field类的对象,下面首先观察如何取得一个类之中的全部属性。

范例:取得一个类之中的全部属性

package reflection_demo1;

import java.lang.reflect.Field;

class Person {
    private String name;
}
public class Demo{
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("reflection_demo1.Person") ; // 取得Class对象
        Field field [] = cls.getDeclaredFields() ; // 取得全部属性
        for(int x = 0; x < field.length; x++) {
            System.out.println(field[x]);
        }
    }
    
}

输出: private java.lang.String reflection_demo1.Person.name


但是找到Field实际上就找到了一个很有意思的操作,在Field类之中提供了两个方法:

·设置属性内容(类似于:对象.属性= 内容):public void set(Object obj, Object value)

throws IllegalArgumentException, IllegalAccessException;

·取得属性内容(类似于:对象.属性):public Object get(Object obj)

throws IllegalArgumentException, IllegalAccessException

可是从类的开发要求而言,一直都强调类之中的属性必须封装,所以现在调用之前要想办法解除封装。

·解除封装:public void setAccessible(boolean flag) throws SecurityException;

范例:利用反射操作类中的属性

package reflection_demo1;

import java.lang.reflect.Field;

class Person {
    private String name;
}
public class Demo{
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("reflection_demo1.Person"); // 取得Class对象
        Object obj = cls.newInstance(); // 对象实例化 属性才会分配空间
        Field nameField = cls.getDeclaredField("name") ; // 找到name属性
        nameField.setAccessible(true) ; // 解除封装了
        nameField.set(obj, "张三") ; // Person对象.name = "张三"
        System.out.println(nameField.get(obj)); // Person对象.name
    }
    
}

 

posted @ 2017-08-28 15:29  小猫慢慢爬  阅读(348)  评论(0编辑  收藏  举报