Java反射机制(1)https://developer.aliyun.com/article/1530899
1.调用方法
当我们获取到一个Method类对象时,就可以对它进行调用。我们以下面的代码为例:
// 一般情况下调用 String 类的 substring() 方法 String s = "Hello world"; String r = s.substring(6); // "world" 如果用反射来调用substring方法,需要以下代码:
import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { // String 对象: String s = "Hello world"; // 获取 String substring(int)方法,形参为 int: Method m = String.class.getMethod("substring", int.class); // 在 s 对象上调用该方法并获取结果: String r = (String) m.invoke(s, 6); // 打印调用结果: System.out.println(r); } }
import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { // String 对象: String s = "Hello world"; // 获取 String substring(int)方法,形参为 int: Method m = String.class.getMethod("substring", int.class); //获得方法 的所属的类 System.out.println(m.getDeclaringClass()); // 在 s 对象上调用该方法并获取结果: String r = (String) m.invoke("qqqqqqqqq", 6); // 打印调用结果: System.out.println(r); } }
- 注意到substring()有两个重载方法,我们获取的是String substring(int)这个方法(即形参类型为 int,且只有一个)。思考一下如何获取String substring(int, int)方法。
- 对Method类对象调用invoke方法就相当于调用该substring(int)方法,invoke的第一个参数是实例对象(即在哪个实例对象上调用该方法),后面的实参要与方法参数的类型一致,否则将报错。
2.调用静态方法
如果获取到的
Method
表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke
方法传入的第一个参数永远为null
。我们以Integer.parseInt(String)
方法为例:
import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { // 获取 Integer.parseInt(String) 方法,参数为 String: Method m = Integer.class.getMethod("parseInt", String.class); // 调用该静态方法并获取结果: Integer n = (Integer) m.invoke(null, "12345"); // 打印调用结果: System.out.println(n);// 12345 } }
经过测试,如果是静态方法invoke的第一个参数可以为任意对象
3.调用非 public方法
和Field类对象类似,对于非 public 方法,我们虽然可以通过Class.getDeclaredMethod()获取该方法的实例对象,但直接对其调用将得到一个IllegalAccessException异常。为了调用非 public 方法,我们通过Method.setAccessible(true)允许其调用:
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);// Bob } } class Person { String name; private void setName(String name) { this.name = name; } }
- 同样,setAccessible(true)可能会失败。如果 JVM 运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证 JVM 核心库的安全
4. 多态
我们来考率这样一种情况:一个
Person
类定义了hello()
方法,并且它的子类Student
也重写了hello()
方法,那么,从Person.class
获取的Method
,作用于Student
类对象时,调用的hello()
方法到底是哪个?
import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { // 获取Person的 hello方法: Method h = Person.class.getMethod("hello"); // 对 Student实例调用 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"); } }
调用的是子类的
- 运行上述代码,发现输出的是
Student:hello
,因此,使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的重写方法(如果存在)。 上述的反射代码:
Method m = Person.class.getMethod("hello"); m.invoke(new Student());
- 实际相当于
Person p = new Student(); p.hello();
5.小结
- Java 的反射 API 提供的Method类对象封装了类定义的全部方法的所有信息:
- 通过Class类对象的方法可以获取Method类对象:getMethod(),getMethods(),getDeclaredMethod(),getDeclaredMethods();
- 通过Method类对象可以获取方法信息:getName(),getReturnType(),getParameterTypes(),getModifiers();
- 通过Method类对象可以调用某个对象的方法:Object invoke(Object instance, Object… parameters);
- 通过设置setAccessible(true)来访问非public方法;
- 通过反射调用方法时,仍然遵循多态原则。
调用构造方法
一般情况下,我们通常使用new
操作符创建新的对象:
Person p = new Person();
如果通过反射来创建新的对象,可以调用
Class
提供的newInstance()
方法:
Person p = Person.class.newInstance();
- 调用
Class.newInstance()
的局限是,它只能调用该类的public
无参构造方法。如果构造方法带有参数,或者不是public
,就无法直接通过Class.newInstance()
来调用。
为了调用任意的构造方法,Java 的反射 API 提供了Constructor类对象,它包含一个构造方法的所有信息,通过Constructor类对象可以创建一个类的实例对象。Constructor类对象和Method类对象非常相似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回一个类的实例对象:
import java.lang.reflect.Constructor; public class Main { public static void main(String[] args) throws Exception { // 获取构造方法 Integer(int),形参为 int Constructor cons1 = Integer.class.getConstructor(int.class); // 调用构造方法: // 传入的形参必须与构造方法的形参类型相匹配 Integer n1 = (Integer) cons1.newInstance(123); System.out.println(n1); // 获取构造方法Integer(String),形参为 String Constructor cons2 = Integer.class.getConstructor(String.class); Integer n2 = (Integer) cons2.newInstance("456"); System.out.println(n2); } }
通过Class实例获取Constructor的方法如下:
- getConstructor(Class…):获取某个public的Constructor 参数是构造器参数的Class对象;
- getDeclaredConstructor(Class…):获取某个Constructor;
- getConstructors():获取所有public的Constructor;
- getDeclaredConstructors():获取所有Constructor。
注意:Constructor类对象只含有当前类定义的构造方法,和父类无关,因此不存在多态的问题。
同样,调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。但setAccessible(true)也可能会失败。
小结
Constructor
类对象封装了其对应的类定义的构造方法的所有信息;
通过Class类对象可以获取Constructor类对象:getConstructor(),getConstructors(),getDeclaredConstructor(),getDeclaredConstructors();
通过``Constructor类对象可以创建一个对应类的实例对象:
newInstance(Object… parameters); 通过设置
setAccessible(true)`来访问非public
六、获取继承方法
当我们获取到某个Class类对象时,实际上就获取到了一个类的类型:
Class cls = String.class; // 获取到 String 的 Class类对象
1
还可以用类对象的getClass()方法获取:
String s = ""; Class cls = s.getClass(); // s是String,因此获取到String的Class
最后一种获取Class的方法是通过Class.forName(“”),传入Class的完整类名获取:
Class s = Class.forName("java.lang.String");
1
这三种方式获取的Class类对象都是同一个对象,因为 JVM 对每个加载的Class只创建一个Class类对象来表示它的类型。
- 获取父类的Class
有了Class类对象,我们还可以获取它的父类的Class类对象:
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类对象都必定存在一个父类类型。
- 获取interface
由于一个类可能实现一个或多个接口,通过Class我们就可以查询到实现的接口类型。例如,查询Integer实现的接口:
import java.lang.reflect.Method; 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实现的接口有:
java.lang.Comparable java.lang.constant.Constable java.lang.constant.ConstantDesc 要特别注意:getInterfaces()方法只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型: // reflection import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { Class s = Integer.class.getSuperclass(); Class[] is = s.getInterfaces(); for (Class i : is) { System.out.println(i); } } }
Integer的父类是Number,Number类实现的接口是java.io.Serializable。
此外,对所有接口interface的Class类对象调用getSuperclass()返回的是null,获取接口的父接口要用getInterfaces():
System.out.println(java.io.DataInputStream.class.getSuperclass()); // 输出 java.io.FilterInputStream。因为 DataInputStream 继承自 FilterInputStream System.out.println(java.io.Closeable.class.getSuperclass()); // 输出 null。因为对接口调用 getSuperclass()总是返回 null,获取接口的父接口要用 getInterfaces()
如果一个类没有实现任何interface,那么getInterfaces()返回空数组。
- 继承关系
当我们判断一个对象是否是某个类型时,正常情况下,使用instanceof操作符:
Object n = Integer.valueOf(123); boolean isDouble = n instanceof Double; // false boolean isInteger = n instanceof Integer; // true boolean isNumber = n instanceof Number; // true boolean isSerializable = n instanceof java.io.Serializable; // true
如果是两个Class类对象,要判断一个向上转型是否成立,可以调用isAssignableFrom()方法:
// Integer i = ? Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer // Number n = ? Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number // Object o = ? Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object // Integer i = ? Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer **小结**
通过Class对象可以获取继承关系:
Class getSuperclass():
获取父类类型;
Class[] getInterfaces()
:获取当前类实现的所有接口。
通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。