初步认识 Java 反射

651 阅读8分钟

这是我参与8月更文挑战的26天,活动详情查看:8月更文挑战

JAVA反射机制

JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

Java的反射机制允许编程人员在对类未知的情况下,获取类相关信息的方式变得更加多样灵活,调用类中相应方法,是Java增加其灵活性与动态性的一种机制

功能

  • 1、获得某个对象的属性。 通过getClass()方法获得某个对象的类,然后实例化一个Field对象,接收类声明的属性,最后通过get()方法获得该属性的实例,注意,这里的属性必须为公有的,否则将会报illegalAeeessException的异常。
  • 2、获得某个类的静态属性。 首先根据类名获得该类,同获得某个对象的属性一样,通过实例化一个Field对象,接收该类声明的属性,不同的是,由于属性是静态的,所以直接从类中获取。
  • 3、执行某对象的方法。 同样要先获得对象的类,然后配置类的数组,并把它作搜索方法的条件。通过getMethod()方法,得到要执行的方法。执行该invoke方法,该方法中执行的是owner对象中带有参数args的方法。返回值是一个对象。
  • 4、执行某个类的静态方法。 基本的原理和“执行某个类的方法”相同,不同点在于method.invoke(null,args),这里的第一个参数是null,因为调用的是静态方法,所以不需要借助owner就能运行。
  • 5、新建类的实例。 我们利用执行带参数的构造函数的方法来新建一个实例。如果不需要参数,可以直接使用newoneClass.newlnstaneeO来实现嘲。同样要先得到要构造的实例的类,然后构造参数的类数组,构造器是通过getConstructor(argsClass)得到的,最后使用newlnstanee(args)方法新建一个实例。

使用

首先,我们创建一个类来供我们测试使用:

class People {
    private String name;
    private int age;
    public int number = 2008;
    public String color = "red";

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

    People(String name, int age) {
        this.name = name;
        this.age = age;
    }

    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 class ReflectionStudy {
    public static void main(String[] args) {
		//todo 反射相关代码
    }


    private static void print(Object o) {
        if (o != null) {
            System.out.println(o.toString());
        }
    }
}

我们之后都操作都要在这个main函数中进行了, 同时因为java中的打印语句太长,我稍微封装了一个print方法.

通过反射获取Class

在java中, 有3种方式获取一个反射类:

  1. object.getClass();
public static void main(String[] args) {

        People people = new People("bob", 18);

        Class<?> c = people.getClass();

        print(c.getName());       //zhuoyuan.li.kotlinstudy.study.People
        print(c.getSimpleName()); //People
    }

可以看到 , 成功打印出了类名以及包名.

