一、反射
以下引自Java反射机制是什么?
Java反射机制是 Java 语言的一个重要特性。在学习 Java 反射机制前,大家应该先了解两个概念,编译期和运行期。
编译期是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误。
运行期是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。在 Java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息
二、反射包,以及常用方法
1、反射机制的相关类所在包
java.lang.reflect.*;
2、反射机制相关的类
java.lang.Class:代表整个字节码,代表一个类型,代表整个类
java.lang.reflect.Method:代表字节码中的方法字节码(代表类中的方法)
java.lang.reflect.Constructor:代表字节码中的构造方法字节码(代表类中的构造方法)
java.lang.reflect.Field:代表字节码中的属性字节码,代表类中的成员变量(静态变量+实例变量)
2.1获取Class类的三种方式
1、Class c = Class.forName("完整带包类名"); 这个方法的执行会导致:类加载,类加载时,静态代码块执行 类的静态代码块执行,其它代码一律不执行
2、Class c = 对象.getClass(); java中任何一个对象都有一个方法:getClass()
3、Class c = 任何类型.class; java语言中任何一种类型,包括基本数据类型,它都有.class属性
/** 1、Class c = Class.forName("完整带包类名") */
Class strClass = null;
Class dateClass = null;
try {
/**
Class.forName()
1、静态方法
2、方法的参数是一个字符串。
3、字符串需要的是一个完整类名。
4、完整类名必须带有包名。java.lang包也不能省略。
*/
strClass = Class.forName("java.lang.String");// strClass代表String.class文件,strClass代表String类型
System.out.println("strClass = " + strClass);//class java.lang.String
dateClass = Class.forName("java.util.Date");
System.out.println("dateClass = " + dateClass);//class java.util.Date
Class integerClass = Class.forName("java.lang.Integer");
System.out.println("integerClass = " + integerClass);//class java.lang.Integer
Class systemClass = Class.forName("java.lang.System");
System.out.println("systemClass = " + systemClass);//class java.lang.System
/**
Class.forName("完整带包类名")
这个方法的执行会导致:类加载,类加载时,静态代码块执行
类的静态代码块执行,其它代码一律不执行
*/
Class.forName("reflect.bean.User");//User类的静态代码块执行了!
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
/** 2、Class c = 对象.getClass() */
String str = "abc";
Class strClass2 = str.getClass();
System.out.println("strClass2 = " + strClass2);//class java.lang.String
System.out.println(strClass == strClass2);// true 未重写的 == 比较内存地址;strClass和strClass2两个变量中保存的内存地址都是一样的,都指向方法区中的字节码文件
Date date = new Date();
Class dateClass2 = date.getClass();
System.out.println("dateClass2 = " + dateClass2);
System.out.println(dateClass == dateClass2);//class java.util.Date
/** 3、Class c = 任何类型.class */
Class strClass3 = String.class;
System.out.println("strClass3 = " + strClass3);//class java.lang.String
System.out.println(strClass == strClass3);// true
Class dateClass3 = date.getClass();
System.out.println("dateClass3 = " + dateClass3);//class java.util.Date
System.out.println(dateClass == dateClass3);// true
Class doubleClass = Double.class;
Class doubleClass1 = double.class;
newInstance()方法
/**
获取Class作用
通过Class的newInstance()方法来实例化对象
注:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在
*/
try {
// 通过反射机制,获取Class,通过Class来实例化对象
Class className = Class.forName("reflect.bean.User");
// newInstance()方法会调用User类的无参数构造方法,完成对象的创建(必须保证无参构造方法是存在)
Object obj = className.newInstance();//User对象无参构造
System.out.println(obj);//reflect.bean.User@1b6d3586
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
获取类的父类,以及实现的接口
try {
// 以String举例
Class stringClass = Class.forName("java.lang.String");
System.out.println("=====父类=====");
// 获取String的父类
Class superClass = stringClass.getSuperclass();
System.out.println(superClass.getName());//java.lang.Object
System.out.println("=====接口=====");
// 获取String类实现的所有接口(一个类可以实现多个接口)
Class[] interfaces = stringClass.getInterfaces();
for (Class interfaceStr:interfaces){
System.out.println(interfaceStr.getName());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
(2)获取路径
classinfo.properties文件
className=reflect.bean.User
classNames=reflect.bean.User,reflect.bean.People
#建议key和value之间使用=的方式
#=左边是key,=右边是value
#尽量不要有空格
#name = zhangsan
#尽量不使用冒号:
#usernamex:admin
#属性配置文件的key重复的话,value会自动覆盖!
#password=admin123
// 这种方式写死了类对象;只能创建一个User类型的对象
//User user = new User();
FileReader fr = null;
try {
// 通过IO流读取classinfo.properties文件
fr = new FileReader("src\\reflect\\classinfo.properties");
// 创建属性类对象Map
Properties ps = new Properties();
// 加载
ps.load(fr);
/** 创建单个对象 */
// 通过key获取value
String className = ps.getProperty("className");
// 通过反射机制实例化对象
Class cls = Class.forName(className);
Object obj = cls.newInstance();
System.out.println(obj);
/** 创建批量对象 */
String classNames = ps.getProperty("classNames");
String[] clsNames = classNames.split(",");
for (String clsName:clsNames) {
Class cls1 = Class.forName(clsName);
Object obj1 = cls1.newInstance();
System.out.println(obj1);
/*
User对象无参构造
reflect.bean.User@4554617c
People对象无参构造
reflect.bean.People@74a14482
*/
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} finally {
//快捷键,fr.notnull
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
【1】以流的形式返回文件路径
/**
以流的形式返回文件路径
Thread.currentThread() 当前线程对象
getContextClassLoader() 线程对象的方法,获取到当前线程的类加载器对象
getResourceAsStream() 类加载器对象的方法,当前线程类加载器默认从类的根路径下以流的形式加载资源
* */
public class IOProperties {
public static void main(String[] args) {
FileReader fr = null;
try {
String filePath = Thread.currentThread().getContextClassLoader()
.getResource("reflect/classinfo.properties").getPath();
fr = new FileReader(filePath);
Properties ps = new Properties();
ps.load(fr);
String clsName = (String) ps.get("className");
Class cls = Class.forName(clsName);
System.out.println(clsName);//reflect.bean.User
System.out.println(cls);//class reflect.bean.User
//以流的形式返回
InputStream is = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("reflect/classinfo.properties");
Properties ps1 = new Properties();
ps1.load(is);
String clsName1 = (String) ps.get("className");
Class cls1 = Class.forName(clsName);
System.out.println(clsName1);//reflect.bean.User
System.out.println(cls1);//class reflect.bean.User
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
//ifn 快捷键
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
【2】获取文件绝对路径
/**
获取文件绝对路径
文件需要在类路径(即src目录)下
*/
public class PropertiesPath {
public static void main(String[] args) {
// 这种获取文件路径方式缺点:移植性差,在IDEA中默认的当前路径是project的根
// 源代码不在IDEA,在其他系统,开发工具打开,当前路径就可能不是project的根,这时路径就无效
//FileReader reader = new FileReader("src/reflect/classinfo.properties");
/**
获取文件通用路径
Thread.currentThread() 当前线程对象
getContextClassLoader() 线程对象的方法,获取到当前线程的类加载器对象
getResource() 类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源
注:这个文件必须在类路径下;src(类的根路径)下的都是类路径下
找不到会报异常:java.lang.NullPointerException
*/
String filePath1 = Thread.currentThread().getContextClassLoader()
.getResource("classinfo1.properties").getPath();
///D:/Java/JavaProject/JavaSE/out/production/JavaSE/classinfo1.properties
System.out.println(filePath1);
//获取文件的绝对路径(从类的根路径src下作为起点开始)
String filePath = Thread.currentThread().getContextClassLoader()
.getResource("reflect/classinfo.properties").getPath();
///D:/Java/JavaProject/JavaSE/out/production/JavaSE/reflect/classinfo.properties
System.out.println(filePath);
}
}
【3】资源绑定器
/**
资源绑定器
java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容
注:
1、资源绑定器,只能绑定xx.properties文件
2、xx.properties文件必须在类(src目录)路径下
3、写路径的时候,路径后面的扩展名不能写
*/
public class ResourceBundleTest {
public static void main(String[] args) {
ResourceBundle rb = ResourceBundle.getBundle("reflect/classinfo");
String clsName = rb.getString("className");
Class cls = clsName.getClass();
System.out.println(clsName);
System.out.println(cls);
}
}
2.2、获取字段Field
反射字段
/** 获取类名 */
Class studentCls = Class.forName("reflect.bean.Student");
//getName() 获取完整类名
System.out.println("完整类名:" + studentCls.getName());//完整类名:reflect.bean.Student
//getSimpleName() 获取简类名
System.out.println("简类名:" + studentCls.getSimpleName());//简类名:Student
System.out.println("==================================");
/** 获取字段Field */
// getFields() 获取类中所有的public修饰的Field
Field[] fields = studentCls.getFields();
System.out.println(fields.length);//2
for (Field field : fields){
// getName() 获取Field的键,名字
System.out.println(field.getName());
//no
//MATH_PI
}
System.out.println("==================================");
Field[] fieldArray = studentCls.getDeclaredFields();
System.out.println(fieldArray.length);//5
for (Field field:fieldArray){
// 获取属性的修饰符列表
// 返回的修饰符是一个数字,每个数字是修饰符的代号
int modify = field.getModifiers();
System.out.println(modify);
// 将属性修饰符数字转换成属性字符串
String modefyStr = Modifier.toString(modify);
// 获取属性的类型
Class fieldType = field.getType();//class java.lang.String
String fieldTypeStr = fieldType.getSimpleName();//String
// 获取属性的名字
String fieldName = field.getName();
System.out.println("修饰符:" + modefyStr
+ " 字段类型:" + fieldTypeStr
+ " 字段名:" + fieldName);
/*
2
修饰符:private 字段类型:String 字段名:name
4
修饰符:protected 字段类型:int 字段名:age
0
修饰符: 字段类型:boolean 字段名:sex
1
修饰符:public 字段类型:int 字段名:no
25
修饰符:public static final 字段类型:double 字段名:MATH_PI
*/
}
反编译类的字段
StringBuilder sb = new StringBuilder();
try {
Class studetClass = Class.forName("reflect.bean.Student");
//获取类的修饰符字符串
int modifyInt = studetClass.getModifiers();
String modifyStr = Modifier.toString(modifyInt);
sb.append(modifyStr + " class " + studetClass.getSimpleName() + "{\n");
//获取所有字段
Field[] fields = studetClass.getDeclaredFields();
for (Field field:fields){
sb.append("\t");
sb.append(Modifier.toString(field.getModifiers()));
sb.append(" ");
sb.append(field.getType().getSimpleName());
sb.append(" ");
sb.append(field.getName());
sb.append(";\n");
}
sb.append("}");
System.out.println(sb);
/*
public class Student{
private String name;
protected int age;
boolean sex;
public int no;
public static final double MATH_PI;
}
*/
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
字段set、get取赋值
Class studentClass = Class.forName("reflect.bean.Student");
// obj就是Student对象(底层调用无参数构造方法)
Object obj = studentClass.newInstance();
Field noField = studentClass.getDeclaredField("no");
// 给obj对象(Student)的no属性赋值
noField.set(obj,18);
// 读取属性的值
System.out.println(noField.get(obj));
Field nameField = studentClass.getDeclaredField("name");
// 打破封装(反射机制的缺点:打破封装!)
// 设置完成后,在外部也可以访问private
nameField.setAccessible(true);
nameField.set(obj,"lisi");
//java.lang.IllegalAccessException: Class reflect.getfield.FieldGetAndSet can not access a member of class reflect.bean.Student with modifiers "private"
System.out.println(nameField.get(obj));
2.3、获取方法Method
可变长度参数
/**
可变长度参数
int... args 这就是可变长度参数
语法是:类型... (注:一定是3个点...)
1、可变长度参数要求的参数个数是:0~N个
2、可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有1个
3、可变长度参数可以当做一个数组来看待
*/
public class VariableLengthParameter {
public static void main(String[] args) {
m();
m(1);
m(1,2);
m2(100);
m2(200, "abc");
m2(200, "abc", "def");
String[] strs = {"a","b","c"};
// 也可以传1个数组
m3(strs);
// 直接传1个数组
m3(new String[]{"我","是","中","国", "人"});
m3("我","是","中","国", "人");
}
public static void m(int... args){
System.out.println("m方法执行了!");
}
/*
可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有1个
Move 'args' to the end of the list
public static void m2(int... args,String... args1){
}
*/
public static void m2(int args,String... args1){
}
public static void m3(String... args){
for (int i = 0;i < args.length;i++){
System.out.print(args[i]);
}
}
}
反射Method
try {
Class loginClass = Class.forName("reflect.getmethod.LoginService");
// 获取所有的Method(包括私有的!)
Method[] methods = loginClass.getDeclaredMethods();
for (Method method:methods){
System.out.println("==========");
//获取方法的修饰符
String methodModify = Modifier.toString(method.getModifiers());
System.out.println("方法的修饰符:" + methodModify);
//获取方法的返回值类型
Class returnType = method.getReturnType();
String methodReturnTypeStr = returnType.getSimpleName();
System.out.println("返回值类型:" + methodReturnTypeStr);
//获取方法名
String methodName = method.getName();
System.out.println("方法名:" + methodName);
//获取参数(形参)列表
Class[] parameterTypes = method.getParameterTypes();
for (Class parameter:parameterTypes){
System.out.println("参数(形参)列表:" + parameter.getSimpleName());
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
反编译类的方法
try {
StringBuilder sb = new StringBuilder();
Class loginClass = Class.forName("reflect.getmethod.LoginService");
sb.append(Modifier.toString(loginClass.getModifiers()) + " class " + loginClass.getSimpleName() + "{\n");
Method[] methods = loginClass.getDeclaredMethods();
for (Method method:methods){
sb.append("\t");
sb.append(Modifier.toString(method.getModifiers()));
sb.append(" ");
sb.append(method.getReturnType().getSimpleName());
sb.append(" ");
sb.append(method.getName());
sb.append("(");
Class[] parameterTypes = method.getParameterTypes();
for (Class parameter:parameterTypes){
sb.append(parameter.getSimpleName());
sb.append(",");
}
if (parameterTypes.length >= 1){
// 删除指定下标位置上的字符
sb.deleteCharAt(sb.length() - 1);
}
sb.append("){}\n");
}
sb.append("}");
System.out.println(sb);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
方法调用
public class LoginService {
/**
* 登录方法
* @param account 用户名
* @param password 密码
* @return true表示登录成功,false表示登录失败!
*/
public String login(String account,String password){
if ("admin".equals(account) && "123".equals(password)){
return "用户名密码正确,登陆成功!";
}
return "用户名密码错误,登陆失败!";
}
// java中区分一个方法,依靠方法名和参数列表
private void login(int cunt){
}
/**
* 退出系统的方法
*/
public void logout(){
System.out.println("系统已经安全退出!");
}
}
/**
反射机制调用方法
* */
public class InvokeMethod {
public static void main(String[] args) {
try {
Class loginService = Class.forName("reflect.getmethod.LoginService");
Object obj = loginService.newInstance();
Method method = loginService.getDeclaredMethod("login",String.class,String.class);
String message = (String) method.invoke(obj,"admin","123");
System.out.println(message);//用户名密码正确,登陆成功!
Method method1 = loginService.getDeclaredMethod("login",int.class);
method.invoke(obj,1);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
2.4 、获取构造方法Constructor
反编译类的Constructor构造方法
try {
StringBuilder sb = new StringBuilder();
Class studentClass = Class.forName("reflect.bean.Student");
sb.append(Modifier.toString(studentClass.getModifiers()));
sb.append(" class ");
sb.append(studentClass.getSimpleName());
sb.append("{\n");
//获取构造方法列表
Constructor[] constructors = studentClass.getDeclaredConstructors();
for (Constructor constructor:constructors){
sb.append("\t");
sb.append(Modifier.toString(constructor.getModifiers()));
sb.append(" ");
sb.append(studentClass.getSimpleName());
sb.append("(");
//获取参数列表
Class[] parameteTypes = constructor.getParameterTypes();
for (Class parameter:parameteTypes){
sb.append(parameter.getSimpleName());
sb.append(",");
}
if (parameteTypes.length > 1){
sb.deleteCharAt(sb.length() - 1);
}
sb.append("){}\n");
}
sb.append("}");
System.out.println(sb);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
通过反射机制调用构造方法实例化java对象
try {
Class studentClass = Class.forName("reflect.bean.Student");
Object obj = studentClass.newInstance();
//Student{name='null', age=0, sex=false, no=0}
Constructor constructor = studentClass.getConstructor();
Object obj1 = constructor.newInstance();
System.out.println(obj1);
//Student{name='zhansgan', age=18, sex=true, no=1}
Constructor constructor1 = studentClass.getConstructor(String.class,int.class,boolean.class,int.class);
Object obj2 = constructor1.newInstance("zhansgan",18,true,1);
System.out.println(obj2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
3、类加载器
(1)专门负责加载类的命令/工具;ClassLoader
(2)JDK中自带了3个类加载器
启动类加载器:rt.jar
扩展类加载器:ext/*.jar
应用类加载器:classpath
(3)代码在开始执行之前,会将所需要类全部加载到JVM当中
通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载
加载过程
首先通过“启动类加载器”加载。
注:启动类加载器专门加载:XX\Java\jdk1.8.0_101\jre\lib\rt.jar
rt.jar中都是JDK最核心的类库
如果通过“启动类加载器”加载不到的时候,会通过"扩展类加载器"加载。
注:扩展类加载器专门加载:XX\Java\jdk1.8.0_101\jre\lib\ext\*.jar
如果“扩展类加载器”没有加载到,那么会通过“应用类加载器”加载。
注:应用类加载器专门加载:classpath中的类
三、反射的优缺点:
1、优点:在运行时获得类的各种内容(如类名,构造方法,字段,方法等),进行反编译,对于Java这种先编译再运行的语言,能够很方便的创建灵活的代码
2、缺点:
(1)反射会消耗一定的系统资源(如果不需要动态创建对象,尽量不用反射)
(2)反射调用方法时可以跳过权限检查,可能会破坏封装性而导致安全问题
四、反射的用途:
1、反编译:.class---->.java
2、通过反射机制访问java对象的属性,方法,构造方法等
3、当我们在使用IDE,比如Ecplise时,当我们输入一个对象或者类,并想调用他的属性和方法是,一按点号,编译器就会自动列出他的属性或者方法,这里就是用到反射。
4、反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。
如,加载数据库驱动的,用到的也是反射
Class.forName("com.mysql.jdbc.Driver"); // 动态加载mysql驱动