Java反射机制及其应用

反射机制是Java语言中的一个非常重要的特性,它允许程序在运行时进行自我检查,同时也允许对其内部的成员进行操作。由于反射机制能够实现在运行时对类进行装载,因此能够增加程序的灵活性,但是不恰当地使用反射机制,也会严重影响系统的性能。
具体而言,反射机制提供的功能主要有:得到一个对象所属的类;获取一个类的所有成员变量和方法;在运行时创建对象;在运行时调用对象的方法。

一、什么是反射?

在运行时,对于任意一个类,都能够获取到这个类的所有成员属性和方法;对于任意一个对象,都能够调用这个对象的任意属性和方法(包括public和private),这种动态获取信息和动态调用对象方法的功能就称为Java语言的反射机制。即,通过反射机制,该类对于我们而言是完全透明的,想要获取任何东西都可以。
想要使用反射机制,必须首先获取这个类的字节码文件对象,通过字节码文件对象,就能够通过Class类中的方法动态地获取到我们想要的信息(类名String,属性Field,方法Method,构造器Constructor,父类或者实现的接口Class等),每一个类都对应着一个字节码文件,也就对应着一个Class类型的对象,也就是字节码文件对象。**获取字节码文件对象Class(得到一个对象所属的类)**有三种方式:
(1)Class c1 = Class.forName(“全限定类名即包名.类名”); //通过Class类中的static Class forName(String className)返回具有给定字符串名称的与类或者接口相关联的Class对象,此时该类还是源文件阶段,还没有变成字节码文件。
(2)Class c2 = 类名.class; //通过每个类都具有的class属性,此时该类处于字节码文件阶段。
(3)Class c3 = 对象实例.getClass();//通过每个类都具有的继承自Object类的Class getClass()方法返回this的运行时类对象,即通过该类的对象实例来获取该类的字节码文件对象,此时该类处于创建对象阶段。

二、反射机制可以获取那些信息?

有了字节码文件对象(Class类型的对象)才能获得类中的所有信息。下面详细来看Class类的API文档提供的方法。

2.1 通过字节码文件对象(Class类型的对象)在运行时创建对象实例

(1)static Class forName(String className):返回具有给定字符串名称的与类或者接口相关的Class对象,该方法会抛出ClassNotFoundException异常。
(2)T newInstance():创建此Class对象表示的类的实例(使用的是该类的无参构造方法)。

//通过反射机制在运行时创建对象
Class c = Class.forName("main.User");//返回具有给定字符串名称(全限定类名)的类或者接口关联的Class对象
//使用无参构造函数创建对象实例
User user1 = (User)c.newInstance();//创建Class对象表示的类的对象实例
2.2 通过字节码文件对象(Class类型的对象)获取构造方法

引入Constructor类,需要导包import java.reflect.*:
(1)Constructor< T > getConstructor(Class… parameterTypes):返回一个构造器对象,该对象反应此Class对象表示的类的指定公共构造方法。
(2)Constructor[] getConstructors():返回一个包含构造器对象的数组,这些对象反映了该Class对象表示的类的所有公共构造方法。
(3)Constructor< T > getDeclaredConstructor(Class… parameterTypes):返回一个构造器对象,该对象反应此Class对象表示的类的指定构造方法。
(4)Constructor[] getDeclaredConstructors():返回一个包含构造器对象的数组,这些对象反映了该Class对象表示的类的所有构造方法。

Class c = Class.forName("main.User");//返回具有给定字符串名称(全限定类名)的类或者接口关联的Class对象
//使用有参构造函数创建对象实例
Constructor cons = c.getConstructor(int.class, String.class);
User user2 = (User)cons.newInstance(1,"Lisa");//推荐使用
Constructor[] cons = c.getConstructors();
for(int i = 0;i < cons.length;i++)
{
   Class[] paras = cons[i].getParameterTypes();
   for(int j = 0;j < paras.length;j++)
   {
    System.out.println(paras[j].getName() + ",");
   }
}
2.3 通过字节码文件对象(Class类型的对象)获取一个类的所有成员变量

引入Field类,需要导包import java.reflect.*:
(1)Field getField(String name):返回一个Field对象,此对象反映此Class对象表示的类或者接口的指定公共成员变量(public的)。
(2)Field[] getFields():返回一个包含Field对象的数组,这些对象反映此Class对象表示的类或者接口的所有公共成员变量(public的)。
(3)Field getDeclaredField(String name):返回一个Field对象,此对象反映此Class对象表示的类或者接口的指定成员变量(包括public和private)。
(4)Field[] getDeclaredFields():返回一个包含Field对象的数组,这些对象反映此Class对象表示的类或者接口的所有成员变量(包括public和private)。

