Java系列 - 反射

  • 是什么:什么是反射?
  • 为什么:为什么会有反射?
  • 怎么办:如何使用反射
  • 在哪里使用?

什么是反射?

  • 正常情况下:先有类,然后有对象
import java.util.Date;//先有类

public class ReflectTest1 {
    public static void main(String[] args) {
        Date date = new Date();//后有对象
        System.out.println(date);
    }
}
  • 反射中的意思是: 通过对象来找类
import java.util.Date;

public class ReflectTest2 {
    public static void main(String[] args) {
        // 对象
        Date date = new Date();

        // 找类
        System.out.println(date.getClass());
    }
}

为什么会有反射?

反射其实是一种语言特性,设计反射的目的是为了溯源:找到源头类,然后使用源头类。

大部分情况都可以通过 new一个对象 来使用类以及类上方法,但是有些情况下无法使用 new 来创建对象,因为类是再运行时动态创建的,压根就不存在,此时就是反射上场了。
当然我们先用简单的例子来讲解。

正常使用一个类的方式是: 首先先有这个类,然后通过new ClassName()来创建这个类的对象,然后就能够借助这个对象调用这个类上面的方法。

代码演示:实现一个唱歌的功能类

package com.ifdom.reflection;

/**
 * 1. 先有 Song 这个歌曲类
 **/
class Song {
    public void sing() {
        System.out.println("我正在唱歌");
    }
}

/**
 * 2.然后通过 `new Song()` 创建出对象 song,借助 song 对象来调用类上面的方法 sing()
 **/
public class Reflection1 {
    public static void main(String[] args) {
        Song song = new Song();
        song.sing();
    }
}

而反射的使用方式有所不同,需要理解:正常使用一个类,等于我们已经有了工具箱,并且知道工具箱上有哪些方法(工具)了,可以直接使用。

反射是需要先找到,先找到,先找到:先把工具箱(类)找到,把工具箱中的工具(构造方法,属性,方法)找到,找到之后才能使用。

如何使用反射?- 01 溯源

反射首先要溯源(寻找对象是由哪个类产生),你只有找到了源头,歌曲类,锤子类,镰刀类,工具箱类…找到了源头,才能调用源头类的属性和方法,否则调用空气呢?

反射溯源有3种方法:

  • 1.通过对象上的 anyObject.getClass() 方法
package com.ifdom.reflection;

/**
 * Reflection
 **/
public class Reflection1 {
    public static void main(String[] args) {
        Song song = new Song();

        Class<?> oneClass = song.getClass();

        // 结果:class com.ifdom.reflection.Song
        System.out.println(oneClass);
    }
}

class Song {
    public void sing() {
        System.out.println("我正在唱歌");
    }
}
  • 2.通过每个类都有的属性 AnyClass.class
package com.ifdom.reflection;

class Song {
    public void sing() {
        System.out.println("我正在唱歌");
    }
}

/**
 * Reflection
 **/
public class Reflection1 {
    public static void main(String[] args) {
        Song song = new Song();

        Class<Song> oneClass = Song.class;

        // 结果:class com.ifdom.reflection.Song
        System.out.println(oneClass);
    }
}
  • 3.通过最底层的Class类提供的 forName()方法每个类都有的属性 Class.forName("AnyClassName")
package com.ifdom.reflection;

class Song {
    public void sing() {
        System.out.println("我正在唱歌");
    }
}

/**
 * Reflection
 **/
public class Reflection1 {
    public static void main(String[] args) throws ClassNotFoundException {
        Song song = new Song();

        Class<?> oneClass = Class.forName("com.ifdom.reflection.Song");

        System.out.println(oneClass);
    }
}

如果你不瞎,那么你应该发现了第三种方法要求必须抛出一个异常。
这是为什么?因为我们是在溯源,溯源,溯源,我们对要找的那个类不甚了解,所以有可能失误,导致要寻找的那个类并不存在
所以会抛出 ClassNotFoundException 没有找到类异常

细心的同学应该发现了,在上面三种溯源方法里有一点差异:

  • 第一种是在已经有 初始化 了一个对象 song 的前提下去溯源。
  • 第二种是已知类名称为Song,通过类名称去溯源
  • 而第三种方法,是在我们只知道 类的包路径 的情况下去溯源。

此时,思考一下,什么时候使用哪种方法去溯源?

别犹豫,你可以相信自己的思考结果。

如何使用反射?- 02 生成实例

现在我们已经知道如何溯源,接下来该如何通过反射溯源后的对象来使用 源头类的 属性和方法呢?

  1. 调用溯源后的类方法 newInstance()生成实例对象;

由于第一和第二种溯源方法已经有 song 对象了,可以直接调用对象上的方法 song.sing(),没必要再经过反射。这里使用第三种溯源方法演示

package com.ifdom.reflection;

class Song {
    private String title;

    public void sing() {
        System.out.println("我正在唱歌" + this.title);
    }
}

/**
 * Reflection
 **/
public class Reflection1 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {

        Class<?> oneClass = Class.forName("com.ifdom.reflection.Song");

        System.out.println(oneClass);

        Object instance = oneClass.newInstance();

        Song song = (Song) instance;

        song.sing();
    }
}

通过上面的演示会发现,通过反射来创建实例过于麻烦,写了3行代码,通过new反而只需要一行代码,但是这种方式有一个好处,避免了使用new,而 new 是代码耦合的元凶。

比如:在工厂模式中,现在想知道一首歌曲作者的年龄

package com.ifdom.reflection;

interface Author {
    void age();
}

class Song implements Author {
    @Override
    public void age() {
        System.out.println("我的年龄18岁");
    }
}

class Company {
    public static Author getInstance(String className) {
        if ("Song" == className) {
            return new Song();
        }
        return null;
    }
}

/**
 * Reflection
 **/
public class Reflection1 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {

        Author song = Company.getInstance("Song");
        song.age();
    }
}

如果 现在公司又有了舞蹈,想知道这个舞蹈作者的年龄.那么不得不修改 Company.getInstance方法中的内容

package com.ifdom.reflection;

interface Author {
    void age();
}

class Song implements Author {
    @Override
    public void age() {
        System.out.println("我的年龄18岁");
    }
}

class Dance implements Author {
    @Override
    public void age() {
        System.out.println("我的年龄88岁");
    }
}

class Company {
    public static Author getInstance(String className) {
        if ("Song" == className) {
            return new Song();
        } else if ("Dance" == className) {
            return new Dance();
        }
        return null;
    }
}

/**
 * Reflection
 **/
public class Reflection1 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {

        Author song = Company.getInstance("Song");
        song.age();

        Author dance = Company.getInstance("Dance");
        dance.age();
    }
}

而使用反射则可以解耦,让你无需改动 Company.getInstance,却依然可以添加额外功能

package com.ifdom.reflection;

interface Author {
    void age();
}

class Song implements Author {
    @Override
    public void age() {
        System.out.println("我的年龄18岁");
    }
}

class Dance implements Author {
    @Override
    public void age() {
        System.out.println("我的年龄88岁");
    }
}

class Company {
    public static Author getInstance(String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Author instance = null;

        try {
            Class<?> oneClass = Class.forName(className);
            instance = (Author) oneClass.newInstance();
            return instance;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

/**
 * Reflection
 **/
public class Reflection1 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        
        Author song = Company.getInstance("com.ifdom.reflection.Song");
        song.age();

        Author dance = Company.getInstance("com.ifdom.reflection.Dance");
        dance.age();
    }
}

如何使用反射?- 03 调用构造方法

在上面使用 newInstance() 创建实例时,Song 使用的都是无参构造函数
如果一个类只有有参构造函数,又或者有多个有参构造函数,构造函数有一个参数,有2个参数,有3个参数…
那么,想要调用 newInstance() 创建实例,就需要先借助 Class类提供的构造方法,主要有2种

  1. 获取指定构造函数 public Constructor<?> getConstructor() throws NoSuchMethodException,SecurityException;
  2. 获取所有构造函数 public Constructor<?>[] getConstructors() throws SecurityException;
package com.ifdom.reflection;

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

class Song {
    private String title;
    private String city;

    public Song(String title) {
        this.title = title;
    }

    public Song(String title, String city) {
        this.title = title;
        this.city = city;
    }
    
    public void sing() {
        System.out.println("我正在唱 " + this.city + this.title);
    }
}

/**
 * Reflection
 **/
public class Reflection1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> oneClass = Class.forName("com.ifdom.reflection.Song");
        
        // 获取一个参数的构造函数
        Constructor<?> constructor = oneClass.getConstructor(String.class);
        Song song = (Song) constructor.newInstance("孤勇者");
        song.sing();

        // 获取两个参数的构造函数
        Constructor<?> constructor1 = oneClass.getConstructor(String.class, String.class);
        Song song1 = (Song) constructor1.newInstance("下一站天后", "港台");
        song1.sing();

        // 获取所有构造函数
        Constructor<?>[] constructorAll = oneClass.getConstructors();
        Arrays.stream(constructorAll).forEach(System.out::println);
    }
}

如何使用反射?-04 调用方法

在上面演示了如何通过反射获取源头类,如何通过反射获取源头类的构造函数,接下来讲解如何通过反射获取源头类上的方法。

在Class类里面提供有以下取得类中Method()的操作:

  1. 取得一个类中的全部方法:public Method[] getMethods() throws SecurityException;
  2. 取得类中指定方法:public Method getMethod(String name,Class<?> ... ParameterTypes) throws NoSuchMethodException,SecurityException;
package com.ifdom.reflection;

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

class Song {
    private String title;
    private String city;

    public void setTitle(String title) { this.title = title;}

    public String getTitle() { return title;}

    public void setCity(String city) {this.city = city;}

    public String getCity() {return city;}

    public void setTitleAndCity(String title, String city) {
        this.title = title;
        this.city = city;
    }

    @Override
    public String toString() {
        return "Song{" +
                "title='" + title + '\'' +
                ", city='" + city + '\'' +
                '}';
    }
}

/**
 * @Author ifredom
 * @Date 2022/6/29
 * @ClassName Reflection1
 * @Version 1.0.0
 * @Description 描述
 **/
public class Reflection1 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class<?> oneClass = Class.forName("com.ifdom.reflection.Song");
        Object instance = oneClass.newInstance();

        // 获取一个方法并调用该方法
        Method setTitleMethod = oneClass.getMethod("setTitle", String.class);
        setTitleMethod.invoke(instance, "洋葱");

        // 获取一个方法并调用该方法
        Method getTitleMethod = oneClass.getMethod("getTitle");
        // 打印结果:洋葱
        System.out.println(getTitleMethod.invoke(instance));

        // 获取一个方法并调用该方法
        Method setCityMethod = oneClass.getMethod("setCity", String.class);
        setCityMethod.invoke(instance, "大陆");

        // 获取所有
        Method[] methods = oneClass.getMethods();
        for (Method method : methods) {
            System.out.println("迭代:" + method);
        }

        Song song = (Song) instance;
        // 打印结果:大陆
        System.out.println(song.getCity());
        System.out.println(song);
    }

    public static String generatorMethodName(String str) {
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }
}

如何使用反射?- 05 调用属性

到了这里,其实已经可以预料到接下来讲解什么。

一个类上能有什么东西呢?无非就是构造方法,普通方法,属性(或者叫成员,字段),当然他们都有修饰符 public, private, static 可以进行修饰。

但是在大类别上,只剩下一个属性还没有讲解。

所以,使用反射如何获取属性?

Class类中提供了2个API方法:

  1. 取得全部成员:public Filed[] getDeclaredFileds() throws SecurityException;
  2. 取得指定成员:public Filed getDeclaredFiled(String name) throws NoSuchMethodException,SecurityException;
package com.ifdom.reflection;

import java.lang.reflect.Field;

class Song {
    private String title;

    @Override
    public String toString() {
        return "Song{" +
                "title='" + title + '\'' +
                '}';
    }
}

/**
 * @Author ifredom
 * @Date 2022/6/29
 * @ClassName Reflection1
 * @Version 1.0.0
 * @Description 描述
 **/
public class Reflection1 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        Class<?> oneClass = Class.forName("com.ifdom.reflection.Song");
        Object instance = oneClass.newInstance();

        // 获取一个方法并调用该方法
        Field field = oneClass.getDeclaredField("title");
        // 值为 true 时,则表示取消 Java 语言访问检查(private关键字不在生效)
        field.setAccessible(true);
        field.set(instance, "她");
        System.out.println(field.get(instance));

        // 获取所有
        Field[] declaredFields = oneClass.getDeclaredFields();
        Arrays.stream(declaredFields).forEach(System.out::println);

        Song song = (Song) instance;
        System.out.println(song);
    }

}

反射在 Spring 中的应用

spring 框架内部已经提前提供了一个工具类:ReflectionUtils,通过这个类上提供的静态方法invokeMethod,与我们上面反射提供的获取method的API是一模一样的效果。

  • ReflectionUtils:ReflectionUtils.invokeMethod(method, obj, args);

另外Spring框架,还提供了一个获取bean的方法 ApplicationContext.getbean(className),其功能与上面的通过反射获取实例是一模一样的效果。稍微服了一些需要手动去继承这个类 ApplicationContextAware

  • ApplicationContext.getbean(className) ≈≈ Class.forName(className).newInstance()
package com.ifdom.reflection;

import com.ifdom.reflection.entity.User;
import com.ifdom.reflection.utils.SpringContextUtils;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ReggieApplicationTests {
    @Test
    void contextLoads() {
        User user = SpringContextUtils.getBean(User.class);
    }
}
package com.ifdom.reflection
lection;

import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @Author ifredom
 **/

@Component
public class SpringContextUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
        if (SpringContextUtils.applicationContext == null) {
            SpringContextUtils.applicationContext = applicationContext;
        }
    }
    /**
     * @apiNote 获取applicationContext
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
    /**
     * @apiNote 通过name获取 Bean.
     */
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }
    /**
     * @apiNote 通过class获取Bean.
     */
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }
    /**
     * @apiNote 通过name, 以及Clazz返回指定的Bean
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}
拓展:反射reflection提供的其他API

以下内容已经不用阅读,大部分人从入行到入土也用不上,仅仅对骨灰级玩家有用。

  1. 获取修饰符
  2. 获取注解
package com.ifdom.reflection;

import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

@DemoAnnotation(key = "author", value = "ifredom")
class Song {
    private String title;
    public String author;

    public static void test() {
        System.out.println("test");
    }
}

/**
 * 自定义注解:这个注解有2个属性
 *
 * @author ifredom
 */
@Retention(RetentionPolicy.RUNTIME)
@interface DemoAnnotation {
    public String key();

    public String value();
}

/**
 * @Author ifredom
 * @Date 2022/6/29
 * @ClassName Reflection1
 * @Version 1.0.0
 * @Description 描述
 **/
public class Reflection1 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException {
        Class<?> oneClass = Class.forName("com.ifdom.reflection.Song");

        // 获取属性上的修饰符类型
        Field titleField = oneClass.getDeclaredField("title");
        int modifiers = titleField.getModifiers();
        System.out.println(modifiers);

        // 获取属性字段名
        Field authorField = oneClass.getDeclaredField("author");
        String fieldName = authorField.getName();
        System.out.println(fieldName);

        // 获取方法上的修饰符类型
        Method testMethod = oneClass.getMethod("test");
        int modifiers2 = testMethod.getModifiers();

        // 获取方法的注解
        Annotation testMethodAnnotation1 = testMethod.getAnnotation(DemoAnnotation.class);
        System.out.println(testMethodAnnotation1);

        // 获取类的注解
        Annotation testMethodAnnotation2 = oneClass.getAnnotation(DemoAnnotation.class);
        System.out.println(testMethodAnnotation2);
    }

}

修饰符属性对照表

  • PUBLIC: 1
  • PRIVATE: 2
  • PROTECTED: 4
  • STATIC: 8
  • FINAL: 16
  • SYNCHRONIZED: 32
  • VOLATILE: 64
  • TRANSIENT: 128
  • NATIVE: 256
  • INTERFACE: 512
  • ABSTRACT: 1024
  • STRICT: 2048

多个修饰符,getModifiers()获取的值是他们和。 例如:public static final 三个修饰的 就是3 个的加和 为 25

扩展阅读

------ 如果文章对你有用,感谢 >>>点赞 | 收藏 <<<

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值