Java之反射机制
一、反射的定义
1、Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
2、Java反射是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时通过Java Reflection API取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。
3、Java反射机制容许程序在运行时加载、探知、使用编译期间完全未知的classes。换言之,Java可以加载一个运行时才得知名称的class,获得其完整结构。
二、反射的原理
下图是类的正常加载过程、反射原理与class对象:
Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
三、JDK中提供的Java Reflection API简介
Java反射相关的API在包java.lang.reflect中,JDK 1.6.0的reflect包如下图
类摘要 | |
---|---|
AccessibleObject | AccessibleObject 类是 Field、Method 和 Constructor 对象的基类,它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力 。 |
Array | Array 类提供了动态创建和访问 Java 数组的方法。 |
Constructor | Constructor 提供关于类的单个构造方法的信息以及对它的访问权限。 |
Field | 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。 |
Method | Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。 |
Modifier | Modifier 类提供了 static 方法和常量,对类和成员访问修饰符进行解码。 |
Proxy | Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。 |
ReflectPermission | 反射操作的 Permission 类。 |
接口摘要 | |
---|---|
AnnotatedElement | 表示目前正在此 VM 中运行的程序的一个已注释元素。 |
GenericArrayType | GenericArrayType 表示一种数组类型,其组件类型为参数化类型或类型变量。 |
GenericDeclaration | 声明类型变量的所有实体的公共接口。 |
InvocationHandler | InvocationHandler 是代理实例的调用处理程序 实现的接口。 |
Member | 成员是一种接口,反映有关单个成员(字段或方法)或构造方法的标识信息。 |
ParameterizedType | ParameterizedType 表示参数化类型,如 Collection。 |
Type | Type 是 Java 编程语言中所有类型的公共高级接口。 |
TypeVariable | TypeVariable 是各种类型变量的公共高级接口。 |
WildcardType | WildcardType 表示一个通配符类型表达式,如 ?、? extends Number 或 ? super Integer。 |
四、关于JDK中自带的类加载器:(聊一聊,不需要掌握,知道当然最好!)
1、什么是类加载器?
专门负责加载类的命令/工具。
ClassLoader
2、JDK中自带了3个类加载器
(1)启动类加载器:rt.jar
(2)扩展类加载器:ext/*.jar
(3)应用类加载器:classpath
3、假设有这样一段代码:
String s = “abc”;
代码在开始执行之前,会将所需要类全部加载到JVM当中。
通过类加载器加载,看到以上代码类加载器会找String.class
文件,找到就加载,那么是怎么进行加载的呢?
(1)首先通过“启动类加载器”加载。
注意:启动类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar
rt.jar中都是JDK最核心的类库。
(2)如果通过“启动类加载器”加载不到的时候,会通过"扩展类加载器"加载。
注意:扩展类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext*.jar
(3)如果“扩展类加载器”没有加载到,那么会通过“应用类加载器”加载。
注意:应用类加载器专门加载:classpath中的类。
4、java中为了保证类加载的安全,使用了双亲委派机制。优先从启动类加载器中加载,这个称为“父”。“父”无法加载到,再从扩展类加载器中加载,这个称为“母”。双亲委派。如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。
四、反射机制常用的类
Java.lang.Class;
Java.lang.reflect.Constructor;
Java.lang.reflect.Field;
Java.lang.reflect.Method;
Java.lang.reflect.Modifier;
五、反射的基本使用
1、反射机制获取类的三种方法
(1)通过java.lang包下Class类的静态方法:forName(String className)(最常用)
(2)java中任何一个对象都有一个方法:getClass()
(3)任何数据类型(包括基本数据类型和引用数据类型)都有一个“静态”的class属性
import java.util.Date;
public class ReflectTest01 {
public static void main(String[] args) {
//第一种方式获取Class对象,Class.forName("完整类名必须带包名")
Class c1=null;
Class c2=null;
try {
c1=Class.forName("java.lang.String");
c2=Class.forName("java.util.Date");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//第二种方式获取Class对象
String s="abc";
Class x1=s.getClass();
System.out.println(x1==c1);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个Class对象
Date time=new Date();//这一new 产生一个Date对象,一个Class对象
Class x2=time.getClass();
System.out.println(x2==c2);
//第三种方式获取Class对象
Class z1=String.class;
Class z2=Date.class;
System.out.println(z1==x1);//判断三种方式是否获取的是同一个Class对象
System.out.println(z2==x2);
}
}
结果:在运行期间,一个类,只有一个Class对象产生,所以打印结果都是true。
注意:如果你只想让一个类的“静态代码块”执行的话,你可以怎么做?
Class.forName("该类的类名");
这样类就加载,类加载的时候,静态代码块执行!!!!
在这里,对该方法的返回值不感兴趣,主要是为了使用“类加载”这个动作。
2、创建对象,通过反射来生成对象主要有两种方法:
(1)获取到Class后,使用Class对象的newInstance()方法来创建对象
Class c=Class.forName("com.java.bean.User");
//newInstance()这个方法调用的是User这个类的无参构造方法,完成对象的创建
//这个方法的调用必须保证无参构造是存在的
Object obj=c.newInstance();
System.out.println(obj);
(2)先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建对象,这种方法可以用指定的构造器创建类的实例。
//1、获取Vip的Class对象
Class vipClass=Class.forName("com.java.service.Vip");
//2、通过Class对象获取指定的Constructor构造器对象
//(1)获取无参数构造方法
Constructor con=vipClass.getDeclaredConstructor();
Object obj=con.newInstance();
System.out.println(obj);
//(2)调用有参数构造方法
Constructor con2=vipClass.getDeclaredConstructor(int.class,String.class,String.class);
Object newObj=con.newInstance(110,"Tom","2020-1-1");
System.out.println(newObj);
通过读取属性类属性来创建对象
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class ReflectTest03 {
public static void main(String[] args) {
try {
FileReader reader=new FileReader("reflect/classinfo.properties");
//创建属性类对象Map
Properties pro=new Properties();
//加载
pro.load(reader);
//关闭流
reader.close();
//通过key获取value
String className=pro.getProperty("className");
//System.out.println(className);
//通过反射机制实例化对象
Class c=Class.forName(className);
Object obj=c.newInstance();
System.out.println(obj);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
资源绑定器
IO + Properties,怎么快速绑定属性资源文件?
只能绑定xxx.properties文件,并且这个文件必须在类路径下,文件扩展名也必须为properties,并且在写路径时,路径后面的扩展名不能写。
ResourceBundle bundle=ResourceBundle.getBundle("classinfo");
String className=bundle.getString("className");
System.out.println(className);
获取一个文件的绝对路径(前提:文件需在类路径下)
String path = Thread.currentThread().getContextClassLoader()
.getResource("写相对路径,但是这个相对路径从src出发开始找").getPath();
String path = Thread.currentThread().getContextClassLoader()
.getResource("abc").getPath(); //必须保证src下有abc文件。
String path = Thread.currentThread().getContextClassLoader()
.getResource("a/db").getPath(); //必须保证src下有a目录,a目录下有db文件。
String path = Thread.currentThread().getContextClassLoader()
.getResource("com/bjpowernode/test.properties").getPath();
//必须保证src下有com目录,com目录下有bjpowernode目录。
//bjpowernode目录下有test.properties文件。
//Thread.currentThread() 当前线程对象
//getContextClassloader() 是线程对象的方法,可以获取当前线程的类加载器对象
//getResource() 这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源
这种方式是为了获取一个文件的绝对路径。(通用方式,不会受到环境移植的影响。)
但是该文件要求放在类路径下,换句话说:也就是放到src下面。
src下是类的根路径。
直接以流的形式返回
InputStream in=Thread.currentThread().getContextClassloader().getResourceAsStream("src下的路径");
3、获取类的Fields
可以通过反射机制获到某个类的某个属性,然后改变对应于这个类的某个实例的属性值。Java 的Class类提供了几个方法获取类的属性。
Field | getField(String name) 返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。 |
Field[] | getFields() 返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。 |
Field | getDeclaredField(String name) 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。 |
Field[] | getDeclaredFields() 返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。 |
int | getModifiers() 返回此类或接口以整数编码的 Java 语言修饰符。 |
String | getName() 以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。 |
String | getSimpleName() 返回源代码中给出的底层类的简称。 |
package com.java.bean;
//反射属性Field
public class Student {
//Field翻译为字段,其实就是属性/成员
public int no;
private String name;
protected int age;
boolean sex;
}
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
/*
反射Student类中的所有的Field
*/
public class ReflectTest05 {
public static void main(String[] args) {
try {
//获取整个类
Class studentClass=Class.forName("com.java.bean.Student");
//获取所有的Field
Field[] fs=studentClass.getDeclaredFields();
//遍历
for(Field field:fs){
//获取属性的修饰符列表
String modifierString= Modifier.toString(field.getModifiers());
System.out.println(modifierString);
//获取属性类型
Class fieldType=field.getType();
String fName=fieldType.getSimpleName();
System.out.println(fName);
//获取属性名字
System.out.println(field.getName());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
import java.lang.reflect.Field;
public class ReflectTest06 {
public static void main(String[] args) {
//使用反射机制,访问一个对象的属性
try {
Class studentClass=Class.forName("com.java.bean.Student");
Object obj=studentClass.newInstance();
//获取no属性
Field noField=studentClass.getDeclaredField("no");
noField.set(obj,111);
System.out.println(noField.get(obj));
//可以访问私有的属性吗? 可以
Field nameField=studentClass.getDeclaredField("name");
//打破封装(打破封装,可能会给不法分子机会)
//这样设置完之后,在外部也是可以访问private
nameField.setAccessible(true);
nameField.set(obj,"zhangsan");
System.out.println(nameField.get(obj));
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
//完成任意一个类属性的反编译
public class ReflectTest07 {
public static void main(String[] args) {
try {
//创建这个是为了拼接字符串
StringBuilder s=new StringBuilder();
Class dateClass=Class.forName("java.util.Date");
s.append(Modifier.toString(dateClass.getModifiers())+" class "+dateClass.getSimpleName()+" {\n");
Field[] fields=dateClass.getDeclaredFields();
for(Field field:fields){
s.append("\t");
s.append(Modifier.toString(field.getModifiers()));
s.append(" ");
s.append(field.getType().getSimpleName());
s.append(" ");
s.append(field.getName());
s.append(";\n");
}
s.append("}");
System.out.println(s);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
4、获取类的Method
通过反射机制得到某个类的某个方法,然后调用对应于这个类的某个实例的方法
package com.java.service;
public class UserService{
/**
* 登录方法
* @param name 用户名
* @param password 密码
* @return true表示成功,false表示失败!
*/
public boolean login(String name,String password){
if("admin".equals(name)&&"123".equals(password)){
return true;
}
return false;
}
/**
* 退出系统的方法
*/
public void logout(){
System.out.println("系统已经安全退出!");
}
}
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class ReflectTest08 {
public static void main(String[] args) {
try {
//获取类
Class userServiceClass=Class.forName("com.java.service.UserService");
//获取所有的Method(包括私有的!)
Method[] methods=userServiceClass.getDeclaredMethods();
//System.out.println(methods.length);
//遍历Method
for(Method method:methods){
//获取修饰符列表
System.out.println(Modifier.toString(method.getModifiers()));
//获取方法的返回值类型
System.out.println(method.getReturnType().getSimpleName());
//获取方法名
System.out.println(method.getName());
//参数列表
Class[] parameterTypes=method.getParameterTypes();
for(Class parameterType:parameterTypes){
System.out.println(parameterType.getSimpleName());
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class ReflectTest09 {
public static void main(String[] args) {
try {
StringBuilder s=new StringBuilder();
Class userServiceClass= Class.forName("com.java.service.UserService");
s.append(Modifier.toString(userServiceClass.getModifiers())+" class "+userServiceClass.getSimpleName()+"{\n");
Method[] methods=userServiceClass.getDeclaredMethods();
for(Method method:methods){
s.append("\t");
s.append(Modifier.toString(method.getModifiers()));
s.append(" ");
s.append(method.getReturnType().getSimpleName());
s.append(" ");
s.append(method.getName());
s.append("(");
//参数列表
Class[] parameterTypes=method.getParameterTypes();
for(Class parameterType:parameterTypes){
s.append(parameterType.getSimpleName());
s.append(",");
}
if(parameterTypes.length>0){
s.deleteCharAt(s.length()-1);
}
s.append("){}\n");
}
s.append("}");
System.out.println(s);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectTest10 {
public static void main(String[] args) {
try {
Class userServiceClass=Class.forName("com.java.service.UserService");
//创建对象
Object obj=userServiceClass.newInstance();
//获取方法
Method logoutMethod=userServiceClass.getDeclaredMethod("logout");
Method loginMethod=userServiceClass.getDeclaredMethod("login",String.class,String.class);
//调用方法
Object retValue=loginMethod.invoke(obj,"admin","123");
System.out.println(retValue);
logoutMethod.invoke(obj);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
5、获取类的Constructor
通过反射机制得到某个类的构造器,然后调用该构造器创建该类的一个实例
package com.java.service;
public class Vip {
int no;
String name;
String birth;
char sex;
public Vip() {
}
public Vip(int no, String name) {
this.no = no;
this.name = name;
}
public Vip(int no, String name, String birth) {
this.no = no;
this.name = name;
this.birth = birth;
}
public Vip(int no, String name, String birth, char sex) {
this.no = no;
this.name = name;
this.birth = birth;
this.sex = sex;
}
@Override
public String toString() {
return "Vip{" +
"no=" + no +
", name='" + name + '\'' +
", birth='" + birth + '\'' +
", sex=" + sex +
'}';
}
}
import java.lang.reflect.Constructor;
//使用反射机制调用构造方法
public class ReflectTest11 {
public static void main(String[] args) throws Exception {
//使用反射机制创建对象
Class vipClass=Class.forName("com.java.service.Vip");
//调用无参数构造方法
Object obj=vipClass.newInstance();
System.out.println(obj);
//调用有参数构造方法
//第一步:先获取这个有参数构造方法
Constructor con=vipClass.getDeclaredConstructor(int.class,String.class,String.class,char.class);
//第二步:调用构造方法new对象
Object newObj=con.newInstance(110,"Tom","2020-1-1",'男');
System.out.println(newObj);
//获取无参数构造方法
Constructor con2=vipClass.getDeclaredConstructor();
Object obj2=con2.newInstance();
System.out.println(obj2);
}
}
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
public class ReflectTest12 {
public static void main(String[] args) throws Exception {
StringBuilder s=new StringBuilder();
Class vipClass=Class.forName("com.java.service.Vip");
s.append(Modifier.toString(vipClass.getModifiers()));
s.append(" class ");
s.append(vipClass.getSimpleName());
s.append("{\n");
//拼接构造方法
Constructor[] constructors=vipClass.getDeclaredConstructors();
for(Constructor constructor:constructors){
//public Vip(int no, String name, String birth, boolean sex) {
s.append("\t");
s.append(Modifier.toString(constructor.getModifiers()));
s.append(" ");
s.append(vipClass.getSimpleName());
s.append("(");
//拼接参数
Class[] parameterTypes=constructor.getParameterTypes();
for(Class parameterType:parameterTypes){
s.append(parameterType.getSimpleName());
s.append(",");
}
//删除最后下标上的字符
if(parameterTypes.length>0){
s.deleteCharAt(s.length()-1);
}
s.append("){}\n");
}
s.append("}");
System.out.println(s);
}
}
public class ReflectTest13 {
public static void main(String[] args) throws Exception {
Class stringClass=Class.forName("java.lang.String");
//获取String的父类
Class superClass=stringClass.getSuperclass();
System.out.println(superClass.getName());
//获取String类所有实现的接口
Class[] interfaces=stringClass.getInterfaces();
for(Class in:interfaces){
System.out.println(in.getName());
}
}
}
六、反射的优缺点:
1、优点:在运行时获得类的各种内容,再进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便地创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
2、缺点:(1)反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
(2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。