第二篇博客,是关于JAVA中的反射。文章中的图表、代码均为原创,如需转载,请注明出处。
一、JAVA中的反射的基本概念
1.1 什么是反射
1.2 反射的主要作用
二、反射的基本用法
2.1 获取Class对象的几种方式
2.2 Class中比较重要的一些方法
2.3 使用反射来执行方法
三、反射真的就那么无敌吗?
总结
JAVA中的反射是指,在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法的一种机制;也就是说,对于任意一个对象,我们都有手段能够调用它的任意方法和属性,无论这个方法和属性是public的还是private的;通常地,我们需要创建一个对象的时候,必定知道它是什么类的对象,知道这个类是用来干啥的。我们可以直接用new关键字对这个类进行实例化。而反射则恰恰相反,一开始我们并不知道我要初始化的类是什么,而是在在运行时才知道要操作的类是什么,并且可以使用 JDK 提供的反射 API 在运行时获取类的几乎所有信息。
其实上一节也大概提到了反射的作用,JAVA反射机制允许我们在运行时才去加载、使用一个编译期间完全未知的类,并且能够获取这个类的几乎所有信息。总结一下,大致有以下几点:
在学习反射用法之前,需要明确一些问题。首先,我们都知道,在面向对象的思想里,万物皆为对象,我们在代码中写的每一个类其实也都是一个对象,它们都是java.lang.Class的对象。比如说有一个Person类,一个Animal类,一个Book类,这些都是不同的类,但是也有共性:它们应该都有类名,属性,方法,构造器等等,现在我们可以用一个类来描述这些类,也就是java.lang.Class。
1.通过类名获取 类名.class
2.通过对象获取 对象名.getClass()
3.通过全限定名获取 Class.forName(全限定名)
全限定名指的是:包名.类型,如以下代码所示:
package com.example.javalib;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void sayHello() {
System.out.println("hello!");
}
}
package com.example.javalib;
public class MainClass {
public static void main(String[] args) throws ClassNotFoundException {
Person personOne = new Person();
Class personClass = Person.class;//1.通过类名获取 类名.class
Class aClass = personOne.getClass();//2.通过对象获取 对象名.getClass()
Class aClass2 = Class.forName("com.example.javalib.Person");//3.通过全限定名获取 Class.forName(全限定名)
}
}
除了上面所说的获取Class对象的几种方法之外,还有一些比较重要的用法需要关注。
1、获取Class对象的实例
通常,我们获取一个类的对象,是直接new出来,学习了反射之后,我们也可以使用Class类中的newInstance方法来获取“Class对象的对象”:
package com.example.javalib;
public class MainClass {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Person personOne = new Person();//通过new关键字实例化一个Person对象
Class aClass = Class.forName("com.example.javalib.Person");
Person personTwo = (Person)aClass.newInstance();//通过newInstance来实例化Person对象
//两个对象都可以成功调用sayHello方法
personOne.sayHello();
personTwo.sayHello();
}
}
上面是我们通过无参的newInstance获取到的Class对象的实例。我们可以看到,通过new创建的对象和通过newInstance方法创建的Person对象都可以成功调用Person类中的sayHello方法。
2、获取Class对象所表示的实体名称
我们可以用getName方法来获取Class对象所表示的实体名称:
package com.example.javalib;
public class MainClass {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class aClass = Class.forName("com.example.javalib.Person");
String className = aClass.getName();
System.out.println(className);
}
}
3、 获取当前Class对象的父类的Class对象
我们可以使用getSuperClass方法来获取当前Class对象的父类的Class对象:
package com.example.javalib;
public class MainClass {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class aClass = Class.forName("com.example.javalib.Person");
String className = aClass.getName();
Class superclass = aClass.getSuperclass();
Object o = superclass.newInstance();
}
}
4、获取当前Class对象实现的接口
我们让Person实现Serializable接口,然后尝试用getInterfaces方法来获取当前Class对象继承的接口:
package com.example.javalib;
public class MainClass {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class aClass = Class.forName("com.example.javalib.Person");
Class superclass = aClass.getSuperclass();
Class[] interfaces = aClass.getInterfaces();
for (Class anInterface : interfaces) {
System.out.println(anInterface.getName());
}
}
}
由上图可以看出,我们通过getInterfaces方法成功获取了Person所实现的接口。
5、获取Class对象的构造方法
使用getConstructors可以获得Class对象的的所有构造方法,如果要获取某个特定的构造方法,需要给getConstructors方法的传入对应的参数类型,并且再拿到构造方法后,我们自然也就能够调用用newInstance来创建对象:
package com.example.libjava;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class MainClass {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class> aClass = Class.forName("com.example.libjava.Person");
//获取Class对象的所有构造方法
Constructor>[] constructors = aClass.getConstructors();
for (Constructor> constructor : constructors) {
System.out.println("Constructor is: "+constructor);
}
//只获取特定的构造方法
Constructor> constructor = aClass.getConstructor( String.class,int.class);
System.out.println("Specific Constructor:"+constructor);
//使用构造器创建对象
Person person = (Person) constructor.newInstance("Marry", 22);
}
}
上面的代码获取了当前Class对象构造方法,不知道大家有没有注意到一个比较有意思的地方,我们在获取指定构造方法的时候,getConstructor的参数传的是getConstructor(String.class,int.class);
有人可能会问,int明明是一种基本数据类型,并不是一个类,为什么这里可以传入int.class而不是Integer.class呢?我们先来看看getConstructor方法的参数列表:
@CallerSensitive
public Constructor getConstructor(Class>... var1) throws NoSuchMethodException, SecurityException {
this.checkMemberAccess(0, Reflection.getCallerClass(), true);
return this.getConstructor0(var1, 0);
}
我们可以看到,这里需要传入的是Class类型的对象,这也就意味着int.class也是一个Class对象,不过这和我们常用的 类名.class 来获取一个Class的方法有所出入,因为int根本不是一个类,按理说不应该支持int.class这种写法,所以就比较奇怪,我们在Class类的官方文档中似乎找到了答案:
关于标蓝的部分,我个人的理解是,虽然基本数据类型不是类或接口,但依然可以有一个Class对象来代表他们,算是一种特例吧。同时,在经典书籍《JAVA核心技术》第五章 5.7节也指出“一个Class对象实际表示的是一种类型,而这个类型未必一定是一个类。”,以下是内容节选:
6、反射中其他关键API
除了上面所列举出的几个常用方法,反射还可以通过以下方法来获取Class对象的方法、属性等更多信息,用法和前面类似,本文就不再一一赘述,将常见用法整理成下表:
上文中说到,我们可以利用反射的一些API来获得构造器、方法、属性等,现在我们尝试使用反射来执行一个方法:
package com.example.libjava;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class MainClass {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class> aClass = Class.forName("com.example.libjava.Person");
//获取Class对象的指定构造器
Constructor> constructor = aClass.getConstructor(String.class, int.class);
//实例化一个对象
Object marry = constructor.newInstance("Marry", 25);
//获取sayhello方方法
Method sayHello = aClass.getMethod("sayHello");
//用实例化的marry对象执行sayhello方法
sayHello.invoke(marry);
}
}
上述代码中,我们先用forName获得了Class对象,然后使用getConstructor(String.class, int.class)获得的了指定参数列表的构造方法,接着使用newInstance("Marry", 25),实例化了一个对象,接着使用getMethod("sayHello", null)获取了sayhello这个方法,最后使用invoke对方法进行了执行,invoke方法中的第一个参数需要传入实例化的对象,从第二个参数起,需要传入准备调用方法的参数类型,这个案例中sayhello没有形参,所以没传。需要注意的是,如果这里我们准备调用的sayhello方法是一个private的方法,或者需要访问private的属性,都需要先使用sayHello.setAccessible(true);才可以通过invoke来执行。
从上文中,我们可以感受到反射的功能十分强大,它可以使程序变得十分灵活,避免把代码“写死”的情况。然而,我们知道,凡事有利必有弊,反射也是一把双刃剑,正是由于它过于“灵活”,就像开了挂一样,能够无视原有的访问修饰符限制,去访问类的几乎所有信息,所以也有人认为反射会一定程度上破坏代码的封装性,同时也会有一定的安全隐患。而且,由于反射是在运行时期才临时去获取、使用类的属性方法,导致反射操作的效率要比正常操作效率低很多,我们应该避免滥用反射,特别是在对性能要求很高的程序以及经常被执行的代码中,应当尽量避免使用反射。
上面是我学习JAVA泛型后整理的部分内容,如有错误还请各位大神指出,邮箱[email protected]