Java的反射机制
众所周知,Java是一门静态语言,及运行时结构不可变的语言就是静态语言。但反射机制的引入使得Java可以变为准动态语言,我们可以利用反射机制获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!
Reflection(反射)是Java被视为动态语言的关键,就像是找镜子,从对象得到类的信息.反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
那么反射到底能提供什么样的功能呢?
Java反射机制提供的功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
…
反射的优缺点
优点: 可以实现动态创建对象和编译,体现出很大的灵活性。
缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作。
Class对象
那么反射到底是如何形成的呢,首先我们看一下所有类的顶尖继承类—Object类,其内部有一个public final Class getClass()方法,这个方法在最顶尖的类中,因此所有的类都会包含这个方法,它的返回值是一个Class对象,我们可以通过对象反射求出类的名称。
对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
Class类到底有什么作用呢?
- Class 本身也是一个类
- Class 对象只能由系统建立对象
- 一个加载的类在 JVM 中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过Class可以完整地得到一个类中的所有被加载的结构
- Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象
方法名 | 功能说明 |
---|---|
static ClassforName(String name) | 返回指定类名name的Class对象 |
Object newInstance() | 调用缺省构造函数,返回Class对象的一个实例 |
getName() | 返回此Class对象所表示的实体(类,接口,数组类或void)的名称。 |
Class getSuperClass() | 返回当前Class对象的父类的Class对象 |
Class[] getinterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Constructor[] getConstructors() | 返回一个包含某些Constructor对象的数组 |
Method getMothed(String name,Class… T) | 返回一个Method对象,此对象的形参类型为paramType |
Field[] getDeclaredFields() | 返回Field对象的一个数组 |
反射的基础:Class对象链接:JAVA反射的基础–Class对象、类加载器.
创建运行时类的对象
在我们获得了Class类的实例后,有了Class对象,通过反射获取运行时类的完整结构
Field(对象)、Method(方法)、Constructor(构造器)、Superclass(父类Class)、Interface(接口)、Annotation(注解)…
创建类的对象
- 创建类的对象:调用Class对象的newInstance()方法
1. 类必须有一个无参数的构造器。
2. 类的构造器的访问权限需要足够
问题: 可以看到,上面的要求是必须是一个无参数构造器,难道没有无参的构造器就不能创建对象了吗?
答: 只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。也就是使用class对象来调用相应的构造器来创建对象。
步骤如下:
- 通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器
- 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
- 通过Constructor实例化对象
使用类的方法
- 通过Class类的getMethod(String name,Class…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
- 之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传
递要设置的obj对象的参数信息。
注意:
- Object 对应原方法的返回值,若原方法无返回值,此时返回null
- 若原方法若为静态方法,此时形参Object obj可为null
- 若原方法形参列表为空,则Object[] args为null
- 若原方法声明为private , 则需要在调用此invoke()方法前, 显式调用方法对象的setAccessible(true)方法,将可访问private的方法。
setAccessible: 作用是启动和禁用访问安全检查的开关。Method和Field、Constructor对象都有setAccessible()方法。
- 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
- 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
- 使得原本无法访问的私有成员也可以访问。
- 参数值为false则指示反射的对象应该实施Java语言访问检查
操作类的属性
依据上面的内容,我们可以获得类的class对象,通过反射当然也可以操作类的属性,对于类的属性,我们可以根据对应的方法得到Field对象,再通过set()方法进行赋值以达到操作类属性的目的,对于某些私有的属性,也可以用到上面提到的setAccessible(true)方法关闭Java语言访问检查来进行访问。
代码实例:
public static void main(String[] args){
Class<?> c1 = Class.forName("User");//forName("")中为实体类所在的位置,及URL,例如在com.PZY.reflection包下有一个User实体类,那么forName("com.PZY.reflection.User")
//forName()会抛出ClassNotFoundException异常
//1.通过动态调用构造方法,构造对象
System.out.println("*****************************************************");
User user = (User) c1.newInstance();// newInstance()会抛出IllegalAccessException, InstantiationException异常
//因为调用newInstance()会返回一个Object类对象,因此这里使用(User)对Object对象进行了强转
System.out.println(user);
//2.通过有参构造创建对象
System.out.println("*****************************************************");
Constructor<User> constructor = (Constructor<User>) c1.getDeclaredConstructor(String.class, int.class, int.class);//getDeclaredConstructor()会抛出NoSuchMethodException异常
User user2 = constructor.newInstance("钢铁侠", 54, 40);//newInstance(参数)会抛出InvocationTargetException异常
System.out.println(user2);
System.out.println("*****************************************************");
//3.调用普通方法
User user3=(User)c1.newInstance();
//获得指定的方法
Method method=c1.getDeclaredMethod("setName",String.class);
//调用invoke(方法执行的对象,方法参数的值)
method.invoke(user3,"钢铁侠");
System.out.println(user3.getName());
System.out.println("*****************************************************");
//4.操作属性(针对于private对象进行操作赋值)
User user4 = (User) c1.newInstance();
Field field = c1.getDeclaredField("name");
field.setAccessible(true); // 关闭权限访问检测 , 默认是false[打开的权限访问检测
//字段设置值 (对象 , 值)
field.set(user4,"钢铁侠");
System.out.println(user4.getName());
}
//User实体类
class User extends Object{
private String name;
private int id;
private int age;
public User() {
}
public User(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private void test(){
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
反射操作泛型
(需要自己下去再进行了解巩固)
Java采用泛型擦除的机制来引入泛型 , Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题 , 但是 , 一旦编译完成 , 所有和泛型有关的类型全部擦除。
为了通过反射操作这些类型 , Java新增了 ParameterizedType , GenericArrayType ,
TypeVariable和 WildcardType 几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
类型名 | 解释 |
---|---|
ParameterizedType | 表示一种参数化类型,比如Collection |
GenericArrayType | 表示一种元素类型是参数化类型或者类型变量的数组类型 |
TypeVariable | 是各种类型变量的公共父接口 |
WildcardType | 代表一种通配符类型表达式 |
public static void main(String[] args) throws Exception {
//获得指定方法的泛型信息
Method method = Test08.class.getDeclaredMethod("test01", Map.class, List.class);
//获得泛型参数类型信息
Type[] t = method.getGenericParameterTypes();
for (Type type : t) {
System.out.println("#"+type);
if (type instanceof ParameterizedType){
//getActualTypeArguments 获得真实的类型参数
Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("真实的泛型类型:"+actualTypeArgument);
}
}
}
//获得返回值泛型信息
Method method2 = Test08.class.getMethod("test2", null);
//获得泛型参数类型信息
//getGenericReturnType获得泛型返回值信息
Type genericReturnType = method2.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType){
//getActualTypeArguments 获得真实的类型参数
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("真实的返回值泛型类型:"+actualTypeArgument);
}
}
}
//测试反射获取泛型
@SuppressWarnings("all")//取消警告
public class Test08 {
//带有泛型参数的方法
public void test01(Map<String,User> map,List<User> list){
System.out.println("test01");
}
//带有泛型返回值的方法
public Map<Integer,User> test2(){
System.out.println("test02");
return null;
}