Javaの反射
反射
第一章 反射的理解
1.1 正射
我们在编写代码时,当需要使用到某一个类的时候,必定先会去了解这是一个什么类,是用来做什么的,有怎么样的功能。之后我们才对这个类进行实例化,之后再使用这个类的实例化对象进行操作。
Person person = new Person();
person.sleep(“8:00”);
1.2 反射
反射则是在代码一开始编写时不知道要初始化的类是什么。因此,自然也无法使用new关键字来创建对象了。而当我们之后得到我们要初始化的类的名称及路径时,我们就可以使用JDK提供的反射API进行反射调用。
Class clazz = Class.forName(“com.Person”);
Method method = clazz.getMethod(“sleep”, String.class);
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, “8:00”);
以上两段代码,其结果都是一样的,但是其实现的过程却有很大的差别:
- 第一段代码在未运行前就已经确定了要运行的类(Person);
- 第二段代码则是在整个程序运行时从某些地方(例:配置文件)获取到相应的字符串值才能知道要运行的类(”com.Person”)。
第二章 反射常用类和常用函数
2.1 获取Class对象
在反射中,要获取一个类或调用一个类的方法,首先必须要获取到该类的对象,在Java API中,获取Class类对象三种方法:
①:Class.forName(“类的路径名”);
Class clazz = Class.forName(“com.test.domain.Person”);
②:利用已有类对象的getClass()方法;
Person person = new Person();
Class clazz = person.getClass();
③:对于在编译前就已经知道的类,可以使用.class属性;
Class clazz = Person.class;
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的 Class对象都是同一个。
2.2 Constructor类
Constructor是构造方法类,类中的每一个构造方法都是Constructor的对象,通过Constructor对象可以实例化对 象。 - Constructor getConstructor(Class… parameterTypes) 根据参数类型获取构造方法对象,只能获得public修饰的构造方法。
- Constructor getDeclaredConstructor(Class… parameterTypes) 根据参数类型获取构造方法对象,包括private修饰的构造方法。
- Constructor[] getConstructors() 获取所有的public修饰的构造方法
- Constructor[] getDeclaredConstructors() 获取所有构造方法,包括privat修饰的
2.3 通过反射创建类对象
①:通过class对象的newInstance()方法
Class clazz = Class.forName(“com.Person”);
Person person = (Person)clazz.newInstance();
②:通过Constructor对象的newInstance()方法;
Class clazz = Class.forName(“com.Person”);
Constructor con = clazz.getConstructor();
Person person = (Person)con.newInstance();
2.4 Method类
Method是方法类,类中的每一个方法都是Method的对象,通过Method对象可以调用方法。
Class类中与Method相关方法: - Method getMethod(“方法名”, 方法的参数类型… 类型) 根据方法名和参数类型获得一个方法对象,只能是获取public修饰的
- Method getDeclaredMethod(“方法名”, 方法的参数类型… 类型) 根据方法名和参数类型获得一个方法对象,包括private修饰的
- Method[] getMethods() 获取所有的public修饰的成员方法,包括父类中。
- Method[] getDeclaredMethods() 获取当前类中所有的方法,包含私有的,不包括父类中。
Method类中常用方法: - Object invoke(Object obj, Object… args) 根据参数args调用对象obj的该成员方法 如果obj=null,则表示该方法是静态方法
- void setAccessible(boolean flag) 暴力反射,设置为可以直接调用私有修饰的成员方法
2.5 通过反射操作成员变量
①:获取所有成员getFields()&getDeclaredFields();
使用getFields()方法可以获取Class类的成员变量,但是无法获取私有属性。
使用getDeclaredFields()方法可以获取Class类的所有成员变量。
Class clazz = Class.forName(“com.Person”);
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.print(field.getName());
}
②:获取单个成员getField(String name)&getDeclared(String name)
③:修改成员变量的值set(Object obj, Object value)
Class clazz = Class.forName(“com.test.domain.Person”);
Person person = (Person)clazz.newInstance();
Field field = clazz.getField(“name”);
field.set(person, “张三”);
④:当属性为private时,这是我们无法直接使用set()方法修改它的值,此时应该使用setAccessible()方法取得访问权限:
Class clazz = Class.forName(“com.Person”);
Person person = (Person)clazz.newInstance();
Field field = clazz.getDeclaredField(“name”);
field.setAccessible(true);
field.set(person, “张三”);
第三章 反射案例
需求:写一段程序,在不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。
步骤: - 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
- 在程序中加载读取配置文件
- 使用反射技术来加载类文件进内存
- 创建对象
- 执行方法
1.pro.properties
className=com.Student
methodName=sleep
2.Student.java
public class Student {
public void sleep(){
System.out.println(“sleep…”);
}
}
3.Test.java
public class Test {
public static void main(String[] args) {
//1.加载配置文件
//1.1创建Properties对象
Properties pro = new Properties();
//1.2加载配置文件,转换为一个集合
//1.2.1获取class目录下的配置文件
ClassLoader classLoader = Test.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream(“pro.properties”);
pro.load(is);
//2.获取配置文件中定义的数据
String className = pro.getProperty(“className”);
String methodName = pro.getProperty(“methodName”);
//3.加载该类进内存
Class cls = Class.forName(className);
//4.创建对象
Object obj = cls.newInstance();
//5.获取方法对象
Method method = cls.getMethod(methodName);
//6.执行方法
method.invoke(obj);
}
}
}
注解
第一章 注解概述
1.1 定义
注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
1.2 作用分类
- 编写文档:通过代码里标识的注解生成文档【例如,生成文档doc文档】
- 代码分析:通过代码里标识的注解对代码进行分析【例如,注解的反射】
- 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【例如,Override】
1.3 常见注解
@author:用来标识作者名。
@version:用于标识对象的版本号,适用范围:文件、类、方法。
@Override :用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。
第二章 自定义注解
定义格式:
元注解
public @interface 注解名称{
属性列表;
}
注解本质上就是一个接口,该接口默认继承Annotation接口。
2.1 注解的属性
- 属性的作用
可以让用户在使用注解时传递参数,让注解的功能更加强大。
- 属性的作用
- 属性的格式
格式1:数据类型 属性名();
格式2:数据类型 属性名() default 默认值;
- 属性的格式
- 属性定义示例
public @interface Student {
String name(); // 姓名
int age() default 18; // 年龄
String gender() default “男”; // 性别
}
// 该注解就有了三个属性:name,age,gender
- 属性定义示例
- 属性适用的数据类型
八种基本数据类型(int,float,boolean,byte,double,char,long,short)
String类型,Class类型,枚举类型,注解类型
以上所有类型的一维数组
2.2 使用自定义注解
定义注解:Book
包含属性:String value() 书名
包含属性:double price() 价格,默认值为 100
包含属性:String[] authors() 多位作者
代码实现:
public @interface Book {
// 书名
String value();
// 价格
double price() default 100;
// 多位作者
String[] authors();
}
使用注解:
public class BookShelf {
@Book(value = “西游记”,price = 998,authors = {“吴承恩”,”白求恩”})
public void showBook(){
}
}
如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。
如果属性没有默认值,那么在使用注解时一定要给属性赋值。
第三章 注解之元注解
默认情况下,注解可以用在任何地方,比如类,成员方法,构造方法,成员变量等地方。如果要限制注解的使用位置怎么办?那就要学习一个新的知识点:元注解。
@Target
@Retention
3.1 元注解之@Target
作用:指明此注解用在哪个位置,如果不写默认是任何地方都可以使用。
可选的参数值在枚举类ElemenetType中包括:
TYPE: 用在类,接口上
FIELD:用在成员变量上
METHOD: 用在方法上
PARAMETER:用在参数上
CONSTRUCTOR:用在构造方法上
LOCAL_VARIABLE:用在局部变量上
3.2 元注解之@Retention
作用:定义该注解的生命周期(有效范围)。
可选的参数值在枚举类型RetentionPolicy中包括:
SOURCE:注解只存在于Java源代码中,编译生成的字节码文件中就不存在了。
CLASS:注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没有,默认值。
RUNTIME:注解存在于Java源代码中、编译以后的字节码文件中、运行时内存中,程序可以通过反射获取该注解。
3.3 元注解使用示例
@Target({ElementType.METHOD,ElementType.TYPE})
@interface Stu{
String name();
}
// 类
@Stu(name=”jack”)
public class AnnotationDemo02 {
// 成员变量
@Stu(name = “lily”) // 编译失败
private String gender;
// 成员方法
@Stu(name=”rose”)
public void test(){
}
// 构造方法
@Stu(name=”lucy”) // 编译失败
public AnnotationDemo02(){}
}
第四章 注解解析
通过java技术获取注解数据的过程叫做注解解析。
4.1 与注解解析相关的接口
- Anontation:所有注解类型的公共接口,类似所有类的父类是Object。
- AnnotatedElement:定义了与注解解析相关的方法,常用方法以下四个
- boolean isAnnotationPresent(Class annotationClass); 判断当前对象是否有指定的注解,有则返回true,否则 返回false。
- T getAnnotation(Class
annotationClass); 获得当前对象上指定的注解对象。 - Annotation[] getAnnotations(); 获得当前对象及其从父类上继承的所有的注解对象。
- Annotation[] getDeclaredAnnotations();获得当前对象上所有的注解对象,不包括父类的。
4.2 获取注解数据的原理
注解作用在哪个成员上,就可以通过反射获取该成员的对象,通过该成员对象获取该成员的注解。
如注解作用在方法上,就通过方法(Method)对象得到它的注解:
// 得到方法对象
Method method = clazz.getDeclaredMethod(“方法名”);
// 根据注解名得到方法上的注解对象
Book book = method.getAnnotation(Book.class);
如注解作用在类上,就通过Class对象得到它的注解:
// 获得Class对象
Class c = 类名.class;
// 根据注解的Class获得使用在类上的注解对象
Book book = c.getAnnotation(Book.class);
第五章 使用反射获取注解的数据
需求说明
- 定义注解Book,要求如下:
- 包含属性:String value() 书名
- 包含属性:double price() 价格,默认值为 100
- 包含属性:String[] authors() 多位作者
- 限制注解使用的位置:类和成员方法上
- 指定注解的有效范围:RUNTIME
定义BookStore类,在类和成员方法上使用Book注解
定义TestAnnotation测试类获取Book注解上的数据
代码实现
注解Book
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
// 书名
String value();
// 价格
double price() default 100;
// 作者
String[] authors();
}
BookStore类
@Book(value = “红楼梦”,authors = “曹雪芹”,price = 998)
public class BookStore {@Book(value = “西游记”,authors = “吴承恩”)
public void buyBook(){
}
}
TestAnnotation类
public class TestAnnotation {
public static void main(String[] args) throws Exception{
System.out.println(“———获取类上注解的数据———-“);
test01();
System.out.println(“———获取成员方法上注解的数据———-“);
test02();
}
/**
获取BookStore类上使用的Book注解数据
/
public static void test01(){
// 获得BookStore类对应的Class对象
Class c = BookStore.class;
// 判断BookStore类是否使用了Book注解
if(c.isAnnotationPresent(Book.class)) {
// 根据注解Class对象获取注解对象
Book book = (Book) c.getAnnotation(Book.class);
// 输出book注解属性值
System.out.println(“书名:” + book.value());
System.out.println(“价格:” + book.price());
System.out.println(“作者:” + Arrays.toString(book.authors()));
}
}
/*获取BookStore类成员方法buyBook使用的Book注解数据
*/
public static void test02() throws Exception{
// 获得BookStore类对应的Class对象
Class c = BookStore.class;
// 获得成员方法buyBook对应的Method对象
Method m = c.getMethod(“buyBook”);
// 判断成员方法buyBook上是否使用了Book注解
if(m.isAnnotationPresent(Book.class)) {
// 根据注解Class对象获取注解对象
Book book = (Book) m.getAnnotation(Book.class);
// 输出book注解属性值
System.out.println(“书名:” + book.value());
System.out.println(“价格:” + book.price());
System.out.println(“作者:” + Arrays.toString(book.authors()));
}
}
}
作业
1.定义Person类,私有成员变量有name,age,公开成员变量有sex,成员方法包括gette和setter,构造方法有无参和全参,用反射去创建一个Person对象,用2种方式。
2.在第一题中加入方法show,打印一串字符串,利用反射执行该方法。
4.在第一题基础之上,利用反射设置name为“张三”,sex为“男”。
Person.java
public class Person {
private String name;
private int age;
private String sex;@Override
public String toString() {
return “Person{“ +
“name=’” + name + ‘'‘ +
“, age=” + age +
“, sex=’” + sex + ‘'‘ +
‘}’;
}public void gogogo(){
System.out.println(“通过读取配置文件得到的gogogo方法在运行!”);
}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 Person() {
}public Person(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public void show(){
System.out.println(“这是一个无参的show方法”);
}
}
public class Demo1 {
public static void main(String[] args) throws Exception{
//1.定义Person类,私有成员变量有name,age,公开成员变量有sex,成员方法包括gette和setter,
// 构造方法有无参和全参,用反射去创建一个Person对象,用2种方式。
//第一种
Class clazz = Class.forName(“com.hmwk124.Person”);
Person p = (Person)clazz.newInstance();
//第二种
Class clazz1 = Class.forName(“com.hmwk124.Person”);
Constructor constructor = clazz.getConstructor();
Person p1 = (Person)constructor.newInstance();
//2.在第一题中加入方法show,打印一串字符串,利用反射执行该方法。
Method show = clazz1.getMethod(“show”);
show.invoke(p1);
//4.在第一题基础之上,利用反射设置name为“张三”,sex为“男”。
Field name = clazz1.getDeclaredField(“name”);
Field sex = clazz1.getDeclaredField(“sex”);
name.setAccessible(true);
name.set(p1,”张三”);
sex.setAccessible(true);
sex.set(p1,”男”);
Method toString = clazz1.getMethod(“toString”);
System.out.println(toString.invoke(p1));
}
}
3.编写一个类A,增加一个实例方法showString,用于打印一条字符串,再编写一个测试类TestA ,在测试类中,用键盘输入一个字符串,该字符串就是类A的全名,使用反射机制创建该类的对象,并调用该对象中的方法showString。
//3.编写一个类A,增加一个实例方法showString,用
// 于打印一条字符串,再编写一个测试类TestA ,在测试类中,
// 用键盘输入一个字符串,该字符串就是类A的全名,使用反射机制创
// 建该类的对象,并调用该对象中的方法showString。
public class A {
public void showString(){
System.out.println(“这是A类的showString方法!”);
}
}
public class TestA {
//3.编写一个类A,增加一个实例方法showString,用
// 于打印一条字符串,再编写一个测试类TestA ,在测试类中,
// 用键盘输入一个字符串,该字符串就是类A的全名,使用反射机制创
// 建该类的对象,并调用该对象中的方法showString。
public static void main(String[] args) {
String s;
Scanner sc = new Scanner(System.in);
System.out.print(“请输入完整的类名:”);
s = sc.nextLine();
Class clazz = null;
try {
clazz = Class.forName(s);
Constructor constructor = clazz.getConstructor();
A a = (A) constructor.newInstance();
a.showString();
} catch (ClassNotFoundException e) {
System.out.println(“找不到该类!”);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
5.在第一题基础上,写一个Properties格式的配置文件,配置类的完整名称和所要执行的方法,写一个程序,读取这个Properties配置文件,获得类的完整名称并加载这个类,并执行读取的方法。
public class Demo5 {
//在第一题基础上,写一个Properties格式的配置文件,配置类的完整名
// 称和所要执行的方法,写一个程序,读取这个Properties配置文件,获
// 得类的完整名称并加载这个类,并执行读取的方法。
public static void main(String[] args) throws Exception {
Properties pro = new Properties();
ClassLoader cll = Demo5.class.getClassLoader();
InputStream is = cll.getResourceAsStream(“com/hmwk124/pro.properties”);
pro.load(is);
String className = pro.getProperty(“className”);
String methodName = pro.getProperty(“methodName”);
Class clazz = Class.forName(className);
Object o = clazz.newInstance();
Method method = clazz.getMethod(methodName);
method.invoke(o);
}
}
配置文件:
className=com.hmwk124.Person
methodName=gogogo
1.自定义注解Hello,包含属性value,name,age(默认值100),设置只能作用于方法和成员变量,生命周期为RUNTIME,创建普通类Person,类中定义成员变量name,方法show(),分别在之上加入注解,利用反射解析注解。
Hello.java
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Hello {
String value();
String name();
int age() default 100;
}
Person.java
public class Person {
@Hello(value = “10”,name=”李四”,age = 20)
private String name;
@Hello(value = “100”,name = “张三”,age = 18)
public void show(){
}
}
Demo6.java
public class Demo6 {
public static void main(String[] args) throws Exception {
System.out.println(“获取成员变量上注解的数据”);
test01();
System.out.println(“获取方法上注解的数据”);
test02();
}
//获取Person类的成员变量上使用的Hello注解数据
public static void test01() throws NoSuchFieldException {
Class clazz = Person.class;
Field field = (Field) clazz.getDeclaredField(“name”);
System.out.println(field.isAnnotationPresent(Hello.class));
if(field.isAnnotationPresent(Hello.class)){
Hello hello = (Hello)field.getAnnotation(Hello.class);
//输出Hello注解属性值
System.out.println(“value:” + hello.value());
System.out.println(“name:” + hello.name());
System.out.println(“age:” + hello.age());
}
}
//获取方法show上对象的Method对象
public static void test02() throws Exception{
Class clazz1 = Person.class;
Method show = clazz1.getMethod(“show”);
//判断是否使用注解
if(show.isAnnotationPresent(Hello.class)){
Hello hello = (Hello)show.getAnnotation(Hello.class);
//输出注解属性值
System.out.println(“value:” + hello.value());
System.out.println(“name:” + hello.name());
System.out.println(“age:” + hello.age());
}
}
}