黑马程序员-Java 反射

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——

一、概述

Java 反射机制是在运行状态中,对于程序中的任意一个类,通过反射机制都能够知道这个类的所有属性和方法,包括共有、包含、默认和私有。对于任意的一个对象,通过反射机制都可以去调用它的每一个方法,这种机制就称为Java的反射机制。一般的操作都在java.lang.reflect包中,常用到的类有ConstructorFieldMethod三种。既然是对Java类的反射,当然也有个比不可少的类Class,这也是一个类,作为Java中类文件的一个类,获取类的类一般有三种方式:

String s = "abc";
// 通过类直接获取
Class<?> c1 = String.class;
// 通过变量的getClass()方法获取
Class<?> c2 = s.getClass();
// 通过Class的静态方法forName(String)根据类的全名获取
Class<?> c3 = Class.forName("java.lang.String");

那么Java的反射机制有什么用处呢?一般的程序开发时,我们都是对使用的类直接操作,如创建等,但是如果遇到不能直接操作时怎么办呢,这里是反射的一个用处,如可是根据一个类名称的字符串来获取类的实例,这也是一种用法,程序可以根据配置文件内的类名去创建不同的类。还有一些情况如,类中有一些私有方法,我们想要访问或者修改,正常的做法是行不通的,通过反射则可以达到目的,这也成为暴力反射。

二、Class

第一次见到这个类可能会有些疑惑,Java中的类有很多,用过的也有很多,但是第一次见到这个描述类的类,多少还是有点好奇的,这也更加验证了一点,所有事物都可以被描述成对象,既然类这么常见,那当然也不会例外。首先我们应该对类文件有一些初步了解,我们在编写代码之后使用javac进行编译,便可以看到目录下由.java文件生成的.class文件,那么这些.class文件便是类文件。执行这些文件时,如java x.class,类文件便会被加载到内存中,以便使用,类文件中使用到的其他类也会随之加载,通过下面的例子结果可以说明一个问题,便是类文件在内存中是唯一的。

String a1 = "abc";
System.out.println(a1.getClass() == String.class);
System.out.println(a1.getClass() == Class.forName("java.lang.String"));
// 执行结果为
true
true

对于基本数据类型boolean,char,byte,short,int,long,float,double共8个和一个void都有其对应的类,如int.class,注意int.classInteger.class不是同一个类。对于数组也有其对应的类,如int[].classint[][].class,类型相同,维数相同的数组类才是同一个类,即int[].class != int[][].class,其他类似,数组类的类名有一定的规则,如int[][].class.getName()返回的是[[I,其中[的个数表示维数,int对应的是I,对应关系如下:

类型名 对应的内容
boolean Z
byte B
char C
double D
float F
int I
long J
short S
class or interface Lclassname

数组类也是继承自Object的,如可以用Object对象接收int[],如Object obj = new int[]{1, 2, 3};,但是注意一点Object[] obj = new int[]{1, 2, 3};这种写法是不正确的,因为基本数据类型不能转成Object对象。

Class的常用方法

  • getConstructor(Class<?>... parameterTypes)此方法用于获取类的构造方法,其中的参数是可变参数,用于指定获取的构造方法是那个,如获取String类的String(StringBuilder stringBuilder)构造方法,可以用Constructor cons String.class.getConstructor(StringBuilder.class);方式来获取。这个方法类似的另一种获取多构造方法的是getConstructors(),可以获取所有的构造方法,返回一个Constructor的数组。
  • newInstance()方法用于实例化一个对象,即用这个类创建一个对象。示例代码如下:
import java.lang.reflect.*;
class Main {
    public static void main(String[] args) throws Exception {
        // 获取String的一个构造方法
        Constructor cons = String.class.getConstructor(StringBuilder.class);
        System.out.println(cons);
        // 创建一个空的字符串
        String s = String.class.newInstance();
        System.out.println(s);
    }
}
// 执行结果为
public java.lang.String(java.lang.StringBuilder)
[空串]
  • String getName()用于获取类名(包含包名),如String.class.getName()的返回值为"java.lang.String"
  • Package getPackage()用于返回包名。
  • Field getField(String name)获取类的成员变量。
  • Field[] getFields()获取类发所有可访问的成员变量。
  • Field getDeclaredField(String name)根据名称获取类声明的一个成员变量。这里可以获取private修饰的成员变量,同样的还有 Field[] getDeclaredFields()用于获取所有类声明的成员变量。
  • Method getMethod(String name, Class<?>... parameterTypes)获取方法,和获取构造方法类似,同样也有获取所有方法和获取声明的方法等等。

三、Constructor

  • Class<T> getDeclaringClass()返回 Class 对象,该对象表示声明由此 Constructor 对象表示的构造方法的类。
  • int getModifiers()返回以整数形式返回此 Constructor 对象所表示构造方法的 Java 语言修饰符。
  • T newInstance(Object... initargs)使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。
import java.lang.reflect.*;
class Main {
    public static void main(String[] args) throws Exception {
        // 获取String的一个构造方法
        Constructor cons = String.class.getConstructor(StringBuilder.class);
        // 使用获取的构造函数示例化一个对象
        String s = (String)cons.newInstance(new StringBuilder("abc"));
        // 输出这个对象
        System.out.println(s);

        // 获取构造函数对应的类
        Class<?> c = cons.getDeclaringClass();
        // 输出类名
        System.out.println(c.getName());

        // 构造方法的类型
        System.out.println(Member.DECLARED == cons.getModifiers());
    }
}

四、Field

描述一个类的成员,一般功能有获取这个成员的值,设置这个成员的值,获取这个成员的数据类型等,下面通过一个实例演示一下Field的相关操作,实例内容为将一个自定义对象中的成员(String类型)中出现的叠词替换为[double],首先是获取该自定义对象的类对象,然会获取所有声明的成员,然会获取这些成员的值,最后再将替换过的值设置回去。

import java.lang.reflect.*;
/**
 * 将一个自定义类中的成员变量(String类型)中出现的连续两
 * 个相同的字符替换成[double]。如aa变成[double]。
 */
class Main {
    public static void main(String[] args) throws Exception {
        Demo demo = new Demo();
        // 获取demo对象中的声明的成员
        Field[] fields = demo.getClass().getDeclaredFields();
        for(Field field : fields) {
            // 如果不是可直接访问的,则将其修改为可直接访问型
            if(!field.isAccessible()) {
                field.setAccessible(true);
            }
            // 获取原始字符串内容
            String oldString = (String)field.get(demo);
            // 将叠词替换为[double]
            String newString = oldString.replaceAll("(.)\\1", "[double]");
            // 最后将新的字符串设置回去
            field.set(demo, newString);
            // 输出新的变量内容
            System.out.println(field);
        }
        System.out.println(demo.toString());
    }
}
class Demo {
    // 修改后应为ac[double]de
    public String a = "abccde";
    // 修改后应为he[double]oawdcs
    private String b = "helloawdcs";
    // 修改后应为[double]sdn[double]asdnjasd[double]adas
    protected String c = "aasdnjjasdnjasdbbadas";

    public String toString() {
        return a + "/" + b + "/" + c;
    }
}

五、Method

描述一个类的方法,一般常用功能, Class<?> getReturnType() 返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型。Object invoke(Object obj, Object... args)对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。 下面通过一个实例,内容是通过反射功能获取一个String对象的第一个位置的字符,即调用charAt()方法。

import java.lang.reflect.*;
class Main {
    public static void main(String[] args) throws Exception {
        String s = "abc";
        // 获取String的类对象
        Class<?> c = String.class;
        // 获取String类的charAt方法
        Method method = c.getMethod("charAt", int.class);
        // 通过方法的反射获取第1个位置的字符
        char ch = (char)method.invoke(s, 1);
        System.out.println(ch); // 结果为b
    }
}

六、数组的反射

使用反射传递数组参数时,需要注意其可能会被系统自动拆分为对应的可变参数类型,如main方法的String[]会变成String...,所以传递时一般有两种解决方式,一是将其再次封装成一个数组,如new Object[]{new String[]{"abc", "cba", "bac"}};二是将其强转为父类(Object)new String[]{"abc", "cba", "bac"},两种方式都可。

import java.lang.reflect.*;
/**
 * 通过反射调用另一个类的main方法,传递去一个数组参数
 */
class Main {
    public static void main(String[] args) throws Exception {
        // 首先获取对应类的Class对象
        Class clazz = Demo.class;
        // 获取Demo的main方法
        Method method = clazz.getMethod("main", String[].class);
        // 定义待传递的参数
        String[] strs = new String[]{"Hello", "Hi", "Bye"};
        /*调用静态方法时,不需要传递变量
         *注意:因为JDK的版本,如果直接传递strs会被拆分成
         *可变类型String...,不符合main的参数要求,所以
         *这里将其转为一个整体,Object类型
         */
        method.invoke(null, (Object)strs);
    }
}
class Demo {
    public static void main(String[] args) {
        // 将传递进来的参数输出
        for(String s : args) {
            System.out.println("参数:" + s);
        }
    }
}
// 执行结果
参数:Hello
参数:Hi
参数:Bye

前面已经说过数组也是一个类,如int[].class,这种类和其他类有一些区别,Java提供了一个用于操作数组类的类Array,Array 类提供了动态创建和访问 Java 数组的方法。Array 允许在执行 get 或 set 操作期间进行扩展转换,但如果发生收缩转换,则抛出 IllegalArgumentException。示例代码如下:

import java.util.*;
import java.lang.reflect.*;
class Main {
    public static void main(String[] args) throws Exception {
        // 获取String[]对应的类
        Class<?> clazz = String[].class;
        // 定义一个String数组并初始化
        String[] strs = new String[]{"Hello", "Hi", "Bye"};
        // 如果这个类是数组类
        if(clazz.isArray()) {
            // 获取数组的长度
            int len = Array.getLength(strs);
            // 变量这个数组
            for(int i=0; i<len; i++) {
                // 反射获取strs的第i位置的值
                String s = (String)Array.get(strs, i);
                // 输出获取的值
                System.out.println(i+":"+s);
                // 如果这个值为Hi,则将其改为Good
                if(s.equals("Hi")) {
                    Array.set(strs, i, "Good");
                }
            }
            // 将整个数组输出
            System.out.println(Arrays.toString(strs));
        }
    }
}
// 执行结果为
0:Hello
1:Hi
2:Bye
[Hello, Good, Bye]

七、反射的应用

在实际开发中,反射的应用一般是用于开发框架,框架是一些功能的核心抽取,涵盖了一个系统的整体,但是没有完成细节的东西。
下面是一个框架的实例,程序可以根据用户配置的文件,执行中使用不同的类,在当前目录建立一个文件名为config.properties在其内写入className=java.util.ArrayList,这是程序中的集合使用的是ArrayList,最后的结果是集合大小为4,然后将className的值修改为java.util.HashSet,最后的结果则变为了3,如此一来,程序可以根据不同的配置使用不同的类,框架便是如此,在使用的类不确定,或者没有现成的类可使用时,不防使用反射来实现程序功能。代码如下:

import java.io.*;
import java.util.*;
import java.lang.reflect.*;
class Main {
    public static void main(String[] args) throws Exception {
        // 配置
        Properties properties = new Properties();
        // 通过类加载器获取输入流
        InputStream in = Main.class.getResourceAsStream("config.properties");
        // 从流中加载配置
        properties.load(in);
        // 关闭流
        in.close();

        // 获取类名
        String className = properties.getProperty("className");
        // 根据类名获取类
        Class clazz = Class.forName(className);

        // 根据clazz类,创建一个集合对象
        Collection collection = (Collection) clazz.newInstance();
        // 向集合中添加一些字符串
        collection.add(new String("Hello"));
        collection.add(new String("Hi"));
        collection.add(new String("Bye"));
        collection.add(new String("Bye"));
        // 输出集合的大小
        System.out.println(collection.size());
    }
}

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。