仓库源文站点原文


title: Java反射基础总结 toc: true date: 2019-09-14 15:31:24 cover: https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1568457978094&di=90a3d25913f9e02d4b7637fa398040af&imgtype=0&src=http%3A%2F%2Fimg0.ph.126.net%2Fwa7MCE5KpwNALpiy-QwXtw%3D%3D%2F6619114974793209336.jpg categories: 学习案例 tags: [反射, 学习案例]

description: 有关Java中反射的相关总结知识!

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

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

<br/>

<!--more-->

Java反射

1. 反射概述

在运行过程中:

<font color="#ff0000">这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制</font>

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

<br/>


2. Class对象特点

Class类的API如下图所示:

Class类的API

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

<br/>


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): 获取类对象

<font color="#ff0000">获取类对象的方法有3种:</font>

<font color="#ff0000">在一个JVM中, 同一个ClassLoader引导创建的类, 只会有一个类对象存在!</font>

所以对于上述三个方法来说, 都是使用的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

三种方法比较:

<font color="#ff0000">一般都第一种,一个字符串可以传入也可写在配置文件中等多种方法.</font>

<br/>


2): 利用反射创建对象

与传统的通过new来获取对象的方法不同: <font color="#0000ff">反射会先拿到Hexo的"类对象", 然后通过类对象获取"构造器对象", 再通过构造器对象创建一个对象!</font>

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

/*
    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}

<br/>

<font color="#ff0000">当Hero的构造方法不是无参构造方法的时候: 需要先获取对应的构造器方法!</font>

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

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}

<br/>

总结:

获取构造器批量的方法:

<br/>

获取构造器单个的方法:

<br/>


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

基本步骤:

例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}

<br/>

补充: getField和getDeclaredField的区别

<br/>


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

基本步骤:

实例:

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}

<br/>

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

<br/>


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

<br/>


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");
    }
}

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

如:

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();
    }
}

<br/>

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

如:

class=reflection.chapter8.settings.service.Service2
method=doService2

<br/>

例如: 测试类

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

<br/>


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

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

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类型, 所以编译器在检查期将报错!

<font color="#ff0000">而使用了反射之后, 通过`反射调用add()方法`将越过编译器类型检查, 而成功加入</font>

<br/>

最终执行会输出结果:

this
is
5

<br/>

附录

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


引用:

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

<br/>