Field类中的方法:
(1)void setAccessible(boolean flag):值true表示反射的对象在使用时应禁止检查Java语言访问控制。值false表示所反射的对象在使用时应强制检查Java语言访问控制检查。
(2)void setInt(Object obj, int i):将指定对象obj的int字段的值设置为i。

//获取一个类的属性
Field field = c.getDeclaredField("id");//返回一个Field对象,该对象反映了Class对象表示的类或者接口的指定成员属性
//由于id属性是private的,所以在使用该反射得到的对象时需要禁止检查Java语言的访问控制
field.setAccessible(true);//设置访问权限
field.setInt(user2, 2);//将指定对象obj的int字段的值设置为i
System.out.println(field.getInt(user2));
2.4 通过字节码文件对象(Class类型的对象)获取一个类的所有成员方法

引入Method类,需要导包import java.reflect.*:
(1)Method getMethod(String name, Class… parameterTypes):返回一个Method对象,该对象反映了Class对象表示的类或者接口的指定公共成员方法(public的)。
(2)Method[] getMethods():返回一个包含Method对象的数组,这些对象反映了Class对象表示的类或者接口的所有公共成员方法(public的)。
(3)Method geDeclaredtMethod(String name, Class… parameterTypes):返回一个Method对象,该对象反映了Class对象表示的类或者接口的指定成员方法(包括public的和private的)。
(4)Method[] getDeclaredMethods():返回一个包含Method对象的数组,这些对象反映了Class对象表示的类或者接口的所有成员方法(包括public的和private的)。

Method类中的方法:
(1)Object invoke(Object obj,Object… args):Method 使用指定的参数args在指定的对象obj上调用此对象的包含指定参数列表的基础方法,如果该方法没有参数则args不写。
(2)void setAccessible(boolean flag):值true表示反射的对象在使用时应禁止检查Java语言访问控制。值false表示所反射的对象在使用时应强制检查Java语言访问控制检查。

//获取一个类的成员方法
Method method = c.getMethod("setName", String.class);
method.invoke(user2, "join");
  
Method[] methods = c.getDeclaredMethods();
for(int i = 0;i < methods.length;i++)
{
   System.out.print(methods[i].getName() + " : ");//返回此Method对象所表示方法的名称
   Class[] paras = methods[i].getParameterTypes();
   for(int j = 0;j < paras.length;j++)
   {
    System.out.print(paras[j].getName() + ", ");
   }
   System.out.println();
}


import
java.lang.reflect.*;
class User
{
    private int id;
    private String name;

    public User(){}
    public User(int id,String name)
    {
        this.id = id;
        this.name = name;
    }

    //对私有属性对外提供访问方式
    public void setId(int id)
    {
        this.id = id;
    }
    public int getId()
    {
        return id;
    }

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

public class ReflectionDemo 
{
    public static void main(String[] args) throws Exception
    {
        //通过反射机制在运行时创建对象
        Class c = Class.forName("main.User");//返回具有给定字符串名称(全限定类名)的类或者接口关联的Class对象

        //使用无参构造函数创建对象实例
        User user1 = (User)c.newInstance();//创建Class对象表示的类的对象实例
        //使用有参构造函数创建对象实例
        Constructor con = c.getConstructor(int.class, String.class);
        User user2 = (User)con.newInstance(1,"Lisa");//推荐使用

        Constructor[] cons = c.getConstructors();
        for(int i = 0;i < cons.length;i++)
        {
           Class[] paras = cons[i].getParameterTypes();
           for(int j = 0;j < paras.length;j++)
           {
               System.out.println(paras[j].getName() + ",");
           }
        }

        //获取一个类的成员变量
        Field field = c.getDeclaredField("id");//返回一个Field对象,该对象反映了Class对象表示的类或者接口的指定成员属性
        //由于id属性是private的,所以在使用该反射得到的对象时需要禁止检查Java语言的访问控制
        field.setAccessible(true);//设置访问权限
        field.setInt(user2, 2);//将指定对象obj的int字段的值设置为i
        System.out.println(field.getInt(user2));

        //获取一个类的成员方法
        Method method = c.getMethod("setName", String.class);
        method.invoke(user2, "join");

        Method[] methods = c.getDeclaredMethods();
        for(int i = 0;i < methods.length;i++)
        {
           System.out.print(methods[i].getName() + "
: ");//返回此Method对象所表示方法的名称
           Class[] paras = methods[i].getParameterTypes();
           for(int j = 0;j < paras.length;j++)
           {
               System.out.print(paras[j].getName() + ",
");
           }
           System.out.println();
        }
    }
}

三、反射的应用?

简单工厂模式:简单工厂模式最大的优点在于工厂类中包含了必要的逻辑判断(switch-case语句),根据客户端选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。
工厂方法模式:定义了一个用于创建对象的接口,让实现该接口的子类决定实例化哪一个类,工厂方法模式将一个类的实例化延迟到其子类。工厂方法模式在实现时,客户端需要决定实例化哪一个工厂来实现产品类,选择判断的问题还是存在的,也就是说,工厂方法模式将简单工厂模式的内部逻辑判断移到了客户端代码中进行。
抽象工厂模式:提供一个创建一系列相关或者相互依赖对象的接口,而无需指定它们具体的类。

如下是通过反射+抽象工厂模式来实现数据访问程序:



package main;
import java.lang.reflect.*;
class User
{
    private int id;
    private String name;

    //对私有属性对外提供访问方式
    public void setId(int id)
    {
        this.id = id;
    }
    public int getId()
    {
        return id;
    }

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

    public String getName()
    {
        return name;
    }
}

class Department
{
    private int id;
    private String name;

    //对私有属性对外提供访问方式
    public void setId(int id)
    {
        this.id = id;
    }
    public int getId()
    {
        return id;
    }

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

    public String getName()
    {
        return name;
    }
}

//抽象产品,它们都有可能有两种不同的实现
interface IUser//操作用户表
{
    public abstract void insertUser(User user);

    public abstract User getUser(int id);
}

interface IDepartment//操作部门表
{
    public abstract void insertDepartment(Department dept);

    public abstract Department getDepartment(int id); 
}

//两种抽象产品的具体分类的实现
class SqlServerUser implements IUser
{
    public void insertUser(User user)
    {
        System.out.println("在SQL Server中给User表增加一条记录");
    }
    public User getUser(int id)
    {
        System.out.println("从SQL Server中获取User表中的一条记录");
        return null;
    }
}

class AccessUser implements IUser
{
    public void insertUser(User user)
    {
        System.out.println("在Access中给User表增加一条记录");
    }
    public User getUser(int id)
    {
        System.out.println("从Access中获取User表中的一条记录");
        return null;
    }
}

class SqlServerDepartment implements IDepartment
{
    public void insertDepartment(Department dept)
    {
        System.out.println("在SQL Server中给Department表增加一条记录");
    }
    public Department getDepartment(int id)
    {
        System.out.println("从SQL Server中获取Department表中的一条记录");
        return null;
    }
}

class AccessDepartment implements IDepartment
{
    public void insertDepartment(Department dept)
    {
        System.out.println("在Access中给Department表增加一条记录");
    }
    public Department getDepartment(int id)
    {
        System.out.println("从Access中获取Department表中的一条记录");
        return null;
    }
}

//去掉IFactory,SqlServerFactory,AccessFactory
//定义一个DataAccess类
class DataAccess
{
    //还可以将以下两个属性的值写道配置文件中,通过配置文件读入
    //包名
    private static String packageName = "main";
    //定义一个私有属性/字段来决定实例化哪一个产品类
    private static String db = "SqlServer";
    //private static String db = "Access";

    /*
    //通过简单工厂模式来简化抽象工厂模式
    public IUser createUser()
    {
        IUser result = null;

        switch(db)
        {
           case "SqlServer":
               result = new SqlServerUser();
               break;
           case "Access":
               result = new AccessUser();
               break;
        }
        return result;
    }

    public IDepartment createDepartment()
    {
        IDepartment result = null;
        switch(db)
        {
           case "SqlServer":
               result = new SqlServerDepartment();
               break;
           case "Access":
               result = new AccessDepartment();
               break;
        }
        return result;
    }
    */

    //用过反射+抽象工厂模式来实现数据访问
    public static IUser createUser() throws Exception
    {
        //全限定类名:包名.类名
        String className = packageName + "." + db + "User";
        /*
        Class c = Class.forName(className);
        User user = (User)c.newInstance();
        return user;
        */
        //得到一个对象所属的类->在运行时创建对象
        return (IUser)Class.forName(className).newInstance();//new main.SqlServerUser()
    }

    public static IDepartment createDepartment() throws Exception
    {
        //全限定类名:包名.类名
        String className = packageName + "." + db + "Department";
        /*
        Class c = Class.forName(className);
        User user = (User)c.newInstance();
        return user;
        */
        //得到一个对象所属的类->在运行时创建对象
        return (IDepartment)Class.forName(className).newInstance();////new main.SqlServerDepartment()
    }
}

public class ReflectionDemo 
{
    //客户端
    public static void main(String[] args) throws Exception
    {
        User user = new User();
        Department dept = new Department();
       
        IUser iu = DataAccess.createUser();
        iu.insertUser(user);
        iu.getUser(1);
    
        IDepartment idept = DataAccess.createDepartment();
        idept.insertDepartment(dept);
        idept.getDepartment(1);
    }
}

所有在用简单工厂模式的地方,都可以考虑反射技术来去除switch或者if,从而解除分支判断带来的耦合。

你可能感兴趣的:(Java基础)