Java 反射
2015-08-29 00:33
441 查看
一、概述
Java 反射机制是在运行状态中,对于程序中的任意一个类,通过反射机制都能够知道这个类的所有属性和方法,包括共有、包含、默认和私有。对于任意的一个对象,通过反射机制都可以去调用它的每一个方法,这种机制就称为Java的反射机制。一般的操作都在java.lang.reflect包中,常用到的类有
Constructor,
Field和
Method三种。既然是对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.class和
Integer.class不是同一个类。对于数组也有其对应的类,如
int[].class,
int[][].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()); } }
相关文章推荐
- Java集合之WeakHashMap
- Java集合之WeakHashMap
- [leetcode-179]Largest Number(java)
- Java-WeakHashMap源码分析及示例
- Java集合之Hashtable
- Java集合之Hashtable
- Java-Hashtable源码分析及示例
- SSH---集成Struts2+Spring+Hibernate(一)
- SSH之旅(二)——Struts2 配置文件
- Java集合之TreeMap
- Java集合之TreeMap
- Java-TreeMap源码分析及示例
- java中的多态
- Java多态 实例子类自动调用父类为空的构造方法 成员变量不支持Override 可写,没多态效果
- 【java-日志组件】slf4j+logback配置及详解
- Java集合之HashMap
- Java集合之HashMap
- Springmvc集成Shiro实现权限管理
- Java-HashMap源码分析及示例
- 详解Java二叉排序树