反射的运用很广泛,很多库都运用了反射,如Junit,EventBus,Gson,Retrofit,Spring等。动态代理,Android的Hook技术也离不开反射的身影。
目录:
1. 反射的概念
在Java运行时环境中,对于任意一个类,可以知道这个类有哪些属性和方法。对于任意一个对象,可以调用它的任意一个方法。
这种动态获取类的信息以及动态调用对象的方法的功能来自于Java 语言的反射(Reflection)机制。
通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。
反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
Java 反射主要提供以下功能:
看一张图大致感受下反射的机制:
对JVM不熟悉的同学,可以翻看我前面的文章《JVM篇 - JVM原理》https://blog.csdn.net/u014294681/article/details/85104210
2. 反射的使用
有三种方式可以获取Class对象:
(1) Class类的forName
静态方法
Class类不能被混淆,否则通过名称将找不到Class对象。
public static void main(String[] args) {
// 1. Class类的forName静态方法获取Class对象
try {
Class> cls = Class.forName("io.kzw.advance.csdn_blog.TestReflection");
System.out.printf(cls.toString());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
执行输出:class io.kzw.advance.csdn_blog.TestReflection
Class 的静态 forName() 方法有两个版本,上面的代码是只指定类名称的版本,而另一个版本可以让你指定类名称、加载时是否运行静态区块、指定类加载器:
static Class forName(String name, boolean initialize, ClassLoader loader)
默认在加载类的时候,如果类中有定义静态块则会运行它。你可以使用 forName() 的第二个版本,将 initialize 设定为 false,这样在加载类时并不会立即运行静态块,而会在使用类建立对象时才运行静态块。
public class Coder {
public String name;
public int age;
static {
System.out.println("Coder static block");
}
}
public class TestReflection {
public static void main(String[] args) {
// 1. Class类的forName静态方法获取Class对象
// try {
// /**
// * 输出
// * Coder static block
// * class io.kzw.advance.csdn_blog.Coder
// */
// Class> coderCls = Class.forName("io.kzw.advance.csdn_blog.Coder");
// System.out.println(coderCls.toString());
// } catch (ClassNotFoundException e) {
// e.printStackTrace();
// }
try {
/**
* 输出
* class io.kzw.advance.csdn_blog.Coder
*/
Class> personCls = Class.forName("io.kzw.advance.csdn_blog.Coder",
false, ClassLoader.getSystemClassLoader());
System.out.println(personCls.toString());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
/**
* 输出Coder static block
*/
Coder coder = new Coder();
}
}
(2) 直接获取某一个类的class对象
public static void main(String[] args) {
// 输出 class io.kzw.advance.csdn_blog.Coder
System.out.println(Coder.class.toString());
}
(3) 调用某个对象的getClass()
public static void main(String[] args) {
Coder coder = new Coder();
// 输出 class io.kzw.advance.csdn_blog.Coder
System.out.println(coder.getClass().toString());
}
类名.class和对象.getClass()几乎没有区别,因为一个类被类加载器加载后,就是唯一的一个类对象。
一般地,我们用instanceof
关键字来判断是否为某个类的实例。同时我们也可以借助反射中Class对象的isInstance()
方法来判断是否为某个类的实例,它是一个 native 方法:
public native boolean isInstance(Object obj)
public static void main(String[] args) {
Coder coder = new Coder();
try {
Class> cls = Class.forName("io.kzw.advance.csdn_blog.Coder");
// 输出true
System.out.println(cls.isInstance(coder));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
通过反射来生成对象主要有两种方式:
(1) 使用Class对象的newInstance()方法来创建Class对象对应类的实例
public static void main(String[] args) {
Class> cls = Coder.class;
try {
Coder coder = (Coder) cls.newInstance();
coder.name = "k";
coder.age = 18;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
(2) 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例
public class Coder {
public String name;
public int age;
public Coder() {
}
public Coder(String name, int age) {
this.name = name;
this.age = age;
}
}
public static void main(String[] args) {
try {
Class> c = Coder.class;
Constructor constructor = c.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("k", 18);
System.out.println(obj);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
获取某个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)
看一个例子:
public class Coder {
public String name;
public int age;
public Coder() {
}
public Coder(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public static void main(String[] args) {
Class> c = Coder.class;
try {
Object object = c.newInstance();
Method[] methods = c.getMethods();
Method[] declaredMethods = c.getDeclaredMethods();
Method method = c.getMethod("setName", String.class);
System.out.println("getMethods获取的方法:");
for(Method m:methods)
System.out.println(m);
System.out.println("getDeclaredMethods获取的方法:");
for(Method m:declaredMethods)
System.out.println(m);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
执行输出:
getMethods获取的方法:
public java.lang.String io.kzw.advance.csdn_blog.Coder.getName()
public void io.kzw.advance.csdn_blog.Coder.setName(java.lang.String)
public int io.kzw.advance.csdn_blog.Coder.getAge()
public void io.kzw.advance.csdn_blog.Coder.setAge(int)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
getDeclaredMethods获取的方法:
public java.lang.String io.kzw.advance.csdn_blog.Coder.getName()
public void io.kzw.advance.csdn_blog.Coder.setName(java.lang.String)
public int io.kzw.advance.csdn_blog.Coder.getAge()
public void io.kzw.advance.csdn_blog.Coder.setAge(int)
获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:
public T newInstance(Object ... initargs)
此方法可以根据传入的参数来调用对应的Constructor创建对象实例。
主要是这几个方法,在此不再赘述:
getFiled
:访问公有的成员变量getDeclaredField
:所有已声明的成员变量,但不能得到其父类的成员变量getFileds
和 getDeclaredFields
方法用法同上(参照 Method)。
public static void main(String[] args) {
Class> c = Coder.class;
try {
Coder coder = (Coder) c.newInstance();
Method method = c.getMethod("setName", String.class);
method.invoke(coder, "k");
// 输出k
System.out.println(coder.getName());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
上面的setName方法是public的,如果改成private呢?
会执行报错:
java.lang.NoSuchMethodException: io.kzw.advance.csdn_blog.Coder.setName(java.lang.String)
at java.lang.Class.getMethod(Class.java:1786)
at io.kzw.advance.csdn_blog.TestReflection.main(TestReflection.java:13)
public static void main(String[] args) {
Class> c = Coder.class;
try {
Coder coder = (Coder) c.newInstance();
// 改成getDeclaredMethod
Method method = c.getDeclaredMethod("setName", String.class);
// 设权限可以访问
method.setAccessible(true);
method.invoke(coder, "k");
System.out.println(coder.getName());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
这样就可以了,获取私有变量也是一样。
数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference。下面我们看一看利用反射创建数组的例子:
public static void main(String[] args) {
try {
Class> cls = Class.forName("java.lang.String");
Object array = Array.newInstance(cls,25);
Array.set(array,0,"a");
Array.set(array,1,"b");
Array.set(array,2,"c");
Array.set(array,3,"d");
Array.set(array,4,"e");
// 输出d
System.out.println(Array.get(array,3));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
3. 反射的优缺点
反射被广泛地用于那些需要在运行时检测或修改程序行为的程序中。这是一个相对高级的特性,只有那些语言基础非常扎实的开发者才应该使用它。如果能把这句警示时刻放在心里,那么反射机制就会成为一项强大的技术,可以让应用程序做一些几乎不可能做到的事情。
反射的缺点:
4. 如何提高反射效率
getMethods()
后再遍历筛选,而直接用getMethod(methodName)
来根据方法名获取方法。void createInstance(String className){
cachedClass = cache.get(className);
if (cachedClass == null) {
// Class.forName耗时
cachedClass = Class.forName(className);
cache.set(className, cachedClass);
}
return cachedClass.newInstance();
}
5. 面试中的反射问题
如果你看了上面的问题,我相信大部分反射面试题都没问题,但是现在的面试题一般不是死记硬背式,而是结合实际例子,考察你会不会活学活用,我这边分享2个问题。
public class SysConstant {
public static final Integer INT_VALUE = 100;
}
public static void main(String[] args) {
try {
System.out.println(SysConstant.INT_VALUE);
Field field = SysConstant.class.getDeclaredField("INT_VALUE");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, 200);
System.out.println(SysConstant.INT_VALUE);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
执行输出:
100
200
说明反射常量成功。
我们再试试String:
public class SysConstant {
public static final String VERSION = "1.0.0";
}
public static void main(String[] args) {
try {
System.out.println(SysConstant.VERSION);
Field field = SysConstant.class.getDeclaredField("VERSION");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, "2.0.0");
System.out.println(SysConstant.VERSION);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
执行输出:
1.0.0
1.0.0
反射没有生效,除了String,然后又试了int、long、boolean,都是反射没生效。
原因:
对于基本类型的静态常量,JAVA在编译的时候就会把代码中对此常量中引用的地方替换成相应常量值。
通过第一个问题,就很容易回答了,如果是基本数据类型,包括String,不会影响程序逻辑。
public class SysConstant {
public static final String VERSION = "1.0.0";
public static final boolean DEBUG = true;
public static void print() {
if (VERSION.equals("1.0.0")) {
System.out.println("1.0.0");
}
}
public static void register() {
if (DEBUG) {
System.out.println("register");
}
}
}
看看编译后的SysConstant.class:
public class SysConstant {
public static final String VERSION = "1.0.0";
public static final boolean DEBUG = true;
public SysConstant() {
}
public static void print() {
if ("1.0.0".equals("1.0.0")) {
System.out.println("1.0.0");
}
}
public static void register() {
System.out.println("register");
}
}
对于基本类型的静态常量,JAVA在编译的时候就会把代码中对此常量中引用的地方替换成相应常量值。