【JavaSE】反射与内省
1. 反射
- 为什么要用反射?
通过反射,可以获得它的真实类型,以及调用该对象中的方法
1.1字节码对象
1.2 获取字节码对象
三种方式:
- Class.forName(类的全限定名);
- 对象.getClass();
- 通过类型的class字段:Student.class/int.class/Integer.class
1.3 获取构造器
1.3.1 反射获取构造器
// 获取所有的public修饰构造器
public Constructor<?> getConstructors();
// 获取所有的(包括非public)构造器
public Constructor<?> getDeclaredConstructors();
// 获取指定的构造器(通过传递参数类型)
public Constructor getConstructor(Class... parameterTypes);
public Constructor getDeclaredConstructor(Class... parameterTypes);
1.3.2 调用构造器
// 调用构造器
Constructor con1 = clz.getConstructor();
Object obj = con1.newInstance();
// 调用私有构造器,必须先设置为可访问
Constructor con2 = clz.getDeclaredConstructor(String.class);
con2.setAccessible(true);
Object obj = con2.newInstance("Tom");
- Class对象.newInstance()
// 反射
Class clz = Class.forName("com.hyh.Student");
Constructor con = clz.getConstructor();
Object obj = con.newInstance();
// 简便
Class clz = Class.forName("com.hyh.Student");
Object obj = clz1.newInstance();
1.4 获取操作方法
- Method类
1.4.1 反射获取方法
// 获取所有方法:
// 获取到所有的公共方法,包括继承的方法
public Method[] getMethods();
// 获取去到奔雷中所有的方法,包括非public的, 不包括继承的
public Method[] getDeclaredMethod();
// 获取指定的方法:
public Method getMethod(String name, Class<?>...parameterTypes);
public Method getDeclaredMethod(String name, Class<?>...parameterTypes);
1.4.2 调用方法
public Object invoke(Object obj, Object... args);
obj:表示该方法要作用在哪个对象上
args: 调用方法的实际参数
// 例子:
Method method = clz.getMethod("study",String.class);
method.invoke(obj, "语文");
// 调用私有方法,必须先设置为可访问
Method sleep = clz.getDeclaredMethod("sleep", int.class);
// 设置可访问
sleep.setAccessible(true);
sleep.invoke(obj, 8);
1.5 操作字段
1.5.1 获取字段
// 获取单个字段, name: 表示要获取的字段的名称
public Field getField(String name);
public Field getDeclaredField(String name);
// 获取所有字段
public Field[] getFields();
public Field[] getDeclaredFields();
1.5.2 操作字段
get(Object obj)
set(Object, Object value)
// 例子:
Field nameField = clz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(obj, "Tom");
System.out.println(nameField.get(obj));
小结:
- 理解为什么需要反射
- 获取字节码对象的三种方式
- 通过字节码对象获取构造器,调用构造器
- 通过字节码对象获取方法,调用方法
2. 内省
2.1 JavaBean规范
- 类名使用public修饰
- 字段私有化
- 提供get/set方法
- 公关的无参构造
- 什么是属性?
JavaBean可以封装数据,就是将数据保存到一个bean对象的属性中的。属性不是字段,属性是通过get/set方法推导出来的。(只要是标准的get/set方法,就存在属性)
2.2 内省的基本操作
-
核心类:Introspector
-
内省的作用:
- 获取属性名和属性类型等相关状态信息
- 获取属性对应的读写方法,操作属性的值 等操作方式
- 常用API
// 1.通过字节码对象,获取到JavaBean的描述对象,
// 会获取到父类的描述对象,stopClass表示到哪个父类不获取
public static BeanInfo getBeanInfo(Class beanClass, Class stopClass);
// 2.通过JavaBean描述对象获取属性描述器
PropertyDescriptor[] getPropertyDescriptors()
// 3.通过属性描述器, 获取到属性名,属性类型,读写方法
public String getName()
public Class<?> getPropertyType()
public Method getReadMethod()
public Method getWriteMethod()
2.3 JavaBean和Map相互转换
- map2bean
public static Object map2bean(Map<String, Object> map, Class clz) throws Exception {
Object obj = clz.newInstance();
// 1. 获取所有属性
BeanInfo beanInfo = Introspector.getBeanInfo(clz, Object.class);
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
// 2. 获取属性名作为map的key,
String key = pd.getName();
// 获取属性值,
Object value = map.get(key);
// 调用Setter方法设置属性值
pd.getWriteMethod().invoke(obj, value);
}
return obj;
}
- bean2map
public static Map<String, Object> bean2map(Object obj) throws Exception {
Map<String, Object> map = new HashMap<>();
BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass(), Object.class);
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
String key = pd.getName();
// 获取属性的getter方法并调用,得到属性值作为map的value
Object value = pd.getReadMethod().invoke(obj);
map.put(key, value);
}
return map;
}
3. 注解的基本定义和使用
3.1 元注解:
@Target: 表示注解可以贴在哪些位置(类,方法上,构造器上等等).位置的常量封装在 ElementType 枚
举类中:
ElementType.ANNOTATION_TYPE只能修饰Annotation
ElementType.CONSTRUCTOR只能修饰构造方法
ElementType.FIELD只能修饰字段(属性),包括枚举常量
ElementType.LOCAL_VARIABLE只能修饰局部变量
ElementType.METHOD 只能修饰方法
ElementType.PACKAGE只能修饰包(极少使用)
ElementType.PARAMETER只能修饰参数
ElementType.TYPE只能修饰类,接口,枚举
@Retention: 表示注解可以保存在哪一个时期. 表示时期的值,封装在RetentionPolicy枚举类中:
@Documented: 使用 @Documented 标注的标签会保存到 API文档中.
@Inherited: @Inherited标注的标签可以被子类所继承.
3.2 注解语法
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface 注解名 {
// 定义抽象方法,
// 在注解中,抽象方法叫属性,抽象方法名叫属性名,返回值叫属性值的类型
返回值类型 方法名() default 默认值;
}
例子:
@Targer(ElementType.TYPE)
@Rentation(RetentionPolicy.RUNTIME)
public @interface VIP{
String name() default "狗子";
int age();
String[] hobby();
}