  1. Class.forName("包名.类名")
    public static void main(String[] args) {
        Class<?> c = null;
        //  People people = new People("bob", 18);
        //c = people.getClass();

        try {
            c = Class.forName("zhuoyuan.li.kotlinstudy.study.People");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        if (c != null) {
            print(c.getName());  //zhuoyuan.li.kotlinstudy.study.People
        }
    }

我注释掉了之前获取类的代码 , 并补充了一下逻辑的完整性 . 成功的在控制台打出了类的相关信息.

  1. Object.Class
    public static void main(String[] args) {

        //  People people = new People("bob", 18);
        //c = people.getClass();

       /* try {
            c = Class.forName("zhuoyuan.li.kotlinstudy.study.People");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }*/
        Class<?> c = null;
        c = People.class;

        print(c.getName());  //zhuoyuan.li.kotlinstudy.study.People
    }

综上 , 这三种方法都可以获取到类的信息 , 根据实际情况选用对应的方法即可 .

Class提供的方法

方法用途
asSubclass(Class clazz)把传递的类的对象转换成代表其子类的对象
Cast把对象转换成代表类或是接口的对象
getClassLoader()获得类的加载器
getClasses()返回一个数组,数组中包含该类中所有公共类和接口类的对象
getDeclaredClasses()返回一个数组,数组中包含该类中所有类和接口类的对象
forName(String className)根据类名返回类的对象
getName()获得类的完整路径名字
newInstance()创建类的实例
getPackage()获得类的包
getSimpleName()获得类的名字
getSuperclass()获得当前类继承的父类的名字
getInterfaces()获得当前类实现的类或是接口
getField(String name)获得某个公有的属性对象
getFields()获得所有公有的属性对象
getDeclaredField(String name)获得某个属性对象
getDeclaredFields()获得所有属性对象
getDeclaredConstructors()获得该类所有构造方法
getConstructors()获得该类的所有公有构造方法
get(Object obj)获得obj中对应的属性值(私有属性会报错)
set(Object obj, Object value)设置obj中对应属性值
invoke(Object obj, Object... args)传递object对象及参数调用该对象对应的方法
接下来重点介绍一下获取属性和对象的方法:

getFields() 获得所有公有的属性对象

    public static void main(String[] args) {

        People people = new People("bob", 18);

        Class<?> c = people.getClass();

        print(c.getName());  //zhuoyuan.li.kotlinstudy.study.People

        Field[] fields = c.getFields();

        for (Field field : fields) {
            print(field);
        }
    }

依然是用上面的类 , 调用了类对象的getFields()方法 , 然后我们看一下打印结果:

public          int         zhuoyuan.li.kotlinstudy.study.People.number
public   java.lang.String   zhuoyuan.li.kotlinstudy.study.People.color

正好看一下Field类的toString方法是什么样的:

    public String toString() {
        int mod = getModifiers();
        return (((mod == 0) ? "" : (Modifier.toString(mod) + " "))
            + getType().getTypeName() + " "
            + getDeclaringClass().getTypeName() + "."
            + getName());
    }

有两行打印 , 刚好对应我们People类中的两个public属性 , 并且是用空格隔开的 (本来空格没这么多, 为了方便看, 我自己多加了几个), 一共是3个数据:

因为我们调用的getFields获取的就是公有属性, 所以返回的类型是public

后面分别是属性的类型和属性名 , 可以看到和我们代码里的是一致的.

getField(String name)

当我们想看这个类有没有某个属性的时候 , 就可以使用这个方法 .

当我拿到类的时候 , 我想看看它是否有numberid 这两个属性 稍微改一下代码:

public static void main(String[] args) {

        People people = new People("bob", 18);

        Class<?> c = people.getClass();

        Field f = null;
        Field f1 = null;
        
        try {
            f = c.getField("number");
          
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        print(f);
        
        try {
            f1 = c.getField("id");
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
      
        print(f1);
    }

然后看一下控制台的打印信息.

public int zhuoyuan.li.kotlinstudy.study.People.number
java.lang.NoSuchFieldException: id
	at java.lang.Class.getField(Class.java:1703)
	at zhuoyuan.li.kotlinstudy.study.ReflectionStudy.main(ReflectionStudy.java:25)

number我们是知道有的, 所以成功打印了相关信息 , 跟上面的一样就不解释了 但是id我们没有定义过这个属性 ,所以它报了错NoSuchFieldException : 表示该类没有指定名称的字段.

getDeclaredFields() 获取类所有的属性

    public static void main(String[] args) {

        People people = new People("bob", 18);

        Class<?> c = people.getClass();

        Field[] f;

        f = c.getDeclaredFields();

        for (Field field : f) {
            print(field);
        }
    }

这里跟上面其实是一样的, 只是把getFields替换成了getDeclaredFields而已 , 看一下打印信息:

private java.lang.String zhuoyuan.li.kotlinstudy.study.People.name
private int zhuoyuan.li.kotlinstudy.study.People.age
public int zhuoyuan.li.kotlinstudy.study.People.number
public java.lang.String zhuoyuan.li.kotlinstudy.study.People.color

刚好对应我们People中定义的4个属性 .

然后我们稍微改动一下这个for循环 , 调用 feild.get()方法 :

        for (Field field : f) {
            try {
                print(field.get(people));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

打印结果:

java.lang.IllegalAccessException: Class zhuoyuan.li.kotlinstudy.study.ReflectionStudy can not access a member of class zhuoyuan.li.kotlinstudy.study.People with modifiers "private"
	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
	at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
	at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
	at java.lang.reflect.Field.get(Field.java:390)
	at zhuoyuan.li.kotlinstudy.study.ReflectionStudy.main(ReflectionStudy.java:20)
java.lang.IllegalAccessException: Class zhuoyuan.li.kotlinstudy.study.ReflectionStudy can not access a member of class zhuoyuan.li.kotlinstudy.study.People with modifiers "private"
	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
	at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
	at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
	at java.lang.reflect.Field.get(Field.java:390)
	at zhuoyuan.li.kotlinstudy.study.ReflectionStudy.main(ReflectionStudy.java:20)
2008
red

只打印了两条public的属性的值 , private的给出了报错信息 , 告诉我们不能访问private的成员.

那如果我想获取私有属性呢?

只要在使用之前去掉类型检查就可以了. 只要一行代码:

        for (Field field : f) {
            try {
            //添加下面这一行
                field.setAccessible(true);
                print(field.get(people));
            } catch (IllegalAccessException e) {
                e.printStackTrace();

            }
        }

setAccessible(true) 并不是将方法的访问权限改成了public,而是取消java的权限控制检查. 所以即使是public方法,其accessible 属相默认也是false.

getDeclaredConstructors() 获取类的所有构造方法

        Constructor<?>[] constructors;

        constructors = c.getDeclaredConstructors();

        for (Constructor constructor : constructors) {
            print(constructor);
        }

其实我们只有一个私有的构造方法, 理论上只会有一条数据 , 而且因为是私有的, 通过getConstructors 来获取公有构造方法的时候会返回一个空数组.

zhuoyuan.li.kotlinstudy.study.People(java.lang.String,int)

可以看到返回了一个People() , 并且有两个参数 分别为String和int , 也正是我们定义的唯一一个构造方法.

特点

尽管反射机制带来了极大的灵活性及方便性,但反射也有缺点。反射机制的功能非常强大,但不能滥用。在能不使用反射完成时,尽量不要使用,原因有以下几点:

1、性能问题。 Java反射机制中包含了一些动态类型,所以Java虚拟机不能够对这些动态代码进行优化。因此,反射操作的效率要比正常操作效率低很多。我们应该避免在对性能要求很高的程序或经常被执行的代码中使用反射。而且,如何使用反射决定了性能的高低。如果它作为程序中较少运行的部分,性能将不会成为一个问题。

2、安全限制。 使用反射通常需要程序的运行没有安全方面的限制。如果一个程序对安全性提出要求,则最好不要使用反射。

3、程序健壮性。 反射允许代码执行一些通常不被允许的操作,所以使用反射有可能会导致意想不到的后果。反射代码破坏了Java程序结构的抽象性,所以当程序运行的平台发生变化的时候,由于抽象的逻辑结构不能被识别,代码产生的效果与之前会产生差异。

参考:

  1. 百度百科
  2. Java高级特性——反射
  3. 【Android】 认识反射机制(Reflection)