Java基础篇笔记(四):Java中的反射机制

一、引入反射概念

在运行状态中,对于任何一个类,我们都能够知道这个类有哪些方法和属性;对于任何一个对象,我们都能够知道它的方法和属性来进行调用。我们把这种动态获取对象信息和调用对象方法的功能称为反射机制。

二、反射的功能

1.获取某个对象的属性。
2.获得某个类的静态属性。
3.执行某对象的方法。
4.执行某个类的静态方法。
5.新建类的实例。

三、获取Class对象

每个类被加载之后,系统就会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类。在Java程序中获得Class对象通常有如下三种方式:
1.使用Class类的forName(String className)静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名(必须添加完整包名)。Class.forName(String className);(最常用的)
2.调用某个类的class属性来获取该类对应的Class对象。例如,className.class将会返回className对应的Class对象。className.class;
3.调用某个对象的getClass()方法。该方法是java.lang.Object类中的一个方法,所以所有的Java对象都可以调用该方法,该方法将会返回该对象所属类对应的Class对象。new className().getClass();
ps:对于第一种和第二种方式都是直接根据类来获取该类的Class对象,相比之下,第二种方法有两种优势(代码更安全:程序在编译阶段就可以检查需要访问的Class对象是否存在。程序性能更好:因为这种方式无须调用方法,所以性能更好。)

四、常见应用

如下为程序获取了ClassTest类对应的Class对象后,通过调用该Class对象的不同方法来得到该Class对象的详细信息:

public class ClassTest {
    private ClassTest(){}
    public ClassTest(String name){
        System.out.println("执行有参数的构造器");
    }
    public void info(){
        System.out.println("执行无参数的info方法");
    }
    public void info(String str){
        System.out.println("执行有参数的info方法"+",其str值为:"+str);
    }
    class Inner{}
    public static void main(String[] args) throws Exception{
        Class<ClassTest> clazz=ClassTest.class;
        Constructor[] ctors=clazz.getDeclaredConstructors();
        System.out.println("ClassTest的全部构造器如下:");
        for(Constructor c:ctors){
            System.out.println(c);
        }
        Constructor[] publicCtors=clazz.getConstructors();
        System.out.println("ClassTest的全部public构造器如下:");
        for(Constructor c:publicCtors){
            System.out.println(c);
        }
        Method[] mtds=clazz.getMethods();
        System.out.println("ClassTest的全部public方法如下:");
        for(Method md:mtds){
            System.out.println(md);
        }
        System.out.println("ClassTest里带一个字符串参数的info方法为:"+clazz.getMethod("info",String.class));
        Class<?>[] inners=clazz.getDeclaredClasses();
        System.out.println("ClassTest的全部内部类如下:");
        for(Class c:inners){
            System.out.println(c);
        }
        Class inClazz=Class.forName("ClassTest$Inner");
        System.out.println("inClazz对应类的外部类为:"+inClazz.getDeclaringClass());
        System.out.println("ClassTest的包为:"+clazz.getPackage());
        System.out.println("ClassTest的父类为"+clazz.getSuperclass());
    }
}
输出结果:
ClassTest的全部构造器如下:
private ClassTest()
public ClassTest(java.lang.String)
ClassTest的全部public构造器如下:
public ClassTest(java.lang.String)
ClassTest的全部public方法如下:
public static void ClassTest.main(java.lang.String[]) throws java.lang.Exception
public void ClassTest.info(java.lang.String)
public void ClassTest.info()
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
ClassTest里带一个字符串参数的info方法为:public void ClassTest.info(java.lang.String)
ClassTest的全部内部类如下:
class ClassTest$Inner
inClazz对应类的外部类为:class ClassTest
ClassTest的包为:package 
ClassTest的父类为class java.lang.Object

不仅仅有这些方法,还有很多其他的方法,可以通过查阅API文档来查看。

使用反射还可以越过泛型的检查,如下代码:

public class QAQTest {
    public static void main(String[] args) throws Exception{
        ArrayList<String> list=new ArrayList<>();
        list.add("???");
        list.add("!!!");
        /*在此处写一list.add(5)报错*/
        /*获取ArrayList的Class对象,反向调用add()方法*/
        Class listClass=list.getClass();
        //获取add()方法
        Method m=listClass.getMethod("add",Object.class);
        //调用add()方法
        m.invoke(list,5);
        for(Object obj:list){
            System.out.println(obj);
        }
    }
}
输出结果:
???
!!!
5

利用反射,创建一个对象:
如下代码实现了一个简单的对象池,该对象池会根据配置文件读取key-value对,然后创建这些对象,并将这些对象放入一个HashMap中:


public class ObjectPoolFactory{
    //定义一个对象池,前面是对象名,后面是实际对象
    private Map<String , Object> objectPool = new HashMap<>();
    //定义一个创建对象的方法,只要传入一个字符串类名,程序可以根据该类名生成Java对象
    private Object createObject(String clazzName) throws Exception
    {
        //根据字符串来获取对应的Class对象
        Class<?> clazz = Class.forName(clazzName);
        //使用clazz对应的默认构造器创建实例(用了newInstance()方法)
        return clazz.getConstructor().newInstance();
    }
    //定义一个根据指定文件来初始化对象池,根据配置文件来创建对象
    public void initPool(String fileName)
    {
        try(FileInputStream fis = new FileInputStream(fileNmae))
        {
            Properties prop = new Properties();
            prop.load(fis);
            for(String name : props.stringPropertyNames())
            {
                //每取出一对key-value对,就根据value创建一个对象
                //调用上面的createObject()创建对象,并将对象添加到对象池中
                objectPool.put(name , createObject(props.getProperty(name)));
            }
        }
        catch(Exception e)
        {
            System.out.println("读取" + fileNmae + "异常");
        }
    }
    public Object getObject(String name)
    {
        //从对象池中取出name对应的对象
        return objectPool.get(name);
    }
    public static void main(String[] args) throws Exception
    {
        ObjectPoolFactory opf = new ObjectPoolFactory();
        /* 提供一个obj.txt配置文件,内容为
           a=java.util.Date
           b=javax.swing.JFrame */
        opf.initPool("obj.txt");
        System.out.println(opf.getObject("a"));
        System.out.println(opf.getObject("b"));
    }
}
输出结果:
系统当前时间和一个JFrame对象。

在很多Java EE框架中都需要根据配置文件信息来创建Java对象,从配置文件读取的只是某个类的字符串类名,程序需要根据该字符串来创建对应的实例,就必须使用反射。这种使用配置文件来配置对象,然后由程序根据配置文件来创建对象的方式非常有用,Spring框架就采用这种方式大大Java EE应用的开发(Spring采用的是XML配置文件)。


利用反射访问成员变量值:通过Class对象的getFields()或getField()方法可以获取该类所包含的全部成员变量或指定成员变量。Field还提供了了两组方法来读取或设置成员变量值(getXxx(Object obj)获取obj对选哪个的该成员变量的值。此处的Xxx对应8种基本类型,如果该成员变量的类型是引用类型,就直接get;setXxx(Object obj,Xxx val)将obj对象的该成员设置成val值。如果该成员变量的类型是引用类型,则取消set后面的Xxx)使用这两个方法可以随意的访问指定对象的所有成员变量,包括private修饰的。

class Person
{
    private String name;
    private int age;
    public String toString()
    {
        return "Person[name:" + name + ",age:" + age + "]";
    }
public class FieldTest
{
    public static void main(String[] args) throws Exception
    {
        Person p = new Person();
        //获取Person类对应的Class对象
        Class<Person> personClazz = Person.class;
        //获取Person的名为name的成员变量
        //使用getDeclaredField()方法表明可获取各种访问控制符的成员变量
        Field nameField = personClazz.getDeclaredField("name");
        //设置通过反射访问该成员变量时取消访问权限检查,即可以访问private
        nameField.setAccessible(true);
        //调用set()方法为p对象的name成员变量设置值
        nameField.set(p , "huanghe");
        //获取Person的名为age的成员变量
        Field ageField = personClazz.getDeclaredField("age");
        ageField.setAccessible(true);
        ageField.setInt(p , 18);
        System.out.println(p);
    }
}
输出结果:
Person[name:huanghe,age:18]

五、反射的特点

尽管反射机制带来了极大的灵活性及方便性,但是反射也有许多缺点。反射机制的功能非常强大,但不能滥用。在能不使用反射就不要使用

  • 性能问题:Java反射机制中包含了一些动态类型,所以Java虚拟机不能够对这些动态代码进行优化。因此,反射操作的效率要比正常操作效率低很多。我们应该避免在对性能要求很高的程序或经常被执行的代码中使用反射。而且如何使用反射决定了性能的高低。如果它作为程序中较少运行的一部分,性能将不会成为一个问题。
  • 安全限制:使用反射通常需要程序的运行没有安全方面的限制。如果一个程序对安全性提出要求,最好不要使用反射。
  • 程序健壮性:反射允许代码执行一些通常不被允许的操作,所以使用反射有可能会导致意想不到的后果。反射代码破坏了Java程序结构的抽象性,所以当程序运行的平台发生变化的时候,由于抽象的逻辑就够不能被识别,代码产生的效果与之前会产生差异。

不过反射也有许多好处:

  • 反射机制极大的提高了程序的灵活性和扩展性,降低模块的耦合性,提高自身的适应能力。
  • 通过反射机制可以让程序创建和控制任何类的对象,无须提前硬编码目标类。
  • 使用反射机制能够在运行时构造一个类的对象,判断一个类所具有的的成员变量和方法,调用一个对象的方法并生成动态代理。
  • 反射机制时构建框架技术的基础所在,使用反射可以避免将代码写死在框架中。

猜你喜欢

转载自blog.csdn.net/laobanhuanghe/article/details/98180175