反射

使用反射获取程序运行时的对象和类的真实信息。

获取 Class 对象

每个类被加载之后,系统会为该类生成一个对应的 Class 对象,通过该 Class 对象可以访问到 JVM 中的这个类。

  • 使用 Class 类的 forName(String clazzName) 静态方法。字符串参数的值是某个类的全限定类名,必须包含完整的包名

  • 调用某个类的 class 属性

  • 调用某个对象的 getClass() 方法。该方法是 java.lang.Object 类中的一个方法,所有的 Java 对象都可以调用,返回该对象所属类对应的 Class 对象

获取 Class 对象中信息

Class 类提供了大量的实例方法来获取该 class 对象所对应的类的详细信息。更多请参考 API。

import java.lang.reflect.*;
import java.lang.annotation.*;

public class ClassTest {
    private ClassTest() {
    }

    public ClassTest(String name) {
        System.out.println("执行有参数的构造器");
    }

    public void info() {
        System.out.println("执行无参数的info方法");
    }

    public void info(String str) {
        System.out.println("执行有参数的info方法" + ",其 str 参数值: " + str);
    }

    class Inner {
    }

    public static void main(String[] args) throws Exception {
        Class<ClassTest> clazz = ClassTest.class;

        // 获取 clazz 对象所对应类的全部构造器
        Constructor<?>[] ctros = clazz.getDeclaredConstructors();
        System.out.println("ClassTest 的全部构造器如下: ");
        for (Constructor c : ctros) {
            System.out.println(c);
        }

        // 获取 clazz 对象所对应类的全部 public 构造器
        Constructor<?>[] publicCtors = clazz.getConstructors();
        System.out.println("ClassTest的全部public构造器如下:");
        for (Constructor c : publicCtors) {
            System.out.println(c);
        }

        // 获取 clazz 对象所对应类的全部 public 方法
        Method[] mtds = clazz.getMethods();
        System.out.println("ClassTest 的全部 public 方法如下: ");
        for (Method md : mtds) {
            System.out.println(md);
        }

        // 获取 clazz 对象所对应类的指定方法
        System.out.println("ClassTest 里带一个字符串参数的 info 方法为:" + clazz.getMethod("info", String.class));

        // 获取 clazz 对象所对应类的全部注解
        Annotation[] anns = clazz.getAnnotations();
        System.out.println("ClassTest 的全部 Annotation 如下: ");
        for (Annotation an : anns) {
            System.out.println(an);
        }

        // 获取 clazz 对象所对应类的全部内部类
        Class<?>[] inners = clazz.getDeclaredClasses();
        System.out.println("ClassTest 的全部内部类如下: ");
        for (Class c : inners) {
            System.out.println(c);
        }

        // 使用 Class.forName() 方法加载 ClassTest 的 Inner 内部类
        Class inClazz = Class.forName("ClassTest$Inner");

        // 访问该类所在的外部类
        System.out.println("inClazz 对应类的外部类为: " + inClazz.getDeclaringClass());

        System.out.println("ClassTest 的包为:" + clazz.getPackage());
        System.out.println("ClassTest 的父类为:" + clazz.getSuperclass());

    }
}

应用

Class 对象可以获得对应类的方法(由 Method 表示)、构造器(由 Constructor 表示)、成员变量(由 Field 对象表示),且这个三个类都实现了 java.lang.reflect.Member 接口。程序可以通过 Method 对象来执行对应的方法,通过 Constructor 对象来调用对应的构造器创建实例,通过 Field 对象直接访问并修改对象的成员变量值。

创建对象

  • 使用 Class 对象的 newInstance() 方法来创建 Class 对象对应类的实例。要求该 Class 对象的对应类有默认构造器

  • 先使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance() 方法来创建该 Class 对象对应类的实例。这种方式可以选择使用指定的构造器来创建实例

方式一

实现了一个简单的对象池,该对象池会根据配置文件读取 key-value 对,然后创建这些对象并放入 HashMap 中

import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class ObjectPoolFactory {
    private Map<String, Object> objectPool = new HashMap<>();

    private Object createObject(String clazzName) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Class<?> clazz = Class.forName(clazzName);
        // 使用 Class 对象对应的类的默认构造器
        return clazz.newInstance();
    }

    public void initPool(String fileName) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        try (
                FileInputStream fis = new FileInputStream(fileName)
        ) {
            Properties props = new Properties();
            props.load(fis);
            for ( String name: props.stringPropertyNames()) {
                objectPool.put(name, createObject(props.getProperty(name)));
            }
        } catch (IOException ex) {
            System.out.println("读取" + fileName + "异常");
        }
    }

    public Object getObject(String name) {
        return objectPool.get(name);
    }

    public static void main(String[] args) throws Exception{
        ObjectPoolFactory pf = new ObjectPoolFactory();
        pf.initPool("obj.txt");
        System.out.println(pf.getObject("a"));
        System.out.println(pf.getObject("b"));
    }
}
/*
obj.txt 内容:

a=java.util.Date
b=javax.swing.JFrame
*/
方式二
import java.lang.reflect.Constructor;

public class CreateJFrame {
    public static void main(String[] args) throws Exception {
        Class<?> jframeClazz = Class.forName("javax.swing.JFrame");
        // 选择使用指定的构造器
        Constructor ctor = jframeClazz.getConstructor(String.class);
        Object obj = ctor.newInstance("测试窗口");
        System.out.println(obj);
    }
}

调用方法

每个 Method 对象对应一个方法,获得 Method 对象后,就可以通过该 Method 来调用它对应的方法。

Method 包含一个 invoke() 方法,该方法的签名如下:

  • Object invoke(Object obj, Object... args):该方法中的 obj 是执行方法的主调(即类的实例对象),后面的 args 是执行该方法的实参

下面是对之前的对象工厂池进行增强,允许在配置文件中增加配置对象的成员变量值,对象池工厂会读取该对象配置的成员变量值,并利用该对象对应的 setter 方法设置成员变量的值:

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class ExtendedObjectPoolFactory {
    // 定义一个对象池,前面是对象名,后面是实际的对象
    private Map<String, Object> objectPool = new HashMap<>();
    private Properties config = new Properties();
    // 从指定文件中初始化 Properties 对象
    public void init(String fileName) {
        try (
                FileInputStream fis = new FileInputStream(fileName);
        ) {
            config.load(fis);
        } catch (IOException ex) {
            System.out.println("读取" + fileName + "异常");
        }
    }
    // 定义创建对象的方法
    private Object createObject(String clazzName) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        // 根据字符串来获取对应的 Class 对象
        Class<?> clazz = Class.forName(clazzName);
        // 使用 clazz 对应类的默认构造器创建实例
        return clazz.newInstance();
    }

    // 初始化对象池
    public void initPool() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        for (String name : config.stringPropertyNames()) {
            if (!name.contains("%")) {
                objectPool.put(name, createObject(config.getProperty(name)));
            }
        }
    }

    // 根据属性文件来调用指定对象的 setter 方法
    public void initProperty() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        for (String name : config.stringPropertyNames()) {
            if (name.contains("%")) {
                String[] objAndProp = name.split("%");
                Object target = getObject(objAndProp[0]);
                String mtdName = "set" + objAndProp[1].substring(1);
                // 通过 target 的 getClass() 获取它的实现类所对应的 Class 对象
                Class<?> targetClass = target.getClass();
                // 获取希望调用的 setter 方法
                Method mtd = targetClass.getMethod(mtdName, String.class);
                // 通过 Method 的 invoke 方法执行 setter 方法
                mtd.invoke(target, config.getProperty(name));
            }
        }
    }

    public Object getObject(String name) {
        // 从 objectPool 中取出指定 name 对应的对象
        return objectPool.get(name);
    }

    public static void main(String[] args) throws Exception {
        ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();
        epf.init("extObj.txt");
        epf.initPool();
        epf.initProperty();
        System.out.println(epf.getObject("a"));
    }
}

/* extObj.txt 内容

a=java.util.Date
b=javax.swing.JFrame
# set the title of a
a%title=Test Title
*/

PS:当通过 Method 的 invoke() 方法来调用对应的方法时,Java 会要求程序必须有调用该方法的权限。如果需要调用某个对象的 private 方法,则可以先调用 Method 对象的如下方法:

  • setAccessible(boolean flag):值为 true,表示该 Method 在使用时取消访问权限检查

访问成员变量值

Filed 提供如下两组方法来读取或设置成员变量值:

  • getXxx(Object obj):获取 obj 对象的该成员变量的值。此处的 Xxx 对应 8 中基本类型。如果成员变量的类型是引用类型,则直接使用 get

  • setXxx(Object obj, Xxx val):将 obj 对象的成员变量值设为 val 值。此处的 Xxx 对应 8 中基本类型。如果成员变量的类型是引用类型,则直接使用 set

public class Person {
    private String name;
    private int age;

    public String toString() {
        return "Person[name:" + name + ", age:" + age + "]";
    }
}

import java.lang.reflect.Field;

public class FieldTest {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        Class<Person> personClazz = Person.class;
        Field nameField = personClazz.getDeclaredField("name");
        nameField.setAccessible(true);
        nameField.set(p, "crazy");
        Field ageField = personClazz.getDeclaredField("age");
        ageField.setAccessible(true);
        ageField.setInt(p, 30);
        System.out.println(p);
    }
}

泛型在反射中的应用

在反射中使用泛型,反射生成的对象就不需要进行强制类型转换。

import java.util.Date;

public class CrazyitObjectFactory {
    public static <T> T getinstance(Class<T> cls) {
        try {
            return cls.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        // 获取实例后无需进行类型转换
        Date d = CrazyitObjectFactory.getinstance(Date.class);
    }
}

欢迎关注我的公众号

Java 基础篇之反射的更多相关文章

  1. 小白—职场之Java基础篇

    java基础篇 java基础 目录 1.java是一种什么语言,jdk,jre,jvm三者的区别 2.java 1.5之后的三大版本 3.java跨平台及其原理 4.java 语言的特点 5.什么是字 ...

  2. Java基础篇(JVM)——类加载机制

    这是Java基础篇(JVM)的第二篇文章,紧接着上一篇字节码详解,这篇我们来详解Java的类加载机制,也就是如何把字节码代表的类信息加载进入内存中. 我们知道,不管是根据类新建对象,还是直接使用类变量 ...

  3. java基础篇---I/O技术

    java基础篇---I/O技术   对于任何程序设计语言而言,输入输出(I/O)系统都是比较复杂的而且还是比较核心的.在java.io.包中提供了相关的API. java中流的概念划分 流的方向: 输 ...

  4. 金三银四跳槽季,BAT美团滴滴java面试大纲(带答案版)之一:Java基础篇

    Java基础篇: 题记:本系列文章,会尽量模拟面试现场对话情景, 用口语而非书面语 ,采用问答形式来展现.另外每一个问题都附上“延伸”,这部分内容是帮助小伙伴们更深的理解一些底层细节的补充,在面试中可 ...

  5. Java提升篇之反射的原理

    Java提升篇之反射的原理 1.构造方法的反射 import java.lang.reflect.Constructor; public class ReflectConstructor { publ ...

  6. java基础篇---HTTP协议

    java基础篇---HTTP协议   HTTP协议一直是自己的薄弱点,也没抽太多时间去看这方面的内容,今天兴致来了就在网上搜了下关于http协议,发现有园友写了一篇非常好的博文,博文地址:(http: ...

  7. 黑马程序猿————Java基础日常笔记---反射与正則表達式

    ------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- 黑马程序猿----Java基础日常笔记---反射与正則表達式 1.1反射 反射的理解和作用: 首 ...

  8. java基础篇---I/O技术(三)

    接上一篇java基础篇---I/O技术(二) Java对象的序列化和反序列化 什么叫对象的序列化和反序列化 要想完成对象的输入或输出,还必须依靠对象输出流(ObjectOutputStream)和对象 ...

  9. Java基础13:反射与注解详解

    Java基础13:反射与注解详解 什么是反射? 反射(Reflection)是Java 程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性. Orac ...

随机推荐

  1. Web安全开发规范手册V1.0

    一.背景 团队最近频繁遭受网络攻击,引起了部门技术负责人的重视,笔者在团队中相对来说更懂安全,因此花了点时间编辑了一份安全开发自检清单,觉得应该也有不少读者有需要,所以将其分享出来. 二.自检清单 检 ...

  2. 剑指Offer(二十八):数组中出现次数超过一半的数字

    剑指Offer(二十八):数组中出现次数超过一半的数字 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn. ...

  3. 【入门】广电行业DNS、DHCP解决方案详解(三)——DNS部署架构及案

    [入门]广电行业DNS.DHCP解决方案详解(三)——DNS部署架构及案 DNS系统部署架构 宽带业务DNS架构 互动业务DNS架构 案例介绍 案例一 案例二 本篇我们将先介绍DNS系统部署架构体系, ...

  4. Node.js+Navicat for MySQL实现的简单增删查改

    前提准备: 电脑上必须装有服务器环境,Navicat for MySQL(我用的是这款MySQL,可随意),Node环境 效果如图所示: 源码地址: GitHub:https://github.com ...

  5. ES6-数组的新方法

    1.Array.of() 方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型. Array.of() 和 Array 构造函数之间的区别在于处理整数参数:Array.of(7)创建一个 ...

  6. CentOS7 小技巧总结

    1.CentOS7 解决无法使用tab自动补全 原因:CentOS在最小化安装时,没有安装自动补全的包,需要手动安装. yum -y install bash-completion 安装好后,重新登陆 ...

  7. NOIP2009 1.多项式输出

    题目: 其中,aixi称为 i 次项,ai 称为 i 次项的系数.给出一个一元多项式各项的次数和系数,请按照如下规定的格式要求输出该多项式: 1. 多项式中自变量为 x,从左到右按照次数递减顺序给出多 ...

  8. STL容器(Stack, Queue, List, Vector, Deque, Priority_Queue, Map, Pair, Set, Multiset, Multimap)

    一.Stack(栈) 这个没啥好说的,就是后进先出的一个容器. 基本操作有: stack<int>q; q.push(); //入栈 q.pop(); //出栈 q.top(); //返回 ...

  9. WebDriver 将浏览器窗口最大化

    package com.entrym.main; import java.io.File; import java.io.IOException; import org.openqa.selenium ...

  10. gh-ost 原理剖析

    gh-ost 原理 一 简介 上一篇文章介绍 gh-ost 参数和具体的使用方法,以及核心特性-可动态调整 暂停,动态修改参数等等.本文分几部分从源码方面解释gh-ost的执行过程,数据迁移,切换细节 ...