一、什么是反射:
见网盘--javaAndWeb---202x年--java--java基础讲解项目--项目‘ReflextTest’
(1)Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
(2)Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
二、反射的原理:
下图是类的正常加载过程、反射原理与class对象:
Class对象的由来是将.class文件读入内存,并为之创建一个Class对象。
三、反射的优缺点:
1、优点:在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
2、缺点:(1)反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
(2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
四、反射的用途:
1、反编译:.class-->.java
2、通过反射机制访问java对象的属性,方法,构造方法等
3、当我们在使用IDE,比如Ecplise时,当我们输入一个对象或者类,并想调用他的属性和方法是,一按点号,编译器就会自动列出他的属性或者方法,这里就是用到反射。
4、反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。
例如,在使用Strut2框架的开发过程中,我们一般会在struts.xml里去配置Action,比如
<action name="login" class="org.ScZyhSoft.test.action.SimpleLoginAction" method="execute">
<result>/shop/shop-index.jsp</result>
<result name="error">login.jsp</result>
</action>
比如我们请求login.action时,那么StrutsPrepareAndExecuteFilter就会去解析struts.xml文件,从action中查找出name为login的Action,并根据class属性创建SimpleLoginAction实例,并用Invoke方法来调用execute方法,这个过程离不开反射。配置文件与Action建立了一种映射关系,当View层发出请求时,请求会被StrutsPrepareAndExecuteFilter拦截,然后StrutsPrepareAndExecuteFilter会去动态地创建Action实例。
比如,加载数据库驱动的,用到的也是反射。
Class.forName("com.mysql.jdbc.Driver"); // 动态加载mysql驱动
五、反射机制常用的类:
Java.lang.Class;
java.lang.reflect.Type;
Java.lang.reflect.Constructor;
Java.lang.reflect.Field;
Java.lang.reflect.Method;
Java.lang.reflect.Modifier;
六、反射的基本使用:
1、获得Class:主要有三种方法:
(1)Object-->getClass stu1.getClass()
(2)任何数据类型(包括基本的数据类型)都有一个“静态”的class属性,如:Student.class
(3)通过class类的静态方法:Class.forName(String className)(最常用)
Class类的主要方法有:
.getName() 返回原类的名字。
3、创建实例:通过反射来生成对象主要有两种方法:
(1)使用Class对象的newInstance()方法来创建Class对象对应类的实例。
Class<?> classStu = Student.class;
Object stu = classStu.newInstance()
(2)先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建对象,这种方法可以用指定的构造器构造类的实例。
//获取Student的Class对象
Class<?> struClass= Student.class;
//通过Class对象获取指定的Constructor构造器对象
Constructor constructor=stuClass.getConstructor(Student.class);
//根据构造器创建实例:
Object obj = constructor.newInstance(“熊少文”,2,"男");
例:Student.java fanshe.java
Student.java
public class Student {
public Sting address;
private String name;
private int age;
private String sex;
public List<String> list; //泛型类型为String ,List是一个泛型类
public Map.Entry<Integer,Long> map; //泛型类型有两个int,long
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Student() {
super();
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";
}
}
Fanshe.java 该方法有main函数,可执行看效果。
public class Fanshe {
public static void main(String[] args) {
//第一种方式获取Class对象
Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
Class stuClass = stu1.getClass();//获取Class对象
System.out.println(stuClass.getName());
//第二种方式获取Class对象
Class stuClass2 = Student.class;
System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个
//第三种方式获取Class对象
try {
Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
一般的,我们使用instanceof 关键字来判断是否为某个类的实例。同时我们也可以借助反射中Class对象的isInstance()方法来判断时候为某个类的实例,他是一个native方法。
public native boolean isInstance(Object obj);
4、通过反射获取构造方法并使用:
(1)批量获取的方法:
public Constructor[] getConstructors():所有"公有的"构造方法
public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
(2)单个获取的方法,并调用:
public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;
(3) 调用构造方法:
Constructor-->newInstance(Object... initargs)
newInstance是 Constructor类的方法(管理构造函数的类)
api的解释为:newInstance(Object... initargs) ,使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。
它的返回值是T类型,所以newInstance是创建了一个构造方法的声明类的新实例对象,并为之调用。
例子:Student2.java定义了6个公有,私有,保护的构造函数,Constructors.java中反射获取,并创建对象。
见网盘--javaAndWeb---202x年--java--java基础讲解项目--项目‘ReflextTest’
5.获取字段。
获取成员变量并调用:
- 1.批量的
1).Field[] getFields():获取所有的"公有字段"
2).Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;
- 2.获取单个的:
1).public Field getField(String fieldName):获取某个"公有的"字段;
2).public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
- 设置字段的值:
Field --> public void set(Object obj,Object value):
参数说明:
1.obj:要设置的字段所在的对象;
2.value:要为字段设置的值;
见百度网盘--javaAndWeb---202x年--java---项目‘ReflextTest’
getField.java
6.获取成员方法
/*
* 获取成员方法并调用:
*
* 1.批量的:
* public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
* public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
* 2.获取单个的:
* public Method getMethod(String name,Class<?>... parameterTypes):
* 参数:
* name : 方法名;
* Class ... : 形参的Class类型对象
* public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
*
* 调用方法:
* Method --> public Object invoke(Object obj,Object... args):
* 参数说明:
* obj : 要调用方法的对象;
* args:调用方式时所传递的实参;
):
*/
、反射方法的其他使用--通过反射运行配置文件内容:
我们利用反射和配置文件,可以使:应用程序更新时,对源码无需进行任何修改
我们只需要将新类发送给客户端,并修改配置文件即可
1.配置文件以txt文件为例子:
className = bean.Student
methodName = show
show为Student类中的一个方法
测试类:getFileData.java,该类用到了文件流FileReader类
具体代码见项目'ReflectTest'
反射方法的其他使用--通过反射越过泛型检查:
------泛型用在编译期,编译过后泛型擦除(消失掉),所以是可以通过反射越过泛型检查的
通过反射越过泛型检查
- 例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值?
public class Demo {
public static void main(String[] args) throws Exception{
ArrayList<String> strList = new ArrayList<>();
strList.add("aaa");
strList.add("bbb");
// strList.add(100);
//获取ArrayList的Class对象,反向的调用add()方法,添加数据
Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象
//获取add()方法
Method m = listClass.getMethod("add", Object.class);
//调用add()方法
m.invoke(strList, 100);
//遍历集合
for(Object obj : strList){
System.out.println(obj);
}
}
java.lang.reflect.Type与java.lang.reflect.ParameterizedType;
Type是Java反射中框架中的一个核心接口,用于获取类型信息,它的定义很简单
package java.lang.reflect;
public interface Type {
default String getTypeName() {
return this.toString();
}
}
Type则是一个非常重要的接口,我们常见的Class类就实现了Type接口,Type接口的另一个重要实现类ParameterizedType,Class类只保存了当前类的基本类型信息。而ParameterizedType则保存了泛型,外部类等额外类型信息,Type没有这个能力。
测试Type,ParameterizedType:
准备:
1.接口StudentInterface ,让Student实现它,这里只做测试用,没有定义接口方法。
package bean;
public interface StudentInterface {
}
2.Student.java
3.测试类:
3.1TypeTest.java
public class TypeTest {
public static void main(String args[]) {
Student student = new Student("熊少文",29,"男");
Class<? extends Student> clazz = student.getClass();
System.out.println("-----------------------------获取接口类型信息-----------------------------------------");
Type[] genericInterfaces = clazz.getGenericInterfaces();
for (Type genericInterface : genericInterfaces)
System.out.println("Generic Interface:" + genericInterface.getTypeName());
System.out.println("-----------------------------打印接口Type的具体类型-----------------------------------------");
for (Type genericInterface : genericInterfaces)
System.out.println("Type Class:" + genericInterface.getClass().getTypeName());
System.out.println("----------------------------- //获取父类类型信息-----------------------------------------");
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println("Generic Superclass:" + genericSuperclass.getTypeName());
//Type来获取字段基本数据类型。
Field[] fields=clazz.getDeclaredFields();
System.out.println("-----------------------获取字段的基本类型address,name,age,sex,list,map-------------");
for(Field field:fields) {
Class<?> type =field.getType();
System.out.println("Basic Type: " + type.getName());
}
//获取字段的泛型类型
System.out.println("-----------------------获取字段的获取字段的泛型类型address,name,age,sex,list,map-------------");
for (Field field : fields) {
Type genericType = field.getGenericType();
System.out.println("Generic Type: " + genericType.getTypeName());
}
//获取Type的实际类型
System.out.println("-----------------------获取字段的的实际类型address,name,age,sex,list,map------------");
for (Field field : fields) {
Type genericType = field.getGenericType();
System.out.println("Type Class: " + genericType.getClass().getName());
}
}
}
测试结果:
3.2 ParameterizedType测试例子:
例1:ParameterizedTypeTest.java ,只对泛型类的参数字段进行测试,本例为Map.Entry<Integer,Long> map
public class ParameterizedTypeTest {
public static void main(String args[]) throws Exception{
Field field = Student.class.getDeclaredField("map");//若此处为"name/address/age",则发出java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType异常。
ParameterizedType type = (ParameterizedType) field.getGenericType();
System.out.println("-----------------------获取字段的基本类型Map.Entry<Integer,Long> map-------------");
Type rawType = type.getRawType();
System.out.println("Raw Type: " + rawType.getTypeName());
System.out.println("-----------------------获取字段泛型参数的类型Map.Entry<Integer,Long> map------------");
Type[] argumentTypes = type.getActualTypeArguments();
for (Type argumentType : argumentTypes)
System.out.println("Argument Type: " + argumentType.getTypeName());
System.out.println("-----------------------如果字段类型是个内部类,则获取字段所在外部类的类型Entry是Map的内部类------------");
Type ownerType = type.getOwnerType();
System.out.println("Owner Type: " + ownerType.getTypeName());
}
}
测试结果:
-----------------------获取字段的基本类型Map.Entry<Integer,Long> map-------------
Raw Type: java.util.Map$Entry
-----------------------获取字段泛型参数的类型Map.Entry<Integer,Long> map------------
Argument Type: java.lang.Integer
Argument Type: java.lang.Long
-----------------------如果字段类型是个内部类,则获取字段所在外部类的类型Entry是Map的内部类------------
Owner Type: java.util.Map
例2:见百度网盘--javaANDweb---202x年---可运行的web项目---mvcproject---->BaseDao.java<--->UserDaoImpl----UserDaoImplTest,该功能是查询数据库表。
invoke,就是通过函数名反射调用相应的函数。
以下代码简单地介绍了java反射中invoke方法
import java.lang.reflect.Method;
public class InvokeMethods {
public static void main(String[] args) {
Employee emp = new Employee();
Class cl = emp.getClass();// 是Class,而不是class
try {
Method sAge = cl.getMethod("setAge", new Class[] { int.class });
Method gAge = cl.getMethod("getAge", null);
Method pName = cl.getMethod("printName", new Class[] { String.class });
Object[] args1 = { new Integer(25) };
// invoke方法中,第二个参数为参数列表,该参数列表是一个object[]数组
// emp为隐式参数该方法不是静态方法必须指定
sAge.invoke(emp, args1);// 通过setter方法赋值
Integer AGE = (Integer) gAge.invoke(emp, null);// 通过getter方法返回值
int age = AGE.intValue();// Integer转换成int
System.out.println("The Employee Age is: " + age);
Object[] args3 = { new String("Jack") };
pName.invoke(emp, args3);
} catch (Exception e) {
e.printStackTrace();
}
System.exit(0);
}
}
class Employee {
private int age;
private String name;
// 定义一个员工类
public Employee() {
age = 0;
name = null;
}
// 将要被调用的方法
public void setAge(int a) {
age = a;
}
// 将要被调用的方法
public int getAge() {
return age;
}
// 将要被调用的方法
public void printName(String n) {
name = n;
System.out.println("The Employee Name is: " + name);
}
}
反射常用的地方(本人)
- 获取Class对象,常用于项目的dao(mvc和hibernate)或service(mybatis)层。
public abstract class BaseServiceImpl<T> implements BaseService<T>{
public Class<?> clazz;
public BaseServiceImpl() {
Type type=this.getClass().getGenericSuperclass();
ParameterizedType pt = (ParameterizedType)type;
Type[] types = pt.getActualTypeArguments();
clazz = (Class<?>)types[0];
}
@Override
public T select(int id) {
Map<Object,Object> rsMap =getBaseDao().select(tableName, id);
//我们需要把Map转型T--Map里有很多个字段名:字段值的键值对
@SuppressWarnings("unchecked")
T t =(T) MapToEntityTool.map2entity(rsMap, clazz);
return t;
}
}
- 把mybatis查询获取的Map对象转化为实体对象。
---利用动态获取对象反射过来的字段,方法来处理
public class MapToEntityTool {
/**
* 缓存类的属性信息
*/
private static Map<String, EntityCacheItem> convertItemCache = new HashMap<>();
/**
* map to entry 的泛型方法 功能:把map对象转化为entity实体对象(entityClass对应的)
* 原理:首先获取参数entityClass的所有字段,与方法,所有字段名放在fieldNameList集合中,所有方法放在Map集合中
* 其次:获取mybatis传递过来的Map集合中的所有键值(键名与entityClass传递过来的字段名一样,不一样不做处理) 所有发生的异常,return
* null;这样不留垃圾
*
* @return
*/
public static <T> T map2entity(Map<Object, Object> map, Class<T> entityClass) {
EntityCacheItem entityCacheItem = convertItemCache.get(entityClass.getName());
if(entityCacheItem ==null) {
entityCacheItem = EntityCacheItem.createEntityCacheItem(entityClass);
convertItemCache.put(entityClass.getName(),entityCacheItem);
}
//entityClass拿到参数传来的对象的属性名秒称(List)集合
List<String> fieldNameList = entityCacheItem.getFieldNameList();
//通过entityClass参数,获取类型里边的set方法的map集合
Map<String,Method> setMethodMap=entityCacheItem.getSetMethodMap();
System.out.println("数据库中查询的结果集:"+map);
System.out.println("实体类对象中的属性名字:"+fieldNameList);
Map<Object, Object> targetMap = new HashMap<>();
String key;
String key1;
String key2;
for(Map.Entry<Object, Object> entry:map.entrySet()) {
key = entry.getKey().toString();
while(key.contains("_")) {
//add_date a_b_c_d
key1 = key.substring(0,key.indexOf("_")); //add
key2 = key.substring(key.indexOf("_")+1); //date
key = key1 + key2.substring(0,1).toUpperCase()+key2.substring(1); //addDate
}
targetMap.put(key, entry.getValue());
}
T entity = null;
try {
entity = entityClass.newInstance(); // 反射方式创建一个对象(实例entity),此时它还是空的,下面要注入内容
} catch (Exception e) {
e.printStackTrace();
return null;
} // 通过反射方式,获取这个类型的对象
Object mapFieldValue = null;
Method setMethod1 = null;
Class<?>[] parameterTypes = null;
for (String fieldName1 : fieldNameList) { // 循环拿到Map集合(调用时参数传过来的map)中的键:循环把map所有的键值注入到实体中(如:User会对应很多setXXX设置字段的方法)
mapFieldValue = targetMap.get(fieldName1);
if (mapFieldValue == null)
continue; // 如果键不存在,就没有做本次循环的必要,继续进行一下次循环
setMethod1 = setMethodMap.get(fieldName1); // 如果方法不存在,也没有。。。。
if (setMethod1 == null)
continue;
parameterTypes = setMethod1.getParameterTypes(); // 获取方法中的所有类型的参数数组
if (parameterTypes == null || parameterTypes.length > 1) { // 如果setxxx方法中参数没有或大于1个时也不做本次循环,因为setUsername(xx)中只有一个参数
continue;
}
if (parameterTypes[0].isAssignableFrom(mapFieldValue.getClass())) {
// 若map传来的属性值的类型和set方法中参数的类型一致
// 如:setUsername(Object)的object,与mapFieldValue的类型是否一致
try {
setMethod1.invoke(entity, mapFieldValue);// 调用是对象的set方法把属性值注入到实体里(entity--不是键值对的)
// 如:setUsername("xiongshaowen");
} catch (Exception e) {
e.printStackTrace();
return null;
}
} else {
//这里输出让我们看到,封装时封装的数据类型,基本数据类型为它的包装类,特别是模型类中int id;要定为Integer id;
System.out.println(
"不同类型:set方法中的参数类型:" + parameterTypes[0] + "======数据库中查询的结果集中数据类型:" + mapFieldValue.getClass());
}
}
return entity;
}
// 定义一个缓存(静态存储)内部类,实例化后有把传来的类封装字段和方法到List,Map集合中的功能。把map转换实例时,会非常频繁的造访下面代码,这样我们定义一个缓存减少系统消耗
static class EntityCacheItem {
private EntityCacheItem() {
}; // 私有化构造(无参)方法,让该类不可在外部实例化,要通过内部方法来创建实例对象
private List<String> fieldNameList = new ArrayList<String>();
private Map<String, Method> setMethodMap = new HashMap<>();
public List<String> getFieldNameList() {
return fieldNameList;
}
public Map<String, Method> getSetMethodMap() {
return setMethodMap;
}
public void parseEntity(Class<?> entityClass) {
Field[] fields = entityClass.getDeclaredFields(); // 获取所有字段,不管私有还是公有
String fieldName;
String setMethodName;
Method setMethod = null;
setMethodMap = new HashMap<>();
for (Field field : fields) {
field.setAccessible(true); // 获取可修改字段的权限
fieldName = field.getName(); // 属性字段的名字
fieldNameList.add(fieldName);
setMethodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); // 设置类似javabean方法,如:setUsername(Username)
try {
setMethod = entityClass.getDeclaredMethod(setMethodName, field.getType()); // 拿到实体对应的类中的setUsername(Object
// param)方法
} catch (Exception e) {
e.printStackTrace();
}
setMethodMap.put(fieldName, setMethod); // 如:Map集合中的一个元素{username,setUsername(Object param)}
}
}
// 通过内部创建该类实例对象,
public static EntityCacheItem createEntityCacheItem(Class<?> entityClass) {
EntityCacheItem ci = new EntityCacheItem();
ci.parseEntity(entityClass);
return ci;
}
}
}