java学习技术干货JavaEE 学习专题

深入理解Java反射(一)

2017-10-31  本文已影响50人  情天孽海

Class
在面向对象中,万物皆对象,我们把人、动物等都抽象成类,将类具体化就成了对象,比如new Studen() 学生是个类,类里面指定的某一个学生就是研究的对象。
在java中类用class来表示
比如定义一个类:

public class Studen {
}

万物抽象可以用class来表示,那么class是不是也属于一个类型呢,所以java将所有的class都用一个类型(Class)来表示,Class(注意大小写)就是用来表示类的类型

就像Studen属于class,那么Student虽在的类的类型用Class表示,一句话,Class是所有类的类型,包括Object是一个class,Object对应的类的类型是Class
笔者认为也可以理解为Class是所有类的字节码对象。负责管理字节码的。

java的原文件以.java为后缀,编译后的字节码以.class为后缀

怎么创建一个类?
在java里创建类的常规方式是通过new这个关键字来创建的。

 Studen studen = new Studen();

还可以通过Class来创建,
每个.class文件都必须先由JVM加载到内存,反射中只要能读取到这个.class文件就能创建一个对象。
java中有三种方式可以获得自己的Class

        //1.通过类名.class
        Class clazz1 = Studen.class;
        //2.通过对象引用.getClass()
        Class clazz2 = studen.getClass();
        //3.通过类的完整路劲名
        Class clazz3 = Class.forName("com.jiuletech.app.Studen");

这里clazz1 clazz2 clazz3都是Class的对象,完全相同

System.out.println(clazz1 == clazz2);
System.out.println(clazz2== clazz3);

可以看到控制台打印两个true,说明都是同一个Class,即上面说的同一份字节码,就算你创建N个对象,内存中依旧只有一份字节码,上面三个Class指向的都是同一份字节码,因此为true

控制台打印

得到了Class类类型,就可以通过它来创建对象
修改Student,添加方法printName()

public void printName(String name){
        System.out.println("以后请叫我的外国名字:"+name);
    }

演示反射调用方法

  Class clz = null;
        try {
            clz = Class.forName("com.jiuletech.app.Student");
            Student o = (Student) clz.newInstance();
            Method method = clz.getMethod("printName", String.class);
            method.invoke(o, "沃德天·维森莫·拉莫帅·帅德布耀德");
        } catch (Exception e) {
            e.printStackTrace();
        }
上面代码运行结果

从上面的代码我们可以看到核心的四步:

  1. 通过Class.forName("完整路劲名")获取到Class对象
  2. 调用Class的newInstance()方法
    这个方法会将上一步加载的字节码实例化出来,相当于平时的new
  3. 通过getMethod("方法名","方法参数类型")获取到对象中的方法
  4. 调用invoke("哪个对象","方法参数")执行Student里面的printName方法。

通过简单的四步,在只知道一个对象完整路劲名的情况下就可以随意玩弄这个对象。


体验了反射的强大之处在来扩展反射常用的API


反射之-构造函数

Student添加一个参数的构造函数

 public Student(String name) {
        this.name = name;
        System.out.println("构造函数执行:"+name);
    }

这里我们再用上面的clz.newInstance()就会报错,因为定义了一个有参构造函数,默认的无参构造函数就没有了,就会早不到这个方法。
怎么调用有参的构造函数呢?

try {
            Constructor constructor = clz.getConstructor(String.class);
            Object o = constructor.newInstance("我的外国名字");
        } catch (Exception e) {
            e.printStackTrace();
        }

通过getConstructor()传入参数类型,得到这个构造函数,然后调用newInstance()传入参数,就会创建这个对象,构造函数就会首先执行。
看看打印的日志是不是这样?

打印日志

其他API介绍

首先修改Student类:

/**
 * Created by on 2017/10/31.
 */
public class Student {

    private String name;
    private int 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 void printName(String name){
        System.out.println("以后请叫我的外国名字:"+name);
    }

    private void printAge(int age){
        System.out.println("我今年"+age+"岁了");
    }
}

getMethods()测试

Method[] methods = clz.getMethods();
            for (Method method : methods) {
                System.out.println(method.getName());
            }
上面代码执行结果

通过日志可以看出getMethods()获取了Student中除了私有方法外的所有方法,包括父类的

getDeclaredMethods()测试

Method[] declaredMethods = clz.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                System.out.println(declaredMethod.getName());
            }
上面代码执行结果
通过上述日志可以看到getDeclaredMethods()获取了Student中所有方法,包括私有方法,但不包括父类中的方法

细心的读者会看到无论是clz.getMethods()还是getDeclaredMethods()都对应有通过参数得到对应的某个方法
例如:得到方法名为setAge,参数类型为int.class的方法

 Student o = (Student) clz.newInstance();
            Method getAge = clz.getMethod("setAge",int.class);
            getAge.invoke(o,23);
            System.out.println(o.getAge());

最后通过打印日志可以看出setAge传入的年龄23生效

上面代码执行结果

查看getMethod(...)源码

getMethod(...)源码
第一参数name需要传入指定方法名称,后面parameterTypes是一个可变参数,按参数的类型和顺序依次传参,getDeclaredMethod(...)方法使用类似,可自行动手练习

获取属性
Student修如下

public class Student {

    private String name;
    private int age;
    protected String hobby;
    public String specialTalents;




    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 void printName(String name){
        System.out.println("以后请叫我的外国名字:"+name);
    }

    private void printAge(int age){
        System.out.println("我今年"+age+"岁了");
    }

    protected void testProtected(){
       System.out.println("testProtected");
    }

}

getFields()测试

Field[] fields = clz.getFields();
      for (Field field : fields) {
            System.out.println(field.getName());
         }
上面代码运行结果
通过运行结果可以看出,只有公有属性被获取到了
获取属性和获取方法类似,可以通过传入参数获取到对应的属性。读者可自行练习

怎么获取私有属性和方法
要获取私有的就调用带getDeclaredxxxx(),Declared的意思是申明的,公开的,需要注意的是getDeclaredxxxx()可以拿到所有私有的,能获取但不一定能访问。

 Field[] fields = clz.getDeclaredFields();
            for (Field field : fields) {
                System.out.println(field.getName());
            }
上面代码运行结果

访问name属性

Object o = clz.newInstance();
            Field[] fields = clz.getDeclaredFields();
            for (Field field : fields) {
                if(field.getName().equals("name")){
                    field.set(o,"我的天");
                }
                System.out.println(field.getName());
            }
上面代码运行结果
异常提示,不能使用私有属性
怎么获取私有属性?
Object o = clz.newInstance();
            Field[] fields = clz.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                if(field.getName().equals("name")){
                    field.set(o,"我的天");
                    System.out.println(((Student)o).getName());
                }
            }
上面代码运行结果
结果可以看到运行成功。
上面的代码只加了field.setAccessible(true);,表示可以访问这个私有属性,然后强转对象打印姓名,发现更改成功。私有方法的访问也是类似的。

关于通过反射怎么得到属性和方法就介绍到这里。
敬请期待下一篇深入理解Java反射(二)
谢谢!

上一篇 下一篇

猜你喜欢

热点阅读