反射机制
1. 概述
反射就是把Java类中的各种成分以及Java类本身映射成相应的java类,以方便类加载器或程序员能够直接对Java类进行操作。
例如:每一个加载进内存的Java类都用一个叫Class的类对象来表示,一个类中的各个组成部分:域,构造器,方法,包等等信息也分别用特定的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。
反射的本质就是对各个Java类的对象化,即是对Java类进行管理的Java类。对所有的Java类进行抽取其共同点,用一个类来描述,这就是Class类。Class类显然提供了一系列的方法,用以获得其中的域,构造器,方法,修饰符,包等信息,这些信息通过面向对象的思想,毫无疑问也封装成了一个个类,即Field、Contructor、Method、Package等等类。
反射的基石就是Class类。反射能够方便地编写能够动态操纵Java代码的程序,特别是在设计或运行中添加新类时,能够快速地应用开发工具动态地查询添加类的能力。反射是一种功能强大且复杂的机制。使用它的主要人员为框架构造者,而非应用程序猿。
2. 作用
- 在运行中分析类的能力。
- 在运行中查看对象,例如,编写一个toString方法供所有类使用。
- 实现通用的数组操作代码。
- 利用Method对象,该对象类似C++中的函数指针。
Class类(java.lang)
1. 概述
所有的Java类都属于同一类事物,所以就可以用一个类来描述,Class类就是用于描述Java类的类,是反射的基石。例如:所有的人用Person类来描述,而所有的Java类就用Class类来描述。
Class类实例对应于加载进内存的Java类字节码对象,不同的Java类加载进内存的字节码是不同的,所以用以表示这些字节码的Class实例是不同的,而这些实例显然具有相同的类型,即Class。而同一个类或类型加载进内存,生成的字节码对象也是同一个,不管该类生成了多少个对象,该类对应的字节码在内存中始终是唯一的。
Class类没有构造器,只能通过特定方法或自有静态方法获取实例。
2. 获取Class类实例
有三种方法可以获得Class类实例:
- 通过Object类中Class<?> getClass()方法
必须通过具体对象才能获取字节码文件对象。 - 使用的任意数据类的一个静态成员class,所有的数据类型都具备的一个属性。
不需要对象,但是需要具体的类,才能够通过静态方法获取字节码对象。 - 使用Class类中static Class<?> forName(String className)方法,通过给定类名来获取对应的字节码文件对象。
通过类名(字符串)获取字节码文件对象。
private static void getClass_1() {
Employee em = new Employee("jacob", 25, 12000);
Class<? extends Employee> c = em.getClass();
System.out.println(c);
}
private static void getClass_2() {
Class<Employee> c = Employee.class;
System.out.println(c);
}
private static Class<?> getClass_3() throws ClassNotFoundException {
Class<?> c = Class.forName("bean.Employee");
// 无法解析字符串,或者没有该类,则报ClassNotFoundException异常。
System.out.println(c);
return c;
}
3. 预定义Class实例
- 8个基本数据类型(byte,short,int,long,char,float,double,boolean)同样有Class实例与之对应。获取方法为:基本数据类型.class。例如:int.class。
- void类型也有Class实例与之对应。获取方法为:void.class。
- 数组本质上相当于一个实例,所以也有Class实例与之对应,获取方法即为普通类获取Class实例的三种方法。
例如:
int[] arr = new int[3];
Class clazz1 = int[].class;
Class clazz2 = arr.getClass();
Class clazz3 = Class.forName("[I");
System.out.println(clazz1 == clazz2); // true
System.out.println(clazz1 == clazz3); // true
4. 常用方法
- 解析字符串以获得Class实例
static Class<?> forName(String className) - 获取类定义的公有内部类,以及从父类、父接口那里继承来的内部类
Class<?>[] getClasses() - 获取类定义的所有内部类,不包括继承来的内部类
Class<?>[] getDeclaredClasses() - 获取加载该类的类加载器
ClassLoader getClassLoader() - 获取该类的公有构造器、域和方法(包括继承而来的域和方法)
Constructor<T> getConstructor(Class<?>... parameterTypes)
Constructor<?>[] getConstructors()
Field getField(String name)
Field[] getFields()
Method getMethod(String name, Class<?>... parameterTypes)
Method[] getMethods() - 获取该类的构造器、域和方法(包括所有权限,但不包括继承而来的域和方法)
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors()
Field getDeclaredField(String name)
Field[] getDeclaredFields()
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
Method[] getDeclaredMethods() - 若该类是内部类,则获取外部类实例
Class<?> getDeclaringClass() - 获取该类或接口所实现或继承的所有接口
Class<?>[] getInterfaces() - 获取该类的修饰符、名称和包
int getModifiers()
String getName()
Package getPackage() - 载入资源文件
URL getResource(String name)
InputStream getResourceAsStream(String name) - 获取该类的父类
Class<? super T> getSuperclass() - 判断该类的属性
boolean isArray()
boolean isEnum()
boolean isInstance(Object obj)
boolean isInterface()
boolean isPrimitive() - 通过无参数构造器创建该类实例
T newInstance() - 返回该类的描述(1.8)
String toGenericString()
5. 示例
String str1 = "abc";
Class cls1 = str1.getClass();
Class cls2 = String.class;
Class cls3 = Class.forName("java.lang.String");
System.out.println(cls1 == cls2); // true
System.out.println(cls1 == cls3); // true
boolean b1 = cls1.isPrimitive(); // false
boolean b2 = int.class.isPrimitive(); // true
boolean b3 = int.class == Integer.class; // false
boolean b4 = int.class == Integer.TYPE; // true
boolean b5 = int[].class.isPrimitive(); // false
boolean b6 = int[].class.isArray(); // true
String str2 = (String) cls1.newInstance(); // 使用反射通过无参数构造器创建String实例
// 通常被反射的类都会有提供空参数的构造函数。 // 没有对应的构造器,则报InstantiationException异常。 // 对于构造器访问权限不够,报IllegalAccessException异常。
Constructor类(java.lang.reflect)
1. 概述
2. Construction类实例的获取
- 获取该类的构造器(包括所有权限)
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors() - 获取该类的公有构造器
Constructor<T> getConstructor(Class<?>... parameterTypes)
Constructor<?>[] getConstructors()
3. 常用方法
- 获取该构造器的修饰符和名称
int getModifiers()
String getName() - 获取该构造器参数信息
int getParameterCount()
Class<?>[] getParameterTypes() - 该构造器是否带可变参数
boolean isVarArgs() - 使用特定参数通过该构造器创建该类实例
T newInstance(Object... initargs) - 获取该构造器的描述(1.8)
String toGenericString()
4. 示例
package reflect;
import java.lang.reflect.Constructor;
import bean.Person;
public class Demo {
public static void main(String[] args) throws Exception {
// 创建字符串
String str1 = new String(new StringBuffer("abc"));
// 用反射实现
Class<?> clazzStr = Class.forName("java.lang.String");
Constructor<?> conStr = clazzStr.getConstructor(StringBuffer.class);
String str2 = (String) conStr.newInstance(new StringBuffer("abc"));
System.out.println(str2);
// 创建自定义类实例
Person p1 = new Person("jacob", 25);
// 用反射实现
Class<?> clazz = Class.forName("bean.Person");
// 获取该类的特定构造器
Constructor<?> con = clazz.getConstructor(String.class, int.class);
// 根据该构造器创建对象
// 没有对应的构造器或是抽象类,则报InstantiationException异常。
// 对于构造器访问权限不够,报IllegalAccessException异常。
// 给出的参数和构造器参数不匹配,则报IllegalArgumentException异常。
// 如果构造器中抛出异常,则报InvocationTargetException异常。
Person p2 = (Person) con.newInstance("jacob", 25);
System.out.println(p2);
}
}
Field类(java.lang.reflect)
1. 概述
2. Field类实例的获取
- 获取该类的公有域和(包括继承而来的域)
Field getField(String name)
Field[] getFields()
- 获取该类域(包括所有权限,但不包括继承而来的域)
Field getDeclaredField(String name)
Field[] getDeclaredFields()
3. 常用方法
- 获取该域所属的类的Class实例
Class<?> getDeclaringClass() - 获取特定实例的该域的值
Object get(Object obj)
boolean getBoolean(Object obj)
byte getByte(Object obj)
char getChar(Object obj)
double getDouble(Object obj)
float getFloat(Object obj)
int getInt(Object obj)
long getLong(Object obj)
short getShort(Object obj) - 获取该域的修饰符和名称
int getModifiers()
String getName() - 获取该域的类型
Class<?> getType() - 对特定实例的该域赋值
void set(Object obj, Object value)
void setBoolean(Object obj, boolean z)
void setByte(Object obj, byte b)
void setChar(Object obj, char c)
void setDouble(Object obj, double d)
void setFloat(Object obj, float f)
void setInt(Object obj, int i)
void setLong(Object obj, long l)
void setShort(Object obj, short s) - 获取该域的描述(1.8)
String toGenericString() - (父类方法)判断及取消权限检查
boolean isAccessible()
void setAccessible(boolean flag)
4. 示例
package cn.itcast.day1;
import java.util.Date;
public class ReflectPoint {
private Date birthday = new Date();
private int x;
public int y;
public String str1 = "ball";
public String str2 = "basketball";
public String str3 = "itjava";
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "ReflectPoint [str1=" + str1 + ", str2=" + str2 + ", str3=" + str3 + "]";
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
package reflect;
import java.lang.reflect.Field;
import bean.ReflectPoint;
public class Demo {
public static void main(String[] args) throws Exception {
ReflectPoint pt = new ReflectPoint(3, 5);
// 获取公有域
Field fieldY = pt.getClass().getField("y");
/*
* 问题:fieldY的值是多少?
* 答:fieldY不是对象身上的变量,而是类上,要用它去取某个对象上对应的值。
*/
Object objY = fieldY.get(pt); // 5
String objName = objY.getClass().getName(); // "java.lang.Integer"
// 获取私有域
// x为私有域,所以需要使用getDeclaredField方法才可以看到
Field fieldX = pt.getClass().getDeclaredField("x");
// x为私有域,所以需要使用setAccessible方法取消权限检查(暴力访问,不推荐)
fieldX.setAccessible(true);
Object objX = fieldX.get(pt); // 3
// 将所有String类型实例域的值中的字母'a'替换为'x'
changeStringValue(pt);
System.out.println(pt);
}
private static void changeStringValue(Object obj) throws Exception {
// 获取该对象所属类的所有公有域
Field[] fields = obj.getClass().getFields();
for (Field field : fields) {
// 判断获取的域类型是否为String类型
// if(field.getType().equals(String.class)){
if (field.getType() == String.class) {
// 获取实例在该域上的值,并替换
String oldValue = (String) field.get(obj);
String newValue = oldValue.replace('a', 'x');
// 写入替换的新值
// 若抛出IllegalAccessException异常,则该字段无权限访问和更改
field.set(obj, newValue);
}
}
}
}
Method类(java.lang.reflect)
1. 概述
2. Method类实例的获取
- 获取该类的公有方法(包括继承而来的方法)
Method getMethod(String name, Class<?>... parameterTypes)
Method[] getMethods() - 获取该类的方法(包括所有权限,但不包括继承而来的方法)
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
Method[] getDeclaredMethods()
3. 常用方法
- 获取该方法所属类的Class实例
Class<?> getDeclaringClass() - 获取该方法的修饰符和名称
int getModifiers()
String getName() - 获取该方法参数个数和类型
int getParameterCount()
Class<?>[] getParameterTypes() - 获取该方法返回值类型
Class<?> getReturnType() - 执行该方法
Object invoke(Object obj, Object... args)
4. 示例
package reflect;
import java.lang.reflect.Method;
public class ReflectMethod {
public static void main(String[] args) throws Exception {
String str = "abc";
// 获取成员方法并执行
Method methodCharAt = String.class.getMethod("charAt", int.class);
Object obj1 = methodCharAt.invoke(str, 1); // 'b'
// 由于invoke方法接收可变参数,所以第2个参数可以接受若干个,但是对于charAt方法来说,只取用数组的第一个数字
Object obj2 = methodCharAt.invoke(str, new Object[] {2}); // 'c'
// 获取无参数成员方法并执行
Method methodIsEmpty = String.class.getMethod("isEmpty", null/*new Class[0]*/);
Object obj3 = methodIsEmpty.invoke(str, null/*new Object[0]*/); // false
// 获取静态方法并执行
Method methodValueOf = String.class.getMethod("valueOf", int.class);
Object obj4 = methodValueOf.invoke(null, 123); // "123"
// 调用其他类的主函数
ReflectMain.main(new String[]{"111","222","333"});
String ClassName = "reflect.ReflectMain";
Class<?> clazz = Class.forName(ClassName);
Method mainMethod = clazz.getMethod("main", String[].class);
/*
* 由于invoke方法第二个参数声明为Object... args
* 所以String[]是作为1个Object对象参数传入,或者作为Object[]中的1个元素,并将该Object[]作为参数传入
*/
mainMethod.invoke(null, (Object) new String[] {"aa", "bb", "cc"});
mainMethod.invoke(null, new Object[]{new String[]{"aaa","bbb","ccc"}});
}
}
数组的反射
1. 概述
2. 数组的类型提升和转换
- 数组本身可以看成是一个Java类实例,所有的数组的父类均是Object,所以可以类型提升为1个Object实例。
- 由于所有的Java类的父类均是Object类,所以除了基本数据类型以外,其他Java类实例数组均可以类型提升为Object数组。
- 由于基本数据类型不可以被类型提升为Object实例,所以基本数据类型数组不可以类型提升为Object数组。
示例
int[] a1 = new int[] {1, 2, 3};
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[] a4 = new String[] {"a", "b", "c"};
boolean b1 = a1.getClass() == a2.getClass(); // true
// boolean b2 = a1.getClass() == a3.getClass(); // 编译错误:Incompatible operand types
// boolean b3 = a1.getClass() == a4.getClass(); // 编译错误:Incompatible operand types
String str1 = a1.getClass().getName(); // "[I"
String str2 = a3.getClass().getName(); // "[[I"
String str3 = a4.getClass().getName(); // "[Ljava.lang.String;"
String str4 = a1.getClass().getSuperclass().getName(); // "java.lang.Object"
String str5 = a3.getClass().getSuperclass().getName(); // "java.lang.Object"
String str6 = a4.getClass().getSuperclass().getName(); // "java.lang.Object"
Object aObj1 = a1;
Object aObj2 = a3;
Object aObj3 = a4; // 均将数组类型提升为1个Object实例
// Object[] aObj4 = a1; // 编译错误:类型不匹配
Object[] aObj5 = a3; // 二维数组即是数组的数组,类型提升后Object数组中的元素是int型一维数组
Object[] aObj6 = a4; // String类型提升为Object类型,Object数组中的元素是String实例
// 由于集合中必须存储Java类实例,而基本数据类型不可以类型提升为Object类实例
// 所以基本数据类型数组存入是作为唯一的Object实例存入,而其他数组存入是将其元素存入集合。
System.out.println(Arrays.asList(a1)); // [[I@15db9742]
System.out.println(Arrays.asList(a4)); // [a, b, c]
3. Array类
3.1 概述
3.2 常用方法
- 创建数组实例
static Object newInstance(Class<?> componentType, int... dimensions)
static Object newInstance(Class<?> componentType, int length) - 获取数组中的元素
static Object get(Object array, int index)
static boolean getBoolean(Object array, int index)
static byte getByte(Object array, int index)
static char getChar(Object array, int index)
static double getDouble(Object array, int index)
static float getFloat(Object array, int index)
static int getInt(Object array, int index)
static long getLong(Object array, int index)
static short getShort(Object array, int index) - 获取数组空间长度
static int getLength(Object array) - 设置数组中元素的值
static void set(Object array, int index, Object value)
static void setBoolean(Object array, int index, boolean z)
static void setByte(Object array, int index, byte b)
static void setChar(Object array, int index, char c)
static void setDouble(Object array, int index, double d)
static void setFloat(Object array, int index, float f)
static void setInt(Object array, int index, int i)
static void setLong(Object array, int index, long l)
static void setShort(Object array, int index, short s)
3.3 示例
Object arr = Array.newInstance(int.class, 3);
int len = Array.getLength(arr); // 3
Array.setInt(arr, 0, 3);
Array.setInt(arr, 1, 2);
Array.setInt(arr, 2, 1);
int i = Array.getInt(arr, 1); // 2
4. 案例
package reflect;
import java.lang.reflect.Array;
public class ReflectArrayString {
public static void main(String[] args) {
int[] arr1 = new int[] {999, 888, 777, 666};
int[][] arr2 = new int[][] { {1, 11, 111}, {2, 22, 222}};
String[] arr3 = {"java", "eclipse", "windows"};
String oStr1 = objectToString(arr1); // "{999, 888, 777, 666}"
String oStr2 = objectToString(arr2); // "{{1, 11, 111}, {2, 22, 222}}"
String oStr3 = objectToString(arr3); // "{java, eclipse, windows}"
String oStr4 = objectToString("xyz"); // "xyz"
}
private static String objectToString(Object obj) {
Class<? extends Object> clazz = obj.getClass();
if (clazz.isArray()) {
// 如果参数为数组
int len = Array.getLength(obj);
StringBuilder sb = new StringBuilder();
// 左括号
sb.append("{");
int i = 0;
Object temp = null;
// 遍历元素并转成字符串
for (; i < len - 1; i++) {
temp = Array.get(obj, i);
if (temp.getClass().isArray()) {
// 如果获取的数组元素仍然是个数组,则递归实现字符串转换,并添加到结果缓冲中
sb.append(objectToString(temp)).append(", ");
} else {
// 如果元素非数组,则直接转字符串添加
sb.append(temp.toString()).append(", ");
}
}
// 最后一个元素后不需要分隔符,同理相同的处理
temp = Array.get(obj, i);
if (temp.getClass().isArray()) {
sb.append(objectToString(temp)).append("}");
} else {
sb.append(temp.toString()).append("}");
}
// 返回最终字符串
return sb.toString();
} else {
// 如果参数不是数组,则直接利用toString返回
return obj.toString();
}
}
}
反射的应用
1. 应用
2. 配置文件
- Java提供了Properties类来处理配置文件,可以直接通过流读取和写入。
- Class类以及类加载器中也提供了 InputStream getResourceAsStream(String path) 方法来实现配置文件的读取,但是没有写入方法。
- 配置文件一般需要和class文件一并交由客户使用,所以配置文件建议放在bin/Resource目录下。
- Eclipse中,将配置文件放在src目录中时,会自动复制一份副本到bin相应目录下。
3. 配置文件目录
- 由于工程目录在客户机器上是不存在的(或是不一定的),所以不可以使用相对工程目录的默认路径。
- 使用InputStream需指定相对工程目录的相对路径或是绝对路径,所以不适合配置文件。
- 使用类加载器ClassLoader提供的getResourceAsStream方法,需要指定相对CLASSPATH的相对路径,可以使用。
- 使用Class提供的getResourceAsStream方法,需要指定相对该class对应包目录的相对路径,可以使用,且较为方便。
示例
InputStream ips = new FileInputStream("bin/reflect/resources/config.properties"); // 相对路径:工程目录
InputStream ips = new FileInputStream(
"E:/Documents/个人文档/My Programs/Java_itheima/bin/reflect/resources/config.properties");
// 绝对路径
InputStream ips = ReflectUsb.class.getClassLoader().getResourceAsStream(
"reflect/resources/config.properties"); // 相对路径:CLASSPATH,推荐
InputStream ips = ReflectUsb.class.getResourceAsStream(
"/reflect/resources/config.properties"); // 相对路径:CLASSPATH,推荐
InputStream ips = ReflectUsb.class.getResourceAsStream(
"resources/config.properties"); // 相对路径:该类路径,推荐
4. 笔记本电脑实例
package reflect.usb;
public interface Usb {
public abstract void run();
public abstract void close();
}
Count = 2
USB1 = reflect.usb.CdRom
USB2 = reflect.usb.Keyboard
package reflect.usb;
public class CdRom implements Usb {
@Override
public void run() {
System.out.println("CD-ROM run!");
}
@Override
public void close() {
System.out.println("CD-ROM close!");
}
}
package reflect.usb;
public class Keyboard implements Usb {
@Override
public void run() {
System.out.println("Keyboard run!");
}
@Override
public void close() {
System.out.println("Keyboard close!");
}
}
package reflect;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import reflect.usb.Usb;
class MainBoard {
private Usb[] usbs = new Usb[10];
private int count = 0;
private boolean isRun = false;
/**
* 主板创建时,检查是否有USB设备,并添加进计算机系统
*
* @throws ClassNotFoundException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IOException
*/
MainBoard() throws ClassNotFoundException, InstantiationException, IllegalAccessException,
IOException {
flushDev();
}
/**
* 运行计算机
*/
public void run() {
System.out.println("MainBoard run!");
for (int i = 0; i < count; i++) {
usbs[i].run();
}
isRun = true;
}
/**
* 关闭计算机
*/
public void close() {
// 先关闭USB设备,再关闭主板
for (int i = 0; i < count; i++) {
usbs[i].close();
}
System.out.println("MainBoard close!");
isRun = false;
}
/**
* 添加USB设备
*
* @param dev
*/
private boolean addUsb(Usb dev) {
if (dev == null) {
return false;
}
// 如果已有该设备,则直接返回
for (Usb usb : usbs) {
if (usb == dev) {
return false;
}
}
usbs[count] = dev;
count++;
// 如果计算机正在运行,则该设备也开始运行
if (isRun) {
dev.run();
}
return true;
}
/**
* 移除USB设备
*
* @param dev
*/
private boolean remove(Usb dev) {
if (dev == null) {
return false;
}
for (int i = 0; i < count; i++) {
if (usbs[i] == dev) {
// 如果计算机正在运行,则先停止设备运行,再移除
if (isRun) {
dev.close();
}
for (int j = i; j < count - 1; j++) {
usbs[j] = usbs[j + 1];
}
return true;
}
}
return false;
}
/**
* 加载配置文件中的USB设备,更新USB设备列表
*
* @throws IOException
* @throws ClassNotFoundException
* @throws InstantiationException
* @throws IllegalAccessException
*/
public void flushDev() throws IOException, ClassNotFoundException, InstantiationException,
IllegalAccessException {
// 读取配置文件
InputStream ips = ReflectUsb.class.getResourceAsStream("resources/config.properties");
Properties config = new Properties();
config.load(ips);
// 读取USB设备数量
int configCount = Integer.parseInt(config.getProperty("Count"));
List<Usb> list = new ArrayList<Usb>();
for (int i = 1; i <= configCount; i++) {
String UsbName = config.getProperty("USB" + i);
// 利用反射创建USB设备
Class<?> clazz = Class.forName(UsbName);
Usb usb = (Usb) clazz.newInstance();
list.add(usb);
// 将还未添加入计算机系统的USB设备加入系统
addUsb(usb);
}
// 将计算机系统上已有,但是配置文件中没有的USB设备移除计算机系统
for (Usb usb : usbs) {
if (!list.contains(usb)) {
remove(usb);
}
}
}
}
public class ReflectUsb {
public static void main(String[] args) throws IOException {
MainBoard mb;
try {
mb = new MainBoard();
mb.run();
mb.close();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
}