学习内容
反射
将类的各个组成部分封装为其他对象的过程叫反射。
好处
- 可以在程序运行中操作这些对象
- 可以解耦,提高程序的可扩展性
Class
对象
获取 Class
对象的三种方式
-
类名.class
多用于参数的传递。
-
对象名.getClass()
多用于对象的获取字节码。
-
Class.forName(类的全名)
多用于配置文件
Class
对象的功能
- 获取成员变量
Field[] getFields()
:获取所有public
修饰的成员变量。Field getField(String fieldName)
:获取指定的,public
修饰的成员变量。Field getDeclaredFields()
:获取所有的成员变量,不考虑修饰符。Field getDeckaredField(String fieldName)
:获取指定的成员变量,不考虑修饰符。setAccessible(true)
:将访问权限打开,打开之后可以忽略修饰符的安全检查,从而访问所有修饰符修饰的字段,也称暴力反射。
- 获取构造方法
Constructor<?>[] getConstructors()
:获取所有的public
构造器。Constructor<?>[] getDeclaredConstructors()
:获取所有的构造器。Constructor<T> getConstructor(类<?>... parameterTypes)
:获取指定的,public
修饰的构造器。Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
:获取指定的构造器,不考虑修饰符。
- 获取成员方法
Method[] getMehods()
:获取所有的public
方法。Method[] getMehod(String name, 类<?>... parameterTypses)
: 获取指定的的public
方法。Method[] getDeclaredehods()
:获取所有方法。Method[] getDeclaredMehod(String name, 类<?>... parameterTypses)
:
- 执行获取的成员方法
(Method 对象).invoke(实例对象)
;
Field:成员变量
-
操作:
- 设置值
void set(Object obj, Object value)
- 获取值
get(Object obj)
- 忽略访问权限修饰符的安全检查
setAccessible(true)
:暴力反射
Constructor:构造方法
- 创建对象:
T newInstance(Object... initargs)
- 如果使用空参数构造方法创建对象,操作可以简化:
Class
对象的newInstance
方法
Method :方法对象
-
执行方法:
Object invoke(Object obj, Object... args)
-
获取方法名称:
String getName
:获取方法名
注解
概念
注解(Annotation),也叫元数据。一种代码级别的说明。可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。但这种注释不是给人看的,是给计算机看的。
使用方式
@注解名称
作用
- 编写文档:通过代码里标识的注解生成文档
- 代码分析:通过代码里标识的注解对代码进行分析
- 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查
JDK 中一些预定义注解
@Override
检测被标注的方法是否是继承自父类的。
@Deprecated
该注解标注的内容已过时(就不推荐你使用这个内容,但是用了也不会出错)。
调用@Deprecated
标注的方法,方法会有一道横线划掉。例如 deprecatedMethod
@SuppressWarinings
压制警告:使编译器对于被标注内容的警告不再提示。一般会传递参数 (“all”)。
注解的本质
本质是一个接口,默认继承了 Annotation
接口。
public interface 注解名 extends java.lang.annotation.Annotation{}
属性
属性:接口中的抽象方法
- 要求:
- 属性的返回值类型有下列取值
- 基本数据类型
- String
- 枚举
- 注解
- 以上类型的数组
- 在使用属性时,需要给属性赋值。
- 在定义属性时,可以后接
default
以设置默认初始化值,这样使用注解时就可以不用赋值。 - 如果只有一个属性需要赋值,并且属性的名称是 value ,则 value 可以省略,直接定义值即可。
- 数组赋值时,值使用 {} 包裹。如果数组中只有一个值,则 {} 可以省略
- 在定义属性时,可以后接
元注解
用于描述注解的注解
常用元注解
1.@Target:描述注解能够作用的位置
- ElementType取值:
- TYPE:可以作用于类上
- METHOD:可以作用于方法上
- FIELD:可以作用于成员变量上
- @Retention:描述注解被保留的阶段
@Retention(RetentionPolicy.RUNTIME)
:当前被描述的注解,会保留到class字节码文件中,并被JVM读取到。- SOURCE:
- CLASS:类对象阶段
- @Documented:描述注解是否被抽取到api文档中
- @Inherited:描述注解是否被子类继承
自定义注解
格式
- 元注解
- public @interface 注解名称{}
工作内容
反射任务
任务1
使用反射解析某个你创建的类,把类中所有的属性、方法、接口都提取出来。要求:
通过三种不同的方式获取 Class 对象;
修改解析出来的类的属性值,然后再给类添加新的属性;
调用类方法(包括私有方法、静态方法和构造方法),执行后打印结果;
解析出该类的父类对象,并同样修改其父类的属性值并调用父类的方法。
答:
//定义 Animal 类
public class Animal {
int weight ;
public void sleep(){
System.out.println("动物在睡觉");
}
}
//定义 Cat 类
class Cat extends Animal {
public String name;
public int age;
public int count;
//重写public 方法 sleep
@Override
public void sleep() {
System.out.println("猫在睡觉");
}
//静态方法 stop
public static void stop(){
System.out.println("静态方法,发呆,什么都不做");
}
//私有方法 catchMouse
private void catchMouse() {
System.out.println("抓老鼠");
}
//构造方法 3个
public void Cat(String name) {
this.name = name;
}
public void Cat(int age) {
this.age = age;
}
public void Cat(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//第一种获取 Class 对象的方式
Class cls1 = Cat.class;
//第二种获取 Class 对象的方式
Cat cat2 = new Cat();
Class cls2 = cat2.getClass();
//第三种获取 Class 对象的方式
Class cls3 = Class.forName("com.xxm.Mission8.Cat");
Class cls4 = Cat.class;
//声明 Field 对象 field,让其cls4类对象的 "name" 字段。
Field field = cls4.getDeclaredField("name");
//创建一个实例对象
Cat cat4 = new Cat();
//field 对象调用get方法,并赋值给 value
Object value = field.get(cat4);
//打印此时的 cat4.name 和 value,因为都未赋值,所以都是 null。
System.out.println(cat4.name);
System.out.println(value);
//field 对象调用 set 方法,将 cat4 对象的值设为 "field猫"
field.set(cat4, "field猫");
//再打印此时的 cat4.name,变为了 "field猫"
System.out.println(cat4.name);
Class cls5 = Cat.class;
Cat cat5 = new Cat();
//普通 public 方法
Method eatMethod = cls5.getMethod("sleep");
eatMethod.invoke(cat5); //结果:打印出了睡觉"
//静态方法
Method stopMethod = cls5.getDeclaredMethod("stop");
stopMethod.invoke(cat5); //结果:打印出了"静态方法,发呆,什么都不做"
//private 方法
Method catMouseMethod = cls5.getDeclaredMethod("catchMouse");
catMouseMethod.setAccessible(true);
catMouseMethod.invoke(cat5); //结果:打印出了抓老鼠
//调用构造器
Constructor constructor = cls5.getConstructor(lass);
System.out.println(constructor); //结果:
Class cls6 = cls5.getSuperclass();
System.out.println(cls6); //结果:class com.xxm.mission8.Animal
Method animalSleepMethod = cls6.getDeclaredMethod("sleep");
animalSleepMethod.invoke(cat5); //结果:打印出了"猫在睡觉"
Animal animal1 = new Animal();
animalSleepMethod.invoke(animal1); //结果:打印出了"动物在睡觉"
这里有一个很有意思的现象,在我使用 invoke
指令时,由于忘记重新创建一个 Animal
类的实例对象,我的invoke
方法传入的参数为前面创建的 cat5
,结果调用的是在 Cat
类中被覆写过的 sleep
方法。可以得出 Method
对象的获取方法的方法,确实是根据传入的字符串来判断的,而实际调用的时候,会根据传入参数的类来决定调用哪个方法。
注解任务
任务1
-
自定义一个 ElementType 类别为 METHOD,RetentionPolicy 类别为 RUNTIME 的注解,任务要求:
- 创建一个类,类的方法使用自定义的注解;
答:
创建自定义注解
MyAnno1
:package com.xxm.mission9; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) public @interface MyAnno1 { String value() default "这是MyAnno1的默认注解内容"; }
创建类 Cat
:
package com.xxm.mission9;
public class Cat {
@MyAnno1
public void eat(){
System.out.println("猫猫吃猫粮");
}
}
- 通过反射解析类注解,判断该方法是否有注解,如果有则打印出注解的内容。
答:
//获取类对象
Class class1 = Cat.class;
//获取方法
Method m = class1.getMethod("eat");
//获取注解对象
MyAnno1 anno1 = m.getAnnotation(MyAnno1.class);
//打印注解
System.out.println(anno1.value()); //结果:这是MyAnno1的默认注解内容
-
自定义一个 ElementType 类别为 FIELD,RetentionPolicy 类别为 CLASS 的注解,任务要求:
- 创建一个类,给类的某个字段使用自定义的注解;
答:
创建自定义注解
MyAnno2
:package com.xxm.mission9; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnno2 { String className(); String fieldName(); String value() default "这是 MyAnno2 的默认内容"; }
将上一题中的
Cat
类添加字段,并添加自定义注解:
package com.xxm.mission9;
public class Cat {
@MyAnno1(className = "com.xxm.mission9.Cat", methodName = "eat")
public void eat() {
System.out.println("猫猫吃猫粮");
}
@MyAnno2(className = "com.xxm.mission9.Cat", fieldName = "name")
public int name;
}
-
通过反射解析类注解,判断该方法是否有注解,如果有则打印出注解的内容。
Class class2 = Cat.class; Field f = class1.getDeclaredField("name"); MyAnno2 anno2 = f.getAnnotation(MyAnno2.class); System.out.println(anno2.value()); //结果:这是 MyAnno2 的默认内容
-
模仿上面的嵌套注解,除了可以字段类型的注解外,再加上字段长度和是否可以为空的注解,并加入到@TableColumn 注解中去。
答:
字符串长度:
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ColumnLength { int value() default 0; }
字符串是否为空:
public @interface ColumnEmptyOrNot { @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ColumnLength { boolean value() default true; } }
添加后的
@TableColumn
:@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface TableColumn { ColumnType columntype() default @ColumnType; ColumnLength columnlength() default @ColumnLength; ColumnEmptyOrNot columnEmptyOrNot() default @ColumnEmptyOrNot; }
-
自定义嵌套注解,用来描述用户类中的「岗位」字段。
- 第一层注解定义岗位所在的部门;
- 第二层注解定义岗位名称和描述。
答:
岗位名称注解:
package com.xxm.advanced_camp.mission9.Task4;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author xxm
* 注解:岗位名称
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PostName {
String postName() default "";
}
岗位描述注解:
package com.xxm.advanced_camp.mission9.Task4;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author xxm
* 注解:岗位描述
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PostDescription {
String postDescription() default "";
}
部门注解:
package com.xxm.advanced_camp.mission9.Task4;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author xxm
* 注解:岗位所在部门
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Department {
String departmentName() default "";
PostName postName() default @PostName;
PostDescription postDescription() default @PostDescription;
}
应用嵌套注解:
package com.xxm.advanced_camp.mission9.Task4;
public class Position {
@Department(postName = @PostName, postDescription = @PostDescription)
private String postName;
private String postDescription;
}