JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
一:Class类
在面向对象的世界里,万物皆对象。类也是对象,类是java.lang.Class类的实例对象。
Class类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
基本的 Java 类型(
boolean
、byte
、char
、short
、int
、long
、float
和double
)和关键字void
也表示为 Class 对象。Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的
defineClass
方法自动构造的。
上面来自于JDK的罗里吧嗦,下面我来说下自己的体会:
类不是抽象的,类是具体的!
类是.class字节码文件,要想获取一个Class实例对象,首先需要获取.class字节码文件!
然后调用Class对象的一些方法,进行动态获取信息以及动态调用对象方法!
二:类类型
新建一个Foo类。Foo这个类也是实例对象,是Class的实例对象。
不知道你是否在意过类的声明与方法的声明:
public class Foo{
Foo(){
//构造方法
}
}
public Foo method(){
//...
}
我们知道public后跟返回类型,也就可以知道class也是一个类型。
如何表示Class的实例对象?
public static void main(String[] args) {
//Foo的实例对象,new 就出来了
Foo foo1 = new Foo();
//如何表示?
//第一种:告诉我们任何一个类都有一个隐含的静态成员变量class
Class c1 = Foo.class;
//第二种:已经知道该类的对象通过getClass方法
Class c2 = foo1.getClass();
System.out.println(c1 == c2);
//第三种:动态加载
Class c3 = null;
try {
c3 = Class.forName("cn.zyzpp.reflect.Foo");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(c2 == c3);
}
上述打印结果全是true
尽管 c1或c2 都代表了Foo的类类型,一个类只能是Class类的一个实例变量。
我们完全可以通过类的类类型(Class类型)创建类的实例对象。
//此时c1 c2 c3为Class的实例对象
try {
// Foo foo = (Foo)c1.newInstance();
Foo foo = (Foo)c3.newInstance();
foo.print();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
静态加载
new 创建对象是静态加载类,在编译时刻就需要加载所有的可能使用到的类 。
动态加载
使用 Class.forName("类的全称") 加载类称作为动态加载 。
编译时刻加载类是静态加载类,运行时刻加载类是动态加载类。
举个例子
定义Office类
public class Office {
public void print() {
System.out.println("office");
}
}
定义Loading类
public class Loading {
public static void main(String[] args) {
try {
//在运行时再动态加载类
//arg[0] 为java执行命令时传的参数
Class<?> a = Class.forName(args[0]);
Office office = (Office) a.newInstance();
office.print();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
执行过程
D:\>javac -encoding utf-8 Loading.java Office.java
D:\>java Loading Office
office
通过Class a=Class.forName(arg[0])
动态加载获取类,因编译时不知道使用哪个类,因此编译没有加载任何类,直接通过编译,运行时,根据 java Loading Office
(office是一个类类型/类,下标arg[0]),去确定a是哪个类。这就是动态加载。如果Office类不存在,此时运行会报错。这就是为何有时候会出现编译通过,运行报错的原因。
动态加载一个好处,就是可以随时增加需要编译的类。例如把Office改造为抽象类或接口,定义不同的子类,动态选择加载。
三:类的反射
通过上面的三种方法获取到类的类类型,就可以获取到该类的成员方法,成员变量,方法参数注释等信息。
方法对象是Method类,一个成员方法就是一个Method对象。
方法 | 解释 |
---|---|
getMethods() | 返回该类继承以及自身声明的所有public的方法数组 |
getDeclaredMethods() | 返回该类自身声明的所有public的方法数组,不包括继承而来 |
成员变量也是对象,是java.lang.reflect.Field对象,Field类封装了关于成员变量的操作。
方法 | 解释 |
---|---|
getFields() | 获取所有的public的成员变量信息,包括继承的。 |
getDeclaredFields() | 获取该类自己声明的成员变量信息,public,private等 |
获取Java语言修饰符(public、private、final、static)的int返回值,再调用Modifier.toString()
获取修饰符的字符串形式,注意该方法会返回所有修饰符。
方法 | 解释 |
---|---|
getModifiers() | 以整数形式返回由此对象表示的字段的 Java 语言修饰符。 |
获取注释
方法 | 解释 |
---|---|
getAnnotations() | 返回此元素上存在的所有注释。 |
getDeclaredAnnotations() | 返回直接存在于此元素上的所有注释。 |
构造函数也是对象,是java.lang.reflect.Constructor的对象。
方法 | 解释 |
---|---|
getConstructors() | 返回所有public构造方法 |
getDeclaredConstructors() | 返回类的所有构造方法,不止public |
完整示例
private void printClassMessage(Object obj){
//要获取类的信息,首先获取类的类类型
Class clazz = obj.getClass();
//获取类的名称
System.out.println(Modifier.toString(clazz.getModifiers())+" "+ clazz.getClass().getName()+" "+clazz.getName()+"{");
System.out.println("----构造方法----");
//构造方法
Constructor[] constructors = clazz.getDeclaredConstructors();
for (Constructor constructor: constructors){
//构造方法修饰符与名字
System.out.print(Modifier.toString(constructor.getModifiers())+" "+constructor.getName()+"(");
//构造函数的所有参数类型
Class[] parameterTypes = constructor.getParameterTypes();
for (Class c: parameterTypes){
System.out.print(c.getName()+", ");
}
System.out.println("){}");
}
System.out.println("----成员变量----");
//成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field: fields){
System.out.println(" "+Modifier.toString(field.getModifiers())+" "+field.getType().getName() + " " + field.getName()+";");
}
System.out.println("----成员方法----");
//Method类,方法对象,一个成员方法就是一个Method对象
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods){
//获取方法返回类型
Class returnType = method.getReturnType();
//获取方法上的所有注释
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation: annotations){
//打印注释类型
System.out.println(" @"+annotation.annotationType().getName()+" ");
}
//打印方法声明
System.out.print(" "+Modifier.toString(returnType.getModifiers())+" "+returnType.getName()+" "+method.getName()+"(");
//获取方法的所有参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
//获取方法的所有参数
Parameter[] parameters = method.getParameters();
for (Parameter parameter: parameters){
//参数的类型,形参(全是arg123..)
System.out.print(parameter.getType().getName()+" "+parameter.getName()+", ");
}
System.out.println(")");
}
System.out.println("}");
}
以String对象为例,打印结果:
public final java.lang.Class java.lang.String{
----构造方法----
public java.lang.String([B, int, int, ){}
java.lang.String([C, boolean, ){}
----成员变量----
private final [C value;
private int hash;
----成员方法----
@java.lang.Deprecated
public abstract final void getBytes(int arg0, int arg1, [B arg2, int arg3, )
......
}
四:方法的反射
定义了一个类Foo用于测试
public class Foo{
public void print(String name,int num) {
System.out.println("I am "+name+" age "+num);
}
}
目标:通过反射获取该方法,传入参数,执行该方法!
1.获取类的方法就是获取类的信息,获取类的信息首先要获取类的类类型
Class clazz = Foo.class;
2.通过名称+参数类型获取方法对象
Method method = clazz.getMethod("print", new Class[]{String.class,int.class});
3.方法的反射操作是通过方法对象来调用该方法,达到和new Foo().print()一样的效果
方法若无返回值则返回null
Object o = method.invoke(new Foo(),new Object[]{"name",20});
五:通过反射认识泛型
public static void main(String[] args) {
ArrayList<String> stringArrayList = new ArrayList<>();
stringArrayList.add("hello");
ArrayList arrayList = new ArrayList();
Class c1 = stringArrayList.getClass();
Class c2 = arrayList.getClass();
System.out.println(c1 == c2);
}
打印结果为true
c1==c2的结果返回说明编译之后集合的泛型是去泛型化的。换句话说,泛型不同,对类型没有影响。
Java中集合的泛型其实只是为了防止错误输入,只在编译阶段有效,绕过编译就无效。
验证
我们可以通过反射来操作,绕过编译。
public static void main(String[] args) {
ArrayList<String> stringArrayList = new ArrayList<>();
stringArrayList.add("hello");
ArrayList arrayList = new ArrayList();
Class c1 = stringArrayList.getClass();
Class c2 = arrayList.getClass();
System.out.println(c1 == c2);
try {
Method method = c1.getMethod("add",Object.class);
method.invoke(stringArrayList,20);
System.out.println(stringArrayList.toString());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
打印结果:
true
[hello, 20]
成功绕过了泛型<String>的约束。