一、什么是反射
反射:Reflection
反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。
通过Class
实例获取class
信息的方法称为反射(Reflection)。
二、Class类
class
是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class
类型时,将其加载进内存。
每加载一种class
,JVM就为其创建一个Class
类型的实例,并关联起来!
这里的Class
类型是一个名叫Class
的class
。。。。
1、以String
类为例,当JVM加载String
类时,它首先读取String.class
文件到内存,然后,为String
类创建一个Class
实例并关联起来
2、Class
实例是JVM内部创建的,如果我们查看JDK源码,可以发现Class
类的构造方法是private
,只有JVM能创建Class
实例,我们自己的Java程序是无法创建Class
实例的。
3、此处为JVM自动创建,是一个私有类!
1 Class cls = new Class(String);
一个Class
实例包含了该class
的所有完整信息!
由于JVM为每个加载的class
创建了对应的Class
实例,并在实例中保存了该class
的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class
实例,我们就可以通过这个Class
实例获取到该实例对应的class
的所有信息。
这种通过Class
实例获取class
信息的方法称为反射(Reflection)。
1、创建CLass
直接通过一个class
的静态变量class
获取:
通过该实例变量提供的getClass()
方法获取:
通过静态方法Class.forName()
获取,参数为完整类名:
因为Class
实例在JVM中是唯一的,所以,上述方法获取的Class
实例是同一个实例。可以用==
比较两个Class
实例:
1 2 3 4 5 String s = "" ; Class cls = String.class; Class cls1 = s.getClass(); Class cls2 = Class.forName("java.lang.String" ); System.out.println(cls == cls1);
2、==
和 instanceof()
instanceof可以判断子类及继承关系!
==
可以用来精确判断类的类型!
通常情况下,我们应该用instanceof
判断数据类型,因为面向抽象编程的时候,我们不关心具体的子类型。只有在需要精确判断一个类型是不是某个class
的时候,我们才使用==
判断class
实例。
1 2 3 4 5 6 7 Integer n = new Integer(123 );boolean b1 = n instanceof Integer; boolean b2 = n instanceof Number; boolean b3 = n.getClass() == Integer.class; boolean b4 = n.getClass() == Number.class;
3、通过Class来获取相关信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.test;public class reflection { public static void main (String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { printClassInfo("" .getClass()); printClassInfo(Runnable.class); printClassInfo(java.time.Month.class); printClassInfo(String[].class); printClassInfo(int .class); System.out.println("end" ); } static void printClassInfo (Class cls) { System.out.println("Class name: " + cls.getName()); System.out.println("Simple name: " + cls.getSimpleName()); if (cls.getPackage() != null ) { System.out.println("Package name: " + cls.getPackage().getName()); } System.out.println("is interface: " + cls.isInterface()); System.out.println("is enum: " + cls.isEnum()); System.out.println("is array: " + cls.isArray()); System.out.println("is primitive: " + cls.isPrimitive()); } }
输出结果:
数组(例如String[]
)也是一种Class
,而且不同于String.class
,它的类名是[Ljava.lang.String
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 Class name: java.lang.String Simple name: String Package name: java.lang is interface: false is enum: false is array: false is primitive: false Class name: java.lang.Runnable Simple name: Runnable Package name: java.lang is interface: true is enum: false is array: false is primitive: false Class name: java.time.Month Simple name: Month Package name: java.time is interface: false is enum: true is array: false is primitive: false Class name: [Ljava.lang.String; Simple name: String[] is interface: false is enum: false is array: true is primitive: false Class name: int Simple name: int is interface: false is enum: false is array: false is primitive: true end
4、动态加载
JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载:
当执行Main.java
时,由于用到了Main
,因此,JVM首先会把Main.class
加载到内存。然而,并不会加载Person.class
,除非程序执行到create()
方法,JVM发现需要加载Person
类时,才会首次加载Person.class
。如果没有执行create()
方法,那么Person.class
根本就不会被加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.test;public class reflectionTest { public static void main (String[] args) { if (args.length > 0 ){ create(args[0 ]); } } static void create (String name) { Person1 p = new Person1(name); } }class Person1 { public String name; public Person1 (String name) { this .name = name; } }
5、Log的动态加载
动态加载class
的特性对于Java程序非常重要。利用JVM动态加载class
的特性,我们才能在运行期根据条件加载不同的实现类。例如,Commons Logging总是优先使用Log4j,只有当Log4j不存在时,才使用JDK的logging。利用JVM动态加载特性,大致的实现代码如下:
所以我们只需要把Log4j的jar包放到classpath中,Commons Logging就会自动使用Log4j的原因。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 LogFactory factory = null ;if (isClassPresent("org.apache.logging.log4j.Logger" )) { factory = createLog4j(); } else { factory = createJdkLog(); }boolean isClassPresent (String name) { try { Class.forName(name); return true ; } catch (Exception e) { return false ; } }
三、访问字段
1、访问字段
Field getField(name):根据字段名获取某个public的field(包括父类)
Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
Field[] getFields():获取所有public的field(包括父类)
Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Main { public static void main (String[] args) throws Exception { Class stdClass = Student.class; System.out.println(stdClass.getField("score" )); System.out.println(stdClass.getField("name" )); System.out.println(stdClass.getDeclaredField("grade" )); } }class Student extends Person { public int score; private int grade; }class Person { public String name; }
1 2 3 public int Student.score public java.lang.String Person.name private int Student.grade
2、field对象方法
一个Field
对象包含了一个字段的所有信息:
getName()
:返回字段名称,例如,"name"
;
getType()
:返回字段类型,也是一个Class
实例,例如,String.class
;
getModifiers()
:返回字段的修饰符,它是一个int
,不同的bit表示不同的含义。
String类的value字段如下定义:
1 2 3 public final class String { private final byte [] value; }
获取所有信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.test;import java.lang.reflect.Field;import java.lang.reflect.Modifier;public class field { public static void main (String[] args) throws Exception { Field f = String.class.getDeclaredField("value" ); System.out.println(f.getName()); System.out.println(f.getType()); int m = f.getModifiers(); System.out.println(m); System.out.println(Modifier.isFinal(m)); System.out.println(Modifier.isPublic(m)); System.out.println(Modifier.isProtected(m)); System.out.println(Modifier.isPrivate(m)); System.out.println(Modifier.isStatic(m)); } }
3、获取字段值
先获取Class
实例:
再获取Field
实例:
然后,用Field.get(Object)
获取指定实例的指定字段的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.test;import java.lang.reflect.Field;public class getField { public static void main (String[] args) throws Exception { Object p = new Person3("Xiao Ming" ); Class c = p.getClass(); Field f = c.getDeclaredField("name" ); Object value = f.get(p); System.out.println(value); } }class Person3 { public String name; public Person3 (String name) { this .name = name; } }
直接访问会得到一个IllegalAccessException
!
解决方法:
在调用Object value = f.get(p);
前,先写一句:
调用Field.setAccessible(true)
的意思是,别管这个字段是不是public
,一律允许访问。
如果使用反射可以获取private
字段的值,那么类的封装还有什么意义?
答案是正常情况下,我们总是通过p.name
来访问Person
的name
字段,编译器会根据public
、protected
和private
决定是否允许访问字段,这样就达到了数据封装的目的。
而反射是一种非常规的用法,使用反射,首先代码非常繁琐,其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值。
此外,setAccessible(true)
可能会失败。如果JVM运行期存在SecurityManager
,那么它会根据规则进行检查,有可能阻止setAccessible(true)
。例如,某个SecurityManager
可能不允许对java
和javax
开头的package
的类调用setAccessible(true)
,这样可以保证JVM核心库的安全。
修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.test;import java.lang.reflect.Field;public class getField { public static void main (String[] args) throws Exception { Object p = new Person3("Xiao Ming" ); Class c = p.getClass(); Field f = c.getDeclaredField("name" ); f.setAccessible(true ); Object value = f.get(p); System.out.println(value); } }class Person3 { private String name; public Person3 (String name) { this .name = name; } }
4、设置字段值
设置字段值是通过Field.set(Object, Object)
实现的,其中第一个Object
参数是指定的实例,第二个Object
参数是待修改的值:
修改非public
字段,需要首先调用setAccessible(true)
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.test;import java.lang.reflect.Field;public class getField { public static void main (String[] args) throws Exception { Person3 p = new Person3("Xiao Ming" ); System.out.println(p.getName()); Class c = p.getClass(); Field f = c.getDeclaredField("name" ); f.setAccessible(true ); f.set(p, "Xiao Hong" ); System.out.println(p.getName()); } }class Person3 { private String name; public Person3 (String name) { this .name = name; } public String getName () { return name; } }
四、调用方法
1、调用方法
我们已经能通过Class
实例获取所有Field
对象,同样的,可以通过Class
实例获取所有Method
信息。Class
类提供了以下几个方法来获取Method
:
Method getMethod(name, Class...)
:获取某个public
的Method
(包括父类)
Method getDeclaredMethod(name, Class...)
:获取当前类的某个Method
(不包括父类)
Method[] getMethods()
:获取所有public
的Method
(包括父类)
Method[] getDeclaredMethods()
:获取当前类的所有Method
(不包括父类)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package com.test;public class method { public static void main (String[] args) throws Exception { Class stdClass = Students.class; System.out.println(stdClass.getMethod("getScore" , String.class)); System.out.println(stdClass.getMethod("getName" )); System.out.println(stdClass.getDeclaredMethod("getGrade" , int .class)); } }class Students extends Person4 { public int getScore (String type) { return 99 ; } private int getGrade (int year) { return 1 ; } }class Person4 { public String getName () { return "Person" ; } }
输出结果:
1 2 3 public int com.test.Students.getScore(java.lang.String) public java.lang.String com.test.Person4.getName() private int com.test.Students.getGrade(int)
2、method对象方法
一个Method
对象包含一个方法的所有信息:
getName()
:返回方法名称,例如:"getScore"
;
getReturnType()
:返回方法返回值类型,也是一个Class实例,例如:String.class
;
getParameterTypes()
:返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
;
getModifiers()
:返回方法的修饰符,它是一个int
,不同的bit表示不同的含义。
3、调用方法
用反射来调用substring
方法:
对Method
实例调用invoke
就相当于调用该方法,invoke
的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.test;import java.lang.reflect.Method;public class getMethod { public static void main (String[] args) throws Exception { String s = "Hello world" ; Method m = String.class.getMethod("substring" , int .class); System.out.println(m); String r = (String) m.invoke(s, 6 ); System.out.println(r); } }
输出结果如下:
1 2 public java.lang.String java.lang.String.substring(int) world
4、调用静态方法
如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke
方法传入的第一个参数永远为null
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.test;import java.lang.reflect.Method;public class Main { public static void main (String[] args) throws Exception { Method m = Integer.class.getMethod("parseInt" , String.class); Integer n = (Integer) m.invoke(null , "12345" ); System.out.println(n); } }
5、调用非public方法
和Field类似,对于非public方法,我们虽然可以通过Class.getDeclaredMethod()
获取该方法实例,但直接对其调用将得到一个IllegalAccessException
。为了调用非public方法,我们通过Method.setAccessible(true)
允许其调用:
此外,setAccessible(true)
可能会失败。如果JVM运行期存在SecurityManager
,那么它会根据规则进行检查,有可能阻止setAccessible(true)
。例如,某个SecurityManager
可能不允许对java
和javax
开头的package
的类调用setAccessible(true)
,这样可以保证JVM核心库的安全。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.test;import java.lang.reflect.Method;public class Main { public static void main (String[] args) throws Exception { Person p = new Person(); Method m = p.getClass().getDeclaredMethod("setName" , String.class); m.setAccessible(true ); m.invoke(p, "Bob" ); System.out.println(p.name); } }class Person { String name; private void setName (String name) { this .name = name; } }
6、多态
我们来考察这样一种情况:一个Person
类定义了hello()
方法,并且它的子类Student
也覆写了hello()
方法,那么,从Person.class
获取的Method
,作用于Student
实例时,调用的方法到底是哪个?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.test;import java.lang.reflect.Method;public class Main { public static void main (String[] args) throws Exception { Method h = Person.class.getMethod("hello" ); h.invoke(new Student()); } }class Person { public void hello () { System.out.println("Person:hello" ); } }class Student extends Person { public void hello () { System.out.println("Student:hello" ); } }
使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法(如果存在)
1 2 3 4 5 6 Method m = Person.class.getMethod("hello" ); m.invoke(new Student()); Person p = new Student(); p.hello();
7、链式编程
1 2 3 4 5 6 7 8 9 Method m1 = Integer.class.getMethod("parseInt" , String.class); Integer n = (Integer) m1.invoke(null , "12345" ); System.out.println(n); Integer n = (Integer) Integer.class.getMethod("parseInt" , String.class).invoke(null , "123456" ); System.out.println(n);
五、调用构造方法
1、通过调用Class提供的newInstance()方法来创建构造方法实例
调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
1 Person p = Person.class.newInstance();
2、通过调用Java的反射API提供了Constructor对象来创建构造方法实例
通过Class实例获取Constructor的方法如下:
getConstructor(Class...)
:获取某个public
的Constructor
;
getDeclaredConstructor(Class...)
:获取某个Constructor
;
getConstructors()
:获取所有public
的Constructor
;
getDeclaredConstructors()
:获取所有Constructor
。
注意Constructor
总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。
调用非public
的Constructor
时,必须首先通过setAccessible(true)
设置允许访问。setAccessible(true)
可能会失败。
Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.lang.reflect.Constructor;public class Main { public static void main (String[] args) throws Exception { Constructor cons1 = Integer.class.getConstructor(int .class); Integer n1 = (Integer) cons1.newInstance(123 ); System.out.println(n1); Constructor cons2 = Integer.class.getConstructor(String.class); Integer n2 = (Integer) cons2.newInstance("456" ); System.out.println(n2); } }
六、获取继承关系
1、获取父类Class
1 2 3 4 5 6 7 8 9 10 public class Main { public static void main (String[] args) throws Exception { Class i = Integer.class; Class n = i.getSuperclass(); System.out.println(n); Class o = n.getSuperclass(); System.out.println(o); System.out.println(o.getSuperclass()); } }
输出结果:
Integer
的父类类型是Number
,Number
的父类是Object
,Object
的父类是null
。
除Object
外,其他任何非interface
的Class
都必定存在一个父类类型。
1 2 3 class java.lang.Number class java.lang.Object null
2、获取接口Interface
如果一个类没有实现任何interface
,那么getInterfaces()
返回空数组。
1 2 3 4 5 6 7 8 9 10 public class Main { public static void main (String[] args) throws Exception { Class s = Integer.class; Class[] is = s.getInterfaces(); for (Class i : is) { System.out.println(i); } } }
输出结果:
可知Integer有以下三个接口:
1 2 3 interface java.lang.Comparable interface java.lang.constant.Constable interface java.lang.constant.ConstantDesc
要特别注意:getInterfaces()
只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型:
1 2 3 4 5 6 7 8 9 10 public class Main { public static void main (String[] args) throws Exception { Class s = Integer.class.getSuperclass(); System.out.println(s); Class[] is = s.getInterfaces(); for (Class i : is) { System.out.println(i); } } }
输出结果如下:
可知Number的接口只有下面这一个:
1 2 class java.lang.Number interface java.io.Serializable
接口使用getSuperclass()
方法永远返回null
:
3、Class的继承关系
判断普通实例使用instanceof ()
方法:
判断Class实例使用isAssignableFrom()
方法:
可以这样简单理解:括号内的是否继承自外面的!
1 2 3 4 5 6 7 8 Integer.class.isAssignableFrom(Integer.class); Number.class.isAssignableFrom(Integer.class); Object.class.isAssignableFrom(Integer.class); Integer.class.isAssignableFrom(Number.class);
七、动态代理(Dynamic Proxy)
1、class 和 interface
class
和interface
的区别:
可以实例化class
(非abstract
);
不能实例化interface
。
2、静态代理实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.test;public class DynamicProxy { public static void main (String[] args) { Hello hello = new HelloWorlds(); hello.morning("Bob" ); } }class HelloWorlds implements Hello { public void morning (String name) { System.out.println("Good morning, " + name); } }interface Hello { void morning (String name) ; }
3、动态代理实现
在运行期动态创建一个interface
实例的方法如下:
定义一个InvocationHandler
实例,它负责实现接口的方法调用;
通过Proxy.newProxyInstance()
创建interface
实例,它需要3个参数:
使用的ClassLoader
,通常就是接口类的ClassLoader
;
需要实现的接口数组,至少需要传入一个接口进去;
用来处理接口方法调用的InvocationHandler
实例。
将返回的Object
强制转型为接口。
ClassLoader,它是类加载器,用来将.class字节码加载转换成class类对象的!
InvocationHandler
相当于:第一个参数传入了创建的实例hello
,第二个参数传入了Hello
的抽象方法morning
,第三个参数传入了抽象方法morning
的参数BOb
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.test;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class DynamicProxy { public static void main (String[] args) { InvocationHandler handler = new InvocationHandler() { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method); if (method.getName().equals("morning" )) { System.out.println("Good morning, " + args[0 ]); } return null ; } }; Hello1 hello = (Hello1) Proxy.newProxyInstance( Hello1.class.getClassLoader(), new Class[] { Hello1.class }, handler); hello.morning("Bob" ); } }interface Hello1 { void morning (String name) ; }
输出结果:
1 2 public abstract void com.test.Hello1.morning(java.lang.String) Good morning, Bob
4、动态代理实际实现方法
动态代理实际上是JDK在运行期动态创建class字节码并加载的过程,它并没有什么黑魔法,把上面的动态代理改写为静态实现类大概长这样:
其实就是JDK帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码),并不存在可以直接实例化接口的黑魔法。
目前我还不太会调用。。。
1 2 3 4 5 6 7 8 9 10 11 12 public class HelloDynamicProxy implements Hello { InvocationHandler handler; public HelloDynamicProxy (InvocationHandler handler) { this .handler = handler; } public void morning (String name) { handler.invoke( this , Hello.class.getMethod("morning" ), new Object[] { name }); } }