反射(Reflection)是Java 程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。官方的解释如下:
Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.
简单的来说,通过反射,我们可以在运行时获得任意一个类的字节码,包括接口、变量、方法等信息。
Java反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。
反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
1.在运行时判断任意一个对象所属的类;
2.在运行时构造任意一个类的对象;
3.在运行时判断任意一个类所具有的成员变量和方法
4.在运行时访问对象的属性,方法,构造方法等。
1、获取某些类的一些变量,调用某些类的私有(private)方法。
2、增加代码的灵活性。很多主流框架都使用了反射技术,我们熟悉的spring等优秀的开源框架都采用两种技术 xml做配置文件+反射实现。
1、使用Class类的forName静态方法
String className = ... ;//在运行期获取的类名字符串
Class class = Class.forName(className);
2、直接获取某一个对象的class
Class> classInt = Integer.TYPE;
3、调用某个对象的getClass()方法
StringBuilder str = new StringBuilder("123");
Class> klass = str.getClass();
三种方式我们常用的是第一种,第二种需要导入类的包,依赖太强,不导包就抛编译错误,第三种对象都有了还要反射干什么。一般都第一种,一个字符串可以传入也可写在配置文件中等多种方法。
一般地,我们用instanceof关键字来判断是否为某个类的实例。同时我们也可以借助反射中Class对象的isInstance()方法来判断是否为某个类的实例,它是一个Native方法:
public native boolean isInstance(Object obj);
(1)使用Class对象的newInstance()方法来创建Class对象对应类的实例。
Class> c = String.class;
Object str = c.newInstance();
(2)先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。
//获取String所对应的Class对象
Class> c = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("23333");
System.out.println(obj);
获取某个Class对象的方法集合,主要有以下几个方法:
1、getDeclaredMethods()方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。public Method[] getDeclaredMethods() throws SecurityException
2、getMethods()方法返回某个类的所有公用(public)方法,包括其继承类的公用方法。
public Method[] getMethods() throws SecurityException
3、getMethod方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象
public Method getMethod(String name, Class>... parameterTypes)
获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例。此方法可以根据传入的参数来调用对应的Constructor创建对象实例。
public T newInstance(Object ... initargs)
方法 | 描述 |
---|---|
public Constructor getConstructor(Class… parameterTypes) | 获得指定的构造方法,注意只能获得 public 权限的构造方法,其他访问权限的获取不到 |
public Constructor getDeclaredConstructor(Class… parameterTypes) | 获得指定的构造方法,注意可以获取到任何访问权限的构造方法。 |
public Constructor[] getConstructors() throws SecurityException | 获得所有 public 访问权限的构造方法 |
public Constructor[] getDeclaredConstructors() throws SecurityException | 获得所有的构造方法,包括(public, private,protected,默认权限的) |
主要是这几个方法,在此不再赘述:
getFiled: 访问公有的成员变量
getDeclaredField:所有已声明的成员变量。但不能得到其父类的成员变量
getFileds和getDeclaredFields用法同上(参照Method)调用方法
当我们从类中获取了一个方法后,我们就可以用invoke()方法来调用这个方法。invoke方法的原型为:
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
下面是实例:
public class test1 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class> klass = methodClass.class;
//创建methodClass的实例
Object obj = klass.newInstance();
//获取methodClass类的add方法
Method method = klass.getMethod("add",int.class,int.class);
//调用method对应的方法 => add(1,4)
Object result = method.invoke(obj,1,4);
System.out.println(result);
}
}
class methodClass {
public final int fuck = 3;
public int add(int a,int b) {
return a+b;
}
public int sub(int a,int b) {
return a+b;
}
}
数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference。下面我们看一看利用反射创建数组的例子:
/**
* 利用反射操作数组
* 1 利用反射修改数组中的元素
* 2 利用反射获取数组中的每个元素
*/
public static void testArrayClass() {
String[] strArray = new String[]{"5","7","暑期","美女","女生","女神"};
Array.set(strArray,0,"帅哥");
Class clazz = strArray.getClass();
if (clazz.isArray()) {
int length = Array.getLength(strArray);
for (int i = 0; i < length; i++) {
Object object = Array.get(strArray, i);
String className=object.getClass().getName();
System.out.println("----> object=" + object+",className="+className);
}
}
}
从结果可以说明,我们成功通过 Array.set(strArray,0,”帅哥”) 改变数组的值。
public static void getGenericHelper(HashMap map) {
}
现在假设我们有这样一个方法,那我们要怎样获得 HashMap 里面的 String,Person 的类型呢?
public static void getGenericType() {
try {
Method method =TestHelper.class.getDeclaredMethod("getGenericHelper",HashMap.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
// 检验是否为空
if (null == genericParameterTypes || genericParameterTypes.length < 1) {
return ;
}
// 取 getGenericHelper 方法的第一个参数
ParameterizedType parameterizedType=(ParameterizedType)genericParameterTypes[0];
Type rawType = parameterizedType.getRawType();
System.out.println("----> rawType=" + rawType);
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
if (actualTypeArguments==genericParameterTypes || actualTypeArguments.length<1) {
return ;
}
// 打印出每一个类型
for (int i = 0; i < actualTypeArguments.length; i++) {
Type type = actualTypeArguments[i];
System.out.println("----> type=" + type);
}
} catch (Exception e) {
}
}
执行上面的代码,输出结果:—-> rawType=class java.util.HashMap
—-> type=class java.lang.String
—-> type=class com.example.reflectdemo.Person
1、反射的优点
动态创建对象和编译,灵活性强。特别适合开源框架的开发。
方便获取类的各种内容,动态调用类的各方法,使用类的各属性,设置是私有方法。
2、反射的缺点
反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。
反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
参考:深入解析Java反射(1)