Java-反射学习笔记
一、反射的概述
1.1 什么是反射
反射是Java的特征之一,是一种间接操作目标对象的机制,核心是JVM在运行的时候才动态加载类,并且对于任意一个类,都能够知道这个类的所有属性和方法,调用方法/访问属性,不需要提前在编译期知道运行的对象是谁,他允许运行中的Java程序获取类的信息,并且可以操作类或对象内部属性。程序中对象的类型一般都是在编译期就确定下来的,而当我们的程序在运行时,可能需要动态的加载一些类,这些类因为之前用不到,所以没有加载到jvm,这时,使用Java反射机制可以在运行期动态的创建对象并调用其属性,它是在运行时根据需要才加载。
2.2 反射的原理
Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
2.3 反射的优缺点
1、优点:使用反射,我们就可以在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
2、(1)反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;(2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
2.4 反射的使用场景与用途
在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法。当然,也不是所有的都适合反射,之前就遇到一个案例,通过反射得到的结果与预期不符。阅读源码发现,经过层层调用后在最终返回结果的地方对应用的权限进行了校验,对于没有权限的应用返回值是没有意义的缺省值,否则返回实际值起到保护用户的隐私目的。
java的反射机制主要提供以下几种用途:
-
反编译:.class–>.java
-
通过反射机制访问java对象的属性,方法,构造方法等
-
当我们在使用IDE,比如Ecplise时,当我们输入一个对象或者类,并想调用他的属性和方法是,一按点号,编译器就会自动列出他的属性或者方法,这里就是用到反射。
-
反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。
比如,加载数据库驱动的,用到的也是反射。
Class.forName("com.mysql.jdbc.Driver"); // 动态加载mysql驱动
二、Java反射API
Java反射API由Java反射核心API和辅助Java反射API的组成,核心API位于java.lang包,是正在运行的Java应用程序中的类与接口的Class类;辅助Java反射的API的API位于java.lang.reflect包,其常用类包含:
Java.lang.Class;
Java.lang.reflect.Constructor;
Java.lang.reflect.Field;
Java.lang.reflect.Method;
Java.lang.reflect.Modifier;
三、反射的基本使用
3.1 反射核心类-Class类
获取Class类的实例有三种方法,分别为:
1、Object–>getClass
2、任何数据类型(包括基本的数据类型)都有一个“静态”的class属性
3、通过class类的静态方法:forName(String className)(最常用)
public class TestDemo {
public static void main(String[] args) {
// 方法一 可以通过该实例变量提供的getClass()方法获取
Student stu1 = new Student();
Class stuClass = stu1.getClass();
System.out.println(stuClass.getName());
// 方法二:直接通过一个class的静态变量class获取
Class stuClass2 = Student.class;
System.out.println("stuClass==stuClass2:" + (stuClass == stuClass2 ));
// 方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取
try {
Class stuClass3 = Class.forName("org.xiaoyi.lesson16.test.Student");
System.out.println(stuClass== stuClass3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果如下:
注意,在运行期间,一个类,只有一个Class对象产生,所以打印结果都是true;
三种方式中,常用第三种,第一种对象都有了还要反射干什么,第二种需要导入类包,依赖太强,不导包就抛编译错误。一般都使用第三种,一个字符串可以传入也可以写在配置文件中等多种方法。
通过上面三种方法的任意一种方法获取Class对象,然后根据对象获取类中的一些信息,
方法 | 用途 |
---|---|
asSubclass(Class clazz) | 把传递的类的对象转换成代表其子类的对象 |
Cast | 把对象转换成代表类或是接口的对象 |
getClassLoader() | 获得类的加载器 |
getClasses() | 返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 |
forName(String className) | 根据类名返回类的对象 |
getName() | 获得类的完整路径名字 |
newInstance() | 创建类的实例 |
getPackage() | 获得类的包 |
getSimpleName() | 获得类的名字 |
getSuperclass() | 获得当前类继承的父类的名字 |
getInterfaces() | 获得当前类实现的类或是接口 |
参考代码如下:
public class TestDemo02 {
public static void main(String[] args) {
Class stuClass = Student.class;
// 获得类的名称(全名)
System.out.println("类的名称:" + stuClass.getName());
// 获得类的简单名称
System.out.println("类的简单名称:" + stuClass.getSimpleName());
// 获得类的包名
if (stuClass.getPackage() != null) {
System.out.println("类的包名称:" + stuClass.getPackage().getName());
}
// 获得类的修饰符
System.out.println("类的修饰符:" + stuClass.getModifiers());
// 判断是否是接口
System.out.println("是否为接口:" + stuClass.isInterface());
// 判断是否为数组
System.out.println("是否为数组:" + stuClass.isArray());
try {
// 使用Class构建对象
Student stu = (Student) stuClass.newInstance(); // 相当于执行new
// Student()它的局限是:只能调用public的无参数构造方法。带参数的构造方法,或者非public的构造方法都无法通过Class.newInstance()被调用。
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-URZ2Z60v-1571383440736)(F:/Java/023_反射/day16-反射(补充)]/images/1571324337890.png)
3.2 反射辅助类-Field类
Field代表类的成员变量(成员变量也称为类的属性),我们先看看如何通过Class
实例获取字段信息。Class
类提供了以下几个方法来获取字段:
-
Field getField(name):根据字段名获取某个public的field(包括父类)
-
Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
-
Field[] getFields():获取所有public的field(包括父类)
-
Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
其具体用法如下代码:
public class TestDemo03 {
public static void main(String[] args) throws Exception {
Class empClass = Emp.class;
// 获得public字段 empno
Field f = empClass.getField("empno");
System.out.println(f);
System.out.println(f.getName()); // 获得字段名称
System.out.println(f.getType()); // 获得字段类型
System.out.println(f.getModifiers()); // 获得字段访问修饰符
System.out.println(Modifier.isFinal(f.getModifiers())); // 字段是否为final
System.out.println(Modifier.isPrivate(f.getModifiers())); // 字段是否private
// 获得继承public字段 name
System.out.println(empClass.getField("name"));
// 获得private 字段 job
System.out.println(empClass.getDeclaredField("job"));
// 获得字段的值
Object obj = new Emp("xiaoxiao");
Field jobField = empClass.getField("name");
String value =(String) jobField.get(obj);
System.out.println(value);
// 设置字段的值
Field nameField = empClass.getDeclaredField("job");
nameField.setAccessible(true);
nameField.set(obj, "java");
Emp emp = (Emp) obj;
System.out.println("emp-job:" + emp.getJob());
}
}
class Person{
public String name;
public Person(String name) {
this.name = name;
}
}
class Emp extends Person{
public Emp(String name) {
super(name);
}
// 公有字段
public Integer empno;
// 私有字段
private String job;
public String getJob() {
return this.job;
}
}
其运行结果如下:
3.3 反射辅助类-Method类
我们已经能通过Class
实例获取所有Field
对象,同样的,可以通过Class
实例获取所有Method
信息。Class
类提供了以下几个方法来获取Method
:
-
Method getMethod(name, Class…):获取某个public的Method(包括父类)
-
Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
-
Method[] getMethods():获取所有public的Method(包括父类)
-
Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
其具体使用如下代码:
public class TestDemo03 {
public static void main(String[] args) throws Exception {
//test4();
test5();
}
// 基本Method的用法
public static void test1() throws Exception {
Class stdClass = Student.class;
// 获取public方法getScore,参数为String:
Method m = stdClass.getMethod("getScore", String.class);
System.out.println(m);
System.out.println("获得方法名称:" + m.getName());
System.out.println("获得方法的返回类型:" + m.getReturnType());
System.out.println("获得参数类型:" + m.getParameterTypes());
System.out.println("获得方法访问修饰符:" + m.getModifiers());
// 获取继承的public方法getName,无参数:
System.out.println(stdClass.getMethod("getName"));
// 获取private方法getGrade,参数为int:
System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
}
// 方法的调用
public static void test2() 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);
}
// 调用静态方法
public static void test3() 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);
}
// 调用非public方法
public static void test4() throws Exception {
Person p = new Person();
Class c = p.getClass();
Method m = c.getDeclaredMethod("setName", String.class);
m.setAccessible(true);
m.invoke(p, "xiaoyi");
System.out.println(p.getName());
}
// 多态的方法
public static void test5() throws Exception{
Method m = Person.class.getMethod("sayHello");
m.invoke(new Student());
}
}
class Person {
private String name;
public String getName() {
return this.name;
}
private void setName(String name) {
this.name = name;
}
public void sayHello() {
System.out.println("person sayHello");
}
}
class Student extends Person {
public int getScore(String type) {
return 99;
}
private int getGrade(int year) {
return 1;
}
public void sayHello() {
System.out.println("student sayHello");
}
}
3.4 反射辅助类-Constructor类
调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例:
public class TestDemo04 {
public static void main(String[] args) throws Exception {
// 获取构造方法Integer(int):
Constructor cons1 = Integer.class.getConstructor(int.class);
// 调用构造方法:
Integer n1 = (Integer) cons1.newInstance(123);
System.out.println(n1);
// 获取构造方法Integer(String)
Constructor cons2 = Integer.class.getConstructor(String.class);
Integer n2 = (Integer) cons2.newInstance("456");
System.out.println(n2);
}
}
其运行结果如下:
四、动态代理
有没有可能不编写实现类,直接在运行期创建某个interface的实例呢?
这是可能的,因为Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。
什么叫运行期动态创建?听起来好像很复杂。所谓动态代理,是和静态相对应的,代码如下:
public class TestDemo05 {
public static void main(String[] args) {
// 实例化invacationHandler实例
MyInvocationHandler handler = new MyInvocationHandler(new HelloImpl());
// 生产代理
Hello proxyHello = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(), new Class[] {Hello.class}, handler);
// 通过代理执行方法
proxyHello.sayHello("zhangsan");
}
}
class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if(method.getName().equals("sayHello")) {
// 调用sayHello方法
System.out.println(args[0]);
// 通过反射执行代理对象的方法
method.invoke(target, args);
}
return null;
}
}
执行结果: