这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战
反射
反射使Java代码能够发现有关已加载类的字段、方法和构造函数的信息,并在安全限制范围内使用反射的字段、方法和构造函数对其底层副本进行操作。
每个类都有一个class对象,包含了与类有关的信息。当编译一个新类是,会产生一个同名的.class文件,该文件内保存着Class对象。
类加载相当于Classs对象的加载,类在第一次使用时才能动态加载到JVM中。
文件名 | 说 明 |
---|---|
getFields() | 获得类的public类型的属性。 |
getDeclaredFields() | 获得类的所有属性 |
getField(String name) | 获得类的指定属性 |
getMethods() | 获得类的public类型的方法 |
getMethod (String name,Class [] args) | 获得类的指定方法 |
getConstrutors() | 获得类的public类型的构造方法 |
getConstrutor(Class[] args) | 获得类的特定构造方法 |
newInstance() | 通过类的无参构造方法创建对象 |
getName() | 获得类的完整名字 |
getPackage() | 获取此类所属的包 |
getSuperclass() | 获得此类的父类对应的Class对象 |
反射的作用
在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中
Class类:字节码对象类,每个类的字节码就是该类的一个对象
Constructor 类:构造法方法类,任何一个构造方法就是该类的一个对象
Field 类:属性类,任何一个属性都是该类的一个对象
Method类:方法类,任何一个方法都是该类的一个对象
Class
Class类:字节码对象类,每个类的字节码就是该类的一个对象
使用反射 获取类的类对象
public class testGetClass {
public static void main(String[] args) throws ClassNotFoundException {
/*
* 通过类的全路径名获取
* 一般结合配置文件 将类的名字放入配置文件中
* */
Class clazz =Class.forName("com.test.demo1.Person");
/*通过类名获取
* 一般明确类名的时候使用
* */
Class clazz2= Person.class;
/*通过对象获取一个类的字节码
* 一般是在多态的时候使用
* 当子类对象指向父类引用,通过对象获取的Class字节码对象
* */
Person person= new Person();
Class clazz3=person.getClass();
System.out.println(clazz==clazz2&&clazz3==clazz2);
//获取实现接口
System.out.println(Arrays.toString(clazz.getInterfaces()));
//获取父类字节码
Class clazz4=clazz3.getSuperclass();
System.out.println(clazz4);
//获取修饰符
System.out.println(clazz.getModifiers());
System.out.println(Modifier.toString(clazz.getModifiers()));
//获取基本数据类型 Class对象
Class clazz2 = int.class;
//获取数组 Class对象
int[] arr = new int[10];
Class clazz3 =arr.getClass();
Class clazz4 = int[].class;
Integer i =10;
System.out.println(i.getClass());
System.out.println(Class.forName("java.lang.Integer"));
System.out.println(Integer.class);
//包装类.TYPE 获取的是基本数据类型的字节码
System.out.println(Integer.TYPE);//Integer包封装的int的类的对象
}
}
Constructor(构造方法)
Constructor 类:构造法方法类,任何一个构造方法就是该类的一个对象
获取一个类的构造方法
public class TestGetConstructor {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class clazz = Class.forName("com.test.demo1.Person");
//通过参数列表获取指定的构造方法
Constructor cons = clazz.getDeclaredConstructor(int.class, String.class, String.class);
//获取构造方法参数个数
System.out.println(cons.getParameterCount());
//获取构造方法参数类型
Class[] parameterTypes = cons.getParameterTypes();
System.out.println(Arrays.toString(parameterTypes));
//获取一个类中的全部构造方法
Constructor[] constructors = clazz.getDeclaredConstructors();
for (Constructor c:constructors){
System.out.print("构造方法名:"+c.getName()+ "构造方法参数个数:"+c.getParameterCount()
+" 构造方法参数类型:"+Arrays.toString(c.getParameterTypes()));
System.out.println();
}
System.out.println(Arrays.toString(constructors));
}
}
使用反射创建对象
public class TestNewInstance {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取一个类的类对象
Class clazz = Class.forName("com.test.demo1.Person");
//获取类的构造方法
Constructor cons = clazz.getDeclaredConstructor(int.class, String.class, String.class);
//使用反射创建对象1
Person person = (Person)cons.newInstance(1,"小白","男");
System.out.println(person);
//使用反射创建对象2,必须保证类中有无参构造方法
Person person2 = (Person)clazz.newInstance();
}
}
Field(属性)
Field 类:属性类,任何一个属性都是该类的一个对象
使用反射获取属性(Field)
1.功能强大
编译的时候不知道类的具体信息,可以使用反射根据运行时获取路径信息来操作
可以突破封装性的限制,即使是**private**的也可以操作
缺点
1.代码可读性操,不好理解
2.执行速度慢
3突破了封装的限制(不遵守规范)
public class testGetField {
public static void main(String[] args) throws NoSuchFieldException {
//获得一个类的类对象
Class clazz = Person.class;
//获得public属性 getField 只能获取public修饰属性
Field field = clazz.getField("lastName");
//获得属性 无论是什么访问修饰符修饰的属性
Field field2 = clazz.getDeclaredField("firstName");
//查看属性名称
System.out.println(field.getName());
System.out.println(field2.getName());
//获得属性的数据类型 属性的数据类型字节码
System.out.println(field.getType());
System.out.println(field2.getType());
//获得全部属性
Field [] fields = clazz.getDeclaredFields();
System.out.println(fields.length);
for(Field f:fields){
System.out.println(f.getName()+" "+f.getType());
}
}
}
使用反射给对象属性赋值
public class TestFieldSet {
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException, NoSuchMethodException, NoSuchFieldException {
//获取一个类的类对象
Class clazz = Class.forName("com.test.demo1.Person");
//使用反射创建对象,必须保证类中有无参构造方法
Person person = (Person)clazz.newInstance();
/**
* 通过Field 对象赋值
* */
Field id =clazz.getDeclaredField("id");
Field firstName =clazz.getDeclaredField("firstName");
Field sex =clazz.getDeclaredField("sex");
/*private修饰的属性不能直接访问 如果要使用先设置属性可以访问
* 使用反射操作私有属性 突破封装性的限制,即使private、默认 的也可以访问
* */
id.setAccessible(true);
firstName.setAccessible(true);
sex.setAccessible(true);
id.set(person,10);
firstName.set(person,"小红");
sex.set(person,"男");
//静态成员变量赋值 ,不需要对象就可以赋值
firstName.set(null,"小红");
System.out.println(person);
}
}
Method(方法)
Method类:方法类,任何一个方法都是该类的一个对象
使用反射获取类的方法
public class TestGetMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//获取一个类的类对象
Class clazz = Class.forName("com.test.demo1.Person");
/*
* 获取一个类的方法
* 根据方法名和参数列表获得指定的一个方法
* */
Method method=clazz.getMethod("sum",int.class,double.class);
// 获取方法名
System.out.println(method.getName());
//获得方法的参数列表
System.out.println(method.getParameterCount());
//获取方法所有参数
Class[] parameterTypes = method.getParameterTypes();
System.out.println(Arrays.toString(parameterTypes));
//获取方法的返回值类型
System.out.println(method.getReturnType());
//获取一个类的所有方法
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method m:declaredMethods){
System.out.print("方法名:"+m.getName());
System.out.print("参数个数:"+m.getParameterCount());
System.out.print("方法返回值类型::"+m.getReturnType());
System.out.println();
}
}
}
使用反射执行对象方法
public class TestInvoke {
public static void main(String[] args) throws Exception {
//获取一个类的类对象
Class clazz = Class.forName("com.test.demo1.Person");
//获取类的构造方法
Constructor cons = clazz.getDeclaredConstructor(int.class, String.class, String.class);
//使用反射创建对象
Person person = (Person) cons.newInstance(1, "xiaobai", "男");
//1.使用反射执行一个无参数无返回值的方法
//获取要调用方法Method对象
Method showNameMethod = clazz.getDeclaredMethod("showName");
/* 调用invoke方法代表让当前方法执行
如果是实例方法,在方法执行时, 定需要一个对象才行
如果该方法执行需要参数,那么还要传入实参*/
showNameMethod.invoke(person);
//2.使用反射执行有返回值 有参数方法
Method sumMethod = clazz.getDeclaredMethod("sum", int.class, double.class);
//设置private方法可以访问的
sumMethod.setAccessible(true);
double sum = (double) sumMethod.invoke(person,1,10.11);
System.out.println(sum);
//3.使用反射执行执行静态方法
Method setFirstName = clazz.getDeclaredMethod("setFirstName");
setFirstName.invoke(null,"小白");
}
}
反射和泛型
没有出现泛型之前,Java中的所有数据类型包括:
1.primitive types:基本类型
2.raw type:原始类型。不仅仅指平常所指的类,还包括数组、接口、注解、枚举等结构。
Class类的一个具体对象代表一个指定的原始类型和基本类型。
泛型出现之后,也就扩充了数据类型:
1.parameterized types(参数化类型): 就是我们平常所用到的泛型List、Map<K,V>的List和Map
2.type variables(类型变量): 比如List中的T等。(注意和参数化类型的区别)
3.array types(数组类型):并不是我们工作中所使用的数组String[] 、byte[](这种都属于Class),
而是带 泛型的数组,比如List [ ],T [ ]
4.WildcardType(泛型表达式类型 通配符类型): 例如List< ? extends Number>
Java采用泛型擦除机制来引入泛型。但是擦除的是方法体中局部变量上定义的泛型,在泛型类、泛型接口中定义的泛型,在成员变量、成员方法上定义的泛型,依旧会保存(可以理解为定义泛型信息保留,使用泛型信息擦除)。保留下来的信息可以通过反射获取。
另外一方面,Class类的一个具体对象代表一个指定的原始类型和基本类型,和泛型相关的新扩充进来的类型不好被统一到Class类中,否则会涉及到JVM指令集的修改,是很致命的。
为了能通过反射操作泛型,但是实现扩展性而不影响之前操作,Java就新增了ParameterizedType, TypeVariable, GenericArrayType, WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
使用反射获取泛型类型
public class TestGeneric {
public static void main(String[] args) throws NoSuchMethodException {
Class clazz = Student.class;
Method declaredMethod = clazz.getDeclaredMethod("getData");
//获取返回值类型
Class returnType = declaredMethod.getReturnType();
System.out.println(returnType);
//获取返回值类型 带泛型
Type genericReturnType = declaredMethod.getGenericReturnType();
System.out.println(genericReturnType);
// 获取返回值类型 泛型参数
Type[] aType = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type t:aType){
System.out.println(t);
}
System.out.println("--------------------------");
//获取泛型参数列表
Method method2 = clazz.getDeclaredMethod("setDate", List.class, Map.class,String.class);
Type[] gPTypes = method2.getGenericParameterTypes();
for (Type type:gPTypes){
System.out.println(type);
if(type instanceof ParameterizedType){
//获取泛型中参数列表
Type[] act = ((ParameterizedType) type).getActualTypeArguments();
for (Type t:act){
System.out.println("\t"+t);
}
}
}
}
}
使用反射突破泛型的限制
public class TestGeneric2 {
public static void main(String[] args) throws Exception {
List<String> list =new ArrayList<>();
Class aClass = list.getClass();
list.add("Java");
list.add("MySQL");
list.add("MyBatis");
System.out.println(list);
Method add = aClass.getDeclaredMethod("add", Object.class);
add.invoke(list,123);
add.invoke(list,"aa");
add.invoke(list,111);
System.out.println(list);
}
}
使用反射创建数组
Array类为java.lang.reflect.Array类
import java.lang.reflect.Array;
public class TestArray {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> cls = Class.forName("java.lang.String");
Object array = Array.newInstance(cls,25);
//往数组里添加内容
Array.set(array,0,"hello");
Array.set(array,1,"Java");
Array.set(array,2,"fuck");
Array.set(array,3,"Scala");
Array.set(array,4,"Clojure");
//获取某一项的内容
System.out.println(Array.get(array,3));
}
}
使用反射注意事项
由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就 不需要用反射。
另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题