文章506
标签266
分类65

Java反射基础总结

最近用到了动态代理, 在Spring框架中也大量使用了反射来完成Ioc和AOP. 对于反射一直也都是使用, 也没怎么系统的学习. 这篇文章就系统的总结一下在Java中反射的相关机制!

Github源码: https://github.com/JasonkayZK/Java_Samples/tree/java-reflection


Java反射

1. 反射概述

在运行过程中:

  • 对于任意一个: 都能够知道这个类的所有属性和方法;
  • 对于任意一个对象: 都能够调用它的任意一个方法和属性;

这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制

实际上, 我们创建的每一个类也都是对象! 即类本身是java.lang.Class类的实例对象, 被称为类对象!



2. Class对象特点

Class类的API如下图所示:

Class类的API

从图中可以得出以下几点:

  • Class 类的实例对象表示正在运行的 Java 应用程序中的类和接口; 也就是jvm中有很多的实例,每个类都有唯一的Class对象;
  • Class 类没有公共构造方法, Class 对象是在加载类时由 Java 虚拟机自动构造的. 也就是说我们不需要创建,JVM已经帮我们创建了;
  • Class 对象用于提供类本身的信息,比如有几种构造方法, 有多少属性,有哪些普通方法;


3. 反射的使用

假设有一个JavaBean: Hero类

package reflection.pojo;

public class Hero {

    public String name;

    public double hp;

    protected double armor;

    public int speed;

    public String getName() {
        return name;
    }

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

    public double getHp() {
        return hp;
    }

    public void setHp(double hp) {
        this.hp = hp;
    }

    public double getArmor() {
        return armor;
    }

    public void setArmor(double armor) {
        this.armor = armor;
    }

    public int getSpeed() {
        return speed;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    @Override
    public String toString() {
        return "Hero{" +
                "name='" + name + '\'' +
                ", hp=" + hp +
                ", armor=" + armor +
                ", speed=" + speed +
                '}';
    }

}

1): 获取类对象

获取类对象的方法有3种:

  • Class.forName() [常用]
  • Hero.class
  • new Hero().getClass()

在一个JVM中, 同一个ClassLoader引导创建的类, 只会有一个类对象存在!

所以对于上述三个方法来说, 都是使用的AppClassLoader引导创建的, 所以产生的类对象都相同:

package reflection.chapter3.getClass;

import reflection.pojo.Hero;

public class GetClassDemo {

    public static void main(String[] args) {
        String className = "reflection.pojo.Hero";

        try {
            Class clazz1 = Class.forName(className);
            Class clazz2 = Hero.class;
            Class clazz3 = new Hero().getClass();

            System.out.println(clazz1 == clazz2);
            System.out.println(clazz2 == clazz3);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

}

输出为:

true
true

三种方法比较:

  • 使用Class.forName()静态方法最常用!
  • 使用Hero.class方法需要导入对于的类的包!
  • 使用new方法已经直接new出了对象, 一般不再需要反射了!

一般都第一种,一个字符串可以传入也可写在配置文件中等多种方法.



2): 利用反射创建对象

与传统的通过new来获取对象的方法不同: 反射会先拿到Hexo的”类对象”, 然后通过类对象获取”构造器对象”, 再通过构造器对象创建一个对象!

如, 使用默认的构造器方法构造对象:

/*
    1.获取类对象 Class clazz = Class.forName("reflection.pojo.Hero");
    2.获取构造器对象 Constructor con = clazz.getConstructor(形参.class);
    3 获取对象 Hero hero =con.newInstance(实参);
*/
package reflection.chapter4.constructObject;

import reflection.pojo.Hero;

import java.lang.reflect.Constructor;

public class DefaultConstructor {

    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("reflection.pojo.Hero");
            Constructor constructor = clazz.getConstructor();

            Hero hero = (Hero) constructor.newInstance();
            System.out.println(hero);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

输出为:

Hero{name='null', hp=0.0, armor=0.0, speed=0}

当Hero的构造方法不是无参构造方法的时候: 需要先获取对应的构造器方法!

如: 获取构造函数构造对象

1.Hero类中添加构造方法

    //---------------构造方法-------------------
    //(默认的构造方法)
    Hero(String str){
        System.out.println("(默认)的构造方法 s = " + str);
    }

    //无参构造方法
    public Hero(){
        System.out.println("调用了公有、无参构造方法执行了。。。");
    }

    //有一个参数的构造方法
    public Hero(char name){
        System.out.println("姓名:" + name);
    }

    //有多个参数的构造方法
    public Hero(String name ,float hp){
        System.out.println("姓名:"+name+"血量:"+ hp);
    }

    //受保护的构造方法
    protected Hero(boolean n){
        System.out.println("受保护的构造方法 n = " + n);
    }

    //私有构造方法
    private Hero(float hp){
        System.out.println("私有的构造方法   血量:"+ hp);
    }

2.通过反射机制获取对象

package reflection.chapter4.constructObject;


import reflection.pojo.Hero;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class SelectConstructor {

    /**
     *
     * 通过Class对象可以获取某个类中的: 构造方法, 成员变量, 成员方法;
     *
     * 并访问成员.
     *
     * 1.获取构造方法:
     *         1).批量的方法:
     *             public Constructor[] getConstructors():所有"公有的"构造方法
     public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)

     *         2).获取单个的方法,并调用:
     *             public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
     *             public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;
     *
     * 2.创建对象
     *         Constructor对象调用newInstance(Object... initargs)
     *
     */
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, ClassNotFoundException {

        // 1. 获取Class对象
        Class clazz = Class.forName("reflection.pojo.Hero");

        // 2. 获取构造方法
        System.out.println("----公有构造方法----");
        Constructor[] constructors = clazz.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }

        System.out.println("----所有的构造方法(包括:私有、受保护、默认、公有)----");
        constructors = clazz.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }

        System.out.println("----获取公有、无参的构造方法----");
        // 1> 因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型!!!!!
        // 2> 返回的是描述这个无参构造函数的类对象.
        Constructor cons = clazz.getConstructor(null);
        System.out.println("consturctor = " + cons);
        // 调用方法
        Object object = cons.newInstance();
        System.out.println("Object: " + (Hero)object);


        System.out.println("----获取私有构造方法,并调用----");
        cons = clazz.getDeclaredConstructor(float.class);
        System.out.println("consturctor = " + cons);
        // 调用方法
        cons.setAccessible(true);
        object = cons.newInstance(100);
        System.out.println("Object: " + (Hero)object);

    }

}

输出为:

----公有构造方法----
public reflection.pojo.Hero()
public reflection.pojo.Hero(char)
public reflection.pojo.Hero(java.lang.String,float)


----所有的构造方法(包括:私有、受保护、默认、公有)----
reflection.pojo.Hero(java.lang.String)
public reflection.pojo.Hero()
public reflection.pojo.Hero(char)
public reflection.pojo.Hero(java.lang.String,float)
protected reflection.pojo.Hero(boolean)
private reflection.pojo.Hero(float)


----获取公有、无参的构造方法----
consturctor = public reflection.pojo.Hero()
调用了公有、无参构造方法执行了...
Object: Hero{name='null', hp=0.0, armor=0.0, speed=0}


----获取私有构造方法,并调用----
consturctor = private reflection.pojo.Hero(float)
私有的构造方法   血量:100.0
Object: Hero{name='null', hp=0.0, armor=0.0, speed=0}

总结:

获取构造器批量的方法:

  • public Constructor[] getConstructors():

    所有”公有的”构造方法

  • public Constructor[] getDeclaredConstructors():

    获取所有的构造方法(包括私有、受保护、默认、公有)


获取构造器单个的方法:

  • public Constructor getConstructor(Class… parameterTypes):

    获取单个的”公有的”构造方法

  • public Constructor getDeclaredConstructor(Class…parameterTypes):

    获取”某个构造方法”可以是私有的,或受保护、默认、公有;



3): 获取成员变量并使用

基本步骤:

  • 获取对象: 通过new或者反射获得对象;
  • 获取属性: Field f1 = hero.getDeclaredField(“属性名”)
  • 修改属性: f1.set(hero, 实参) 此处为对象, 而不是类对象!!!

例1: 获取并修改属性

package reflection.chapter5.param;

import reflection.pojo.Hero;

import java.lang.reflect.Field;

public class GetAndModifyParamDemo {

    public static void main(String[] args) {
        Hero hero = new Hero();

        try {
            // 获取hero的叫做name字段的属性
            Field field = hero.getClass().getDeclaredField("name");
            // 修改属性
            field.set(hero, "teemo");

            System.out.println(hero);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

    }

}

输出为:

Hero{name='teemo', hp=0.0, armor=0.0, speed=0}

补充: getField和getDeclaredField的区别

  • getField: 只能获取public的,包括从父类继承来的字段;

  • getDeclaredField: 可以获取本类所有的字段,包括private的,但是不能获取继承来的字段.

    (注: 这里只能获取到private的字段,但并不能访问该private字段的值,除非加上setAccessible(true))



4): 获取成员方法并使用

基本步骤:

  • 获取对象;

  • 获取成员方法:

    • public Method getMethod(String name ,Class<?>… parameterTypes): 获取”公有方法”; (包含了父类的方法也包含Object类)
    • public Method getDeclaredMethods(String name ,Class<?>… parameterTypes) : 获取成员方法,包括私有的(不包括继承的)

    参数解释:

    • name : 方法名;
    • Class … : 形参的Class类型对象
  • 调用方法:

    Method –> public Object invoke(Object obj,Object… args)
    参数说明

    • obj: 要调用方法的对象;
    • args: 调用方式时所传递的实参;

*实例: *

package reflection.chapter6.method;

import reflection.pojo.Hero;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class InvokeMethodDemo {

    public static void main(String[] args) {
        Hero hero = new Hero();

        Hero heroSet = new Hero();

        try {
            // 获取方法
            Method method = hero.getClass().getMethod("setName", String.class);

            // 对heroSet调用反射方法!
            method.invoke(heroSet, "Garon");
            // 对hero调用常规方法
            hero.setName("Teemo");

            System.out.println("hero: " + hero);
            System.out.println("heroSet: " + heroSet);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

输出结果为:

hero: Hero{name='Teemo', hp=0.0, armor=0.0, speed=0}
heroSet: Hero{name='Garon', hp=0.0, armor=0.0, speed=0}

关于Java方法反射的源码实现的分析: 深入分析Java方法反射的实现原理



5): 获取main方法并使用

*例: *

在Hero中添加main方法:

    public static void main(String[] args) {
        System.out.println("执行main方法");
        for (String arg : args) {
            System.out.println(arg);
        }
    }

通过反射获取main方法, 并执行:

package reflection.chapter7.main;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class InvokeMainDemo {

    public static void main(String[] args) {
        try {
            // 1. 获取Class对象
            Class clazz = Class.forName("reflection.pojo.Hero");

            // 2. 获取main方法
            Method mainMethod = clazz.getMethod("main", String[].class);

            // 3. 调用main方法

            /*
                1. 错误调用
                mainMethod.invoke(null, new String[] {"a", "b", "c"});

                首先,
                    第一个参数: 对象类型, 当方法是静态方法时, 可以为null!
                    第二个参数: String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数

                上述方法会报错:
                    这里拆的时候会将 new String[]{"a","b","c"} 拆成3个对象!!!
                    所以需要将它强转!!!
             */

            mainMethod.invoke(null, (Object)new String[] {"a", "b", "c"}); // 方法一

            mainMethod.invoke(null, new Object[] {new String[] {"a", "b", "c"}}); // 方法二

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

输出结果:

执行main方法
a
b
c


4. 反射的应用:

1): 通过反射读取并运行配置文件内容

首先准备两个业务类:

package reflection.chapter8.settings.service;

public class Service1 {

    public void doService1() {
        System.out.println("Service 1");
    }
}
package reflection.chapter8.settings.service;

public class Service2 {

    public void doService2() {
        System.out.println("Service 2");
    }
}

此时如果需要讲业务方法一切换为业务方法二时, 如果使用非反射方式: 必须修改源代码, 然后重新编译, 运行才可以!

如:

package reflection.chapter8.settings;

import reflection.chapter8.settings.service.Service1;
import reflection.chapter8.settings.service.Service2;

/**
 * 不使用反射时, 需要修改源代码, 并重新编译!!!
 */
public class CommonDemo {

    public static void main(String[] args) {
        // new Service1().doService1();

        // 想要使用service2, 必须修改源码!
        new Service2().doService2();
    }
}

如果使用反射, 将会方便的多!

  • 首先准备一个配置文件, 如: reflection.properties;

    文件存放的是类的名称,和要调用的方法名.

如:

class=reflection.chapter8.settings.service.Service2
method=doService2
  • 测试类中,首先取出类名称和方法名,然后通过反射去调用这个方法;
  • 当需要从调用第一个业务方法,切换到调用第二个业务方法的时候,不需要修改一行代码,也不需要重新编译,只需要修改配置文件,再运行即可

例如: 测试类

package reflection.chapter8.settings;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Properties;

public class ReflectSettingsDemo {

    public static void main(String[] args) {
        Properties properties;
        InputStream in = null;
        try {
            properties = new Properties();
            in = ReflectSettingsDemo.class.getClassLoader().getResourceAsStream("reflection.properties");
            properties.load(in);

            String className = properties.getProperty("class");
            String methodName = properties.getProperty("method");

            // 根据配置类名寻找类对象
            Class clazz = Class.forName(className);

            // 根据方法名, 寻找方法对象
            Method method = clazz.getMethod(methodName);

            // 获取默认无参构造器
            Constructor constructor = clazz.getConstructor();

            // 根据构造器, 实例化对象, 并调用指定方法!
            method.invoke(constructor.newInstance());

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

最后读取配置文件后输出:

Service 2

将配置文件修改为:

class=reflection.chapter8.settings.service.Service1
method=doService1

输出为

Service 1


2): 通过反射越过泛型检查

泛型是在编译期间起作用的。在编译后的.class文件中是没有泛型的。所有比如T或者E类型啊,本质都是通过Object处理的。所以可以通过使用反射来越过泛型!

package reflection.chapter9.genericType;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class AviodGenericTypeCheckDemo {

    @SuppressWarnings({"unchecked", "rawtypes"})
    public static void main(String[] args) throws Exception {
        List<String> list = new ArrayList<>();
        list.add("this");
        list.add("is");

        // List.add(5) 编译报错!

        /* 越过泛型检查! */

        // 获取ArrayList的Class对象, 反射调用add()
        Class listClazz = list.getClass();

        // 获取add()方法
        Method method = listClazz.getMethod("add", Object.class);

        method.invoke(list, 5);

        for (Object obj : list) {
            System.out.println(obj);
        }

    }
}

正常情况下, 由于声明的泛型类型为String, 而向其中加入的是Integer类型, 所以编译器在检查期将报错!

而使用了反射之后, 通过反射调用add()方法将越过编译器类型检查, 而成功加入


最终执行会输出结果:

this
is
5

附录

Github源码: https://github.com/JasonkayZK/Java_Samples/tree/java-reflection


*引用: *

  1. Java基础之—反射(非常重要) ↩︎
  2. 反射有什么用 ↩︎
  3. 深入分析Java方法反射的实现原理


本文作者:Jasonkay
本文链接:https://jasonkayzk.github.io/2019/09/14/Java反射基础总结/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可