JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息,以及动态调用对象方法的功能称为java语言的反射机制。
1.Class类的使用
(1)java中普通的数据类型类和静态的成员不属于对象。
(2)类也是对象,是java.lang.Class类的实例对象。
(3)Class类本身是无法new自身的。因为可以从源码看到:
Class类本身的构造方法是一个私有的构造方法,只有java虚拟机可以来调用。
(4)Class类的使用代码:
package function.reflect;
/**
* Class类的使用
*
* @author kimtian
*/
public class ClassUsed {
public static void main(String[] args) {
//Student 对象的实例
Student student1 = new Student();
//Student这个类也是一个实例对象,是Class类的实例对象
//任意一个类都是Class的实例对象,这个实例对象有三种表示方式
//第一种表示方式-->任何一个类都有一个隐含的静态成员变量class
Class c1 = Student.class;
System.out.println(c1);
//第二种表达方式 已经知道该类的对象,通过getClass方法调用
Class c2 = student1.getClass();
//第三种表达方式
Class c3 = null;
try {
c3 = Class.forName("function.reflect.Student");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(c2 == c3);
/**
* c1,c2表示了Student类的类类型(ClassType)
* 万事万物皆对象
* 类也是对象,是Class类的实例对象
* 这个对象我们称为该类的类类型
*/
// 不管c1 or c2 都代表了foo类的类类型,一个类只可能是Class类的一个实例对象
System.out.println(c1 == c2);
//我们完全可以通过类的类类型创建该类的实例对象。-->通过c1 or c2 or c3创建Student的实例
Student student2 = null;
try {
//需要有无参数的构造方法,否则会报错
student2 = (Student) c1.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
class Student {
}
2.动态加载类
(1)从上面可以看到 Class.forName("类的全称") 可以用来创建类的实例。
c1.newInstance();不仅表示了类的类类型,还代表了动态加载类;
编译时刻加载类是静态加载类、运行时刻加载类是动态加载类,我们在做一些通过功能方法的时候最好使用动态加载类。
(2)静态加载类的实现:
由于使用的静态加载类,如果我们没有Office类,那么在编译Office类的时候就会出错。
如果有一个类似于Word、Excel这样的类出错,全部其他的功能都使用不了。
package function.reflect;
/**
* 如果有一个出问题,全部其他的功能也都使用不了
* 每次增加新功能,要修改基础代码
*
* @author kimtian
*/
public class Office {
public static void main(String[] args) {
if ("Word".equals(args[0])) {
//new创建对象是静态加载类,在编译时刻就需要加载所有可能使用到的类。
//只想在运行哪个时候加载哪个,就要使用动态加载类
Word word = new Word();
word.start();
}
if ("Excel".equals(args[0])) {
Excel excel = new Excel();
excel.start();
}
}
}
(3)动态加载类的实现:
使用动态加载类,如果要使用Excel,但是没有Excel类,则只在运行的时候才会报错。但不会影响其他的,如Word的使用。
我们给定一个OfficeAble的通用接口,使Word、Excel等都实现该接口,可扩展性好,新增类似其他的功能时,只需要增加新的代码实现OfficeAble通用接口。不用重新修改编译这个功能基础类。
package function.reflect;
/**
* 如果需要加其他OfficeAble的具体实现,也不需要重新修改编译这个类
*
* @author kimtian
*/
public class OffiiceBetter {
public static void main(String[] args) {
try {
//动态加载类,在运行时刻加载
Class c = Class.forName(args[0]);
//通过类类型,创建该类对象
OfficeAble oa = (OfficeAble) c.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
3.获取类的信息
(1)获取类的方法的信息
对类进行操作,通过class类获得类的所有方法信息。
- getMethods获取的是所有的public的函数,包括父类继承而来的;
- getDeclaredMethods获取的是所有该类自己声明的方法,不问访问权限(父类继承来的就没有了);
package function.reflect;
import java.lang.reflect.Method;
/**
* Class类的基本操作
* 通过Class类获取类的所有方法信息
*
* @author kimtian
*/
public class ClassOperating {
/**
* 打印类的信息,包括类的成员函数和成员变量
*
* @param object 对象
*/
public static void printClassMessage(Object object) {
//要获取类的信息 首先要获取类的类类型
//传递是哪个子类的类类型,c就是该子类的类类型
Class c = object.getClass();
//获取类的名称
System.out.println("类的名称是" + c.getName());
/**
* 获取所有的方法,万事万物皆对象,方法也是对象
* 一个成员方法就是一个Method对象
* getMethods获取的是所有的public的函数,包括父类继承而来的
* getDeclaredMethods获取的是所有该类自己声明的方法,不问访问权限(父类继承来的就没有了)
*
*/
Method[] ms = c.getMethods();
Method[] ms1 = c.getDeclaredMethods();
//得到方法的名称
for (int i = 0; i < ms.length; i++) {
//得到方法的返回值类型 --得到的是返回值类型的类类型
Class returnType = ms[i].getReturnType();
//得到返回值名字
System.out.print(returnType.getName() + " ");
//得到方法的名称
System.out.print(ms[i].getName() + "(");
//获取参数的类型-->得到的是参数列表的类型的类类型
Class[] paramTypes = ms[i].getParameterTypes();
for (int j = 0; j < paramTypes.length; j++) {
//为了美观,最后一个返回值不加标点符号
if (j == paramTypes.length - 1) {
System.out.print(paramTypes[j].getSimpleName());
} else {
System.out.print(paramTypes[j].getSimpleName() + ",");
}
}
System.out.print(")");
System.out.println();
}
}
public static void main(String[] args) {
String s = "hello world";
//得到String类的所有类型
ClassOperating.printClassMessage(s);
//得到Integer类的所有类型
Integer i = 1;
ClassOperating.printClassMessage(i);
}
}
(2)获取成员变量信息
对类进行操作,通过class类获得类的所有成员变量的信息。
- getFields()方法获取的是所有的public的成员变量的信息
- getDeclaredFields()获取的是所有该类自己声明的成员变量的信息
package function.reflect;
import java.lang.reflect.Field;
/**
* Class类的基本操作
* 通过Class类获取类的所有成员变量
* @author kimtian
*/
public class ClassOperateTwo {
/**
* 打印成员变量的信息
*
* @param object 对象
*/
public static void printFieldMessage(Object object) {
Class c = object.getClass();
/**
* 成员变量也是对象
* java.lang.reflect.Field中封装了关于成员变量的操作
* getFields()方法获取的是所有的public的成员变量的信息
* getDeclaredFields()获取的是所有该类自己声明的成员变量的信息
*/
Field[] fs = c.getFields();
Field[] fs1 = c.getDeclaredFields();
for (Field field : fs1) {
//得到成员变量的类型的类类型
Class filedType = field.getType();
//得到成员变量类型的名称
String typeName = filedType.getName();
//得到成员变量的名称
String fieldName = field.getName();
System.out.println(typeName + " " + fieldName);
}
}
/**
* 测试
* @param args
*/
public static void main(String[] args) {
String s = "hello world";
//得到String类的所有局部变量以及类型
ClassOperateTwo.printFieldMessage(s);
//得到Integer类的所有局部变量以及类型
Integer i = 1;
ClassOperateTwo.printFieldMessage(i);
}
}
(3)获取类的构造方法的信息
package function.reflect;
import java.lang.reflect.Constructor;
/**
* Class类的基本操作
* 通过Class类获取类的构造函数的信息
*
* @author kimtian
*/
public class ClassOperateThree {
/**
* 打印构造函数的信息
*
* @param object 对象
*/
public static void printContructMessage(Object object) {
//获取类类型
Class c = object.getClass();
/**
* 构造函数也是对象
* java.lang.reflect.Constructor中封装了构造函数的信息
* Field类封装了关于成员变量的操作
* getConstructors()方法获取的是所有的public的构造函数的信息
* getDeclaredConstructors()获取的是所有的构造函数的信息,构造方法必须是自己声明的
*/
Constructor[] cs = c.getConstructors();
Constructor[] cs1 = c.getDeclaredConstructors();
for (Constructor constructor : cs) {
//得到构造方法的名称
System.out.print(constructor.getName() + "(");
//获取构造函数的参数列表-->得到的是参数列表的类类型
Class[] paramTypes = constructor.getParameterTypes();
for (int j = 0; j < paramTypes.length; j++) {
//为了美观,最后一个返回值不加标点符号
if (j == paramTypes.length - 1) {
System.out.print(paramTypes[j].getName());
} else {
System.out.print(paramTypes[j].getName() + ",");
}
}
System.out.println(")");
}
}
public static void main(String[] args) {
printContructMessage("hello");
printContructMessage(new Integer("1"));
}
}
(4)类的其他信息的获取
还可以获取类实现的接口,类的访问权限,类的父类等信息。
可以查看帮助文档和源码确认还可以获取哪些信息。
package function.reflect;
import java.lang.reflect.Modifier;
/**
* Class类的基本操作
* 通过Class类获取类的其他信息
*
* @author kimtian
*/
public class ClassOperateFour {
/**
* 打印构造函数的信息
*
* @param object 对象
*/
public static void printOtherMessage(Object object) {
//获取类类型
Class c = object.getClass();
/**
* 得到类实现的接口
*/
Class[] interfaces = c.getInterfaces();
for (Class class1 : interfaces) {
System.out.println(class1.getName());
}
/**
* 得到类的访问权限,返回的是一个int类型
*/
int a = c.getModifiers();
/**
* 由于int类型我们还是不好确认访问权限是什么,所以使用Modifier.toString将其转换
*/
String string = Modifier.toString(a);
System.out.println(string);
/**
* 得到包名称
*/
Package pk = c.getPackage();
System.out.println(pk);
/**
* 得到父类
*/
Class class2 = c.getSuperclass();
System.out.println(class2);
/**
* 还有很多信息可以获取,可自行查看源码和文档
*/
}
public static void main(String[] args) {
printOtherMessage("hello");
printOtherMessage(new Integer("1"));
}
}
4.方法反射的基本操作
语法: method.invoke(对象,参数列表)
package function.reflect;
import java.lang.reflect.Method;
/**
* 方法反射的基本操作
*
* @author kimtian
*/
public class ReflectMethod {
public static void main(String[] args) {
/**
* 获取print(int,int)方法
* 要获取一个方法就是获取类的信息,获取类的信息,首先要获取类的类类型
*/
Ademo a1 = new Ademo();
//获取类的类类型
Class c = a1.getClass();
/**
* 获取方法 名称和参数列表决定
* getMethod获取的是public方法
* getDeclaredMethods获取的是自己声明的方法
*/
try {
//要处理异常,万一没有这样的方法会引发异常
Method m = c.getMethod("print", new Class[]{int.class, int.class});
//基本使用该方法的方式
a1.print(10, 50);
/**
* 方法的反射操作是用m对象来进行方法调用,和上面a1.print调用的效果完全相同
* 方法如果没有返回值返回null,有返回值返回具体的返回值
*/
Object o = m.invoke(a1, new Object[]{15, 25});
System.out.println("====================================");
//也可以写成这样
Method m1 = c.getMethod("print", String.class, String.class);
Object o1 = m1.invoke(a1, "AAbb", "CCdd");
System.out.println("====================================");
/**
* 如果没有参数,这样使用
*/
Method m2 = c.getMethod("print", new Class[]{});
Method m3 = c.getMethod("print");
Object o2 = m3.invoke(a1, new Object[]{});
Object o3 = m2.invoke(a1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Ademo {
public void print() {
System.out.println("nihao");
}
public void print(int a, int b) {
System.out.println(a + b);
}
public void print(String a, String b) {
System.out.println(a.toUpperCase() + "," + b.toLowerCase());
}
}
5.集合泛型的本质
可以知道集合的泛型是为了防止错误输入,只在编译阶段有效。
而反射的操作都是编译后操作,在运行时操作。可以通过反射绕开集合的泛型编译校验,插入数据。
package function.reflect;
import java.lang.reflect.Method;
import java.util.ArrayList;
/**
* 通过Class,Method来认识泛型的本质
*
* @author kimtian
*/
public class ReflectGeneric {
public static void main(String[] args) {
ArrayList list = new ArrayList();
//泛型为String的例子
ArrayList<String> stringList = new ArrayList<String>();
stringList.add("hello");
System.out.println("集合的前期大小为:" + stringList.size());
//获取集合1的类类型
Class c1 = list.getClass();
//获取集合2的类类型
Class c2 = stringList.getClass();
/**
* 反射的操作都是编译之后的操作,运行时操作
* c1==c2 结果返回true说明编译之后集合是去泛型化的
* Java中集合的泛型是防止错误输入的,只在编译阶段有效,绕过编辑就无效了
* 验证:我们可以通过方法的反射来操作,绕过编译
*/
System.out.println(c1 == c2);
/**
* 验证:我们可以通过方法的反射来操作,绕过编译
*/
try {
Method m = c2.getMethod("add", Object.class);
//绕过编译操作就绕开了泛型
m.invoke(stringList, 10);
System.out.println("集合的后期大小为:" + stringList.size());
System.out.println(stringList);
/**
* 不能使用for循环去遍历,因为类型不同会报错
*/
// for (String s:stringList){
// System.out.println(s);
// }
} catch (Exception e) {
e.printStackTrace();
}
}
}