一、简介
反射介绍:
框架:半成品软件。可以在框架的基础上进行软件开发,简化编码。
反射:将类的各个组成部分封装为其他对象,这就是反射机制。反射是框架设计的灵魂。
注:写框架用到反射,用框架不需要反射。
好处:
- 可以在程序运行过程中,操作这些对象
例如:String str =“a”; str. 会发现idea给我们好多方法提示 前提:idea是一直在运行。 (内部用的反射机制,定义一个字节码文件,把字节码文件封装成对象,把所有的方法放到Method对象数组中,用的时候把数组里面的所有方法展示) - 可以解耦,提高程序的可扩展性。
类的简介:
用以下6个类,就可以描述任何一个类。
类名 | 解释说明 |
---|
Class | 用于描述类本身 | Package | 用于描述类所属的包 | Field | 用于描述类中的属性 | Constructor | 用于描述类中的构造方法 | Method | 用于描述类中的方法 | Annotation | 用于描述类中的注解 |
二、重要的类
1>.Class类
Class类 用于描述类本身。我们知道,类是用来描述具有相同特征的一组对象的,而这个Class类是用来描述类本身的。
1.获取Class对象的方式:
- Class.forName(“包名.类名”);
将字节码文件加载进内存,返回Class对象。 多用于配置文件,将类名定义在配置文件中,读取文件,加载类。 - 类名.class;
通过类名的属性.class获取,多用于参数的传递。 - 对象.getClass();
getClass()方法在Object类中定义着,多用于对象的获取字节码方式。
结论: 同一个字节码( .class)文件,在一次程序运行过程中只会被加载一次。即:不论通过哪种方式获取Class对象,其实得到的都是同一个对象(用 " == " 号判断会返回true)。
2.重要方法
·操作成员:
方法 | 返回值 | 解释说明 |
---|
newInstance() | Object | 用于创建一个当前类的实例对象, 相当于调用了当前类的无参构造方法。 | 获取类中的属性: | | | getField(“属性名”) | Field | 用于获取指定名称的、 public修饰的属性, 也可以是父类中的 public属性。 | getFields() | Field[] | 用于获取所有的public修饰的属性, 也可以是父类中的 public属性。 | getDeclaredField(“属性名”) | Field | 获取指定名称的属性,不考虑修饰符, 但只能获取本类中的属性,不可获取父类中的属性 。 | getDeclaredFields() | Field[] | 获取当前类中的所有属性,不考虑修饰符。 但只能获取本类中的属性,不可获取父类中的属性 。 | 获取类的构造方法: | | | getConstructor( Class<?>… parameterTypes) | Constructor | 获取当前类中某个public修饰的构造方法。 Class<?>是该构造方法的形参表列,这里需传入参数.class类型。如果使用空参数构造方法创建对象,操作可以简化为:Class对象的newInstance方法. | getConstructors() | Constructor[] | 用于获取所有的public修饰的构造方法。 | getDeclaredConstructor( Class<?>… parameterTypes) | Constructor | 获取类中的某个构造方法,不考虑修饰符。 Class<?>是该构造方法的形参表列,这里需传入参数.class类型。 | getDeclaredConstructors() | Constructor[] | 用于获取类中的所有构造方法,不考虑修饰符。 | 获取类中的方法: | | | getMethod(“方法名” ,Class<?>… parameterTypes) | Method | 获取指定名称的、 public修饰的类内方法, 也可以是父类中的 public方法。 Class<?>是该方法的形参表列,这里需传入参数.class类型。 | getMethods() | Method[] | 用于获取所有的public修饰的方法, 也可以是父类中的 public方法。 | getDeclaredMethod(“方法名” , Class<?>… parameterTypes) | Method | 用于获取指定名称的方法,不考虑修饰符, 但只能获取本类中的方法,不可获取父类中的方法 。 | getDeclaredMethods() | Method[] | 获取当前类中的所有方法,不考虑修饰符。 但只能获取本类中的方法,不可获取父类中的方法 。 | 获取类、属性、方法上的注解: | | | getDeclaredAnnotationsByType (Class annotationClass) | Annotation[] | 通过指定的注解类型,获取所有注解。 | getAnnotation(Class annotationClass) | Annotation | 通过指定的注解类型,获取注解。 | getAnnotations() | Annotation[] | 获取所有的注解。 | getDeclaredAnnotation (Class annotationClass) | Annotation | 通过指定的注解类型,获取注解。 | getDeclaredAnnotations() | Annotation[] | 获取所有的注解。 |
·查看性质:
方法 | 返回值 | 解释说明 |
---|
getModifiers() | int | 用于获取类的修饰符,包括权限修饰符和特征修饰符。 每个修饰符都用一个固定不变的static修饰的int来表示, 从0开始,依次是0、1、2、4、8 、16、32 … 如果该类的修饰符有多个,则返回的int值是多个修饰符的和。 (0–默认不写、1–public、2–private、4–protected、8–static、 16–final、32–synchronize、64–volatile、128–transient、 256–native、512–interface、1024–abstract、2048–strict) | getSuperclass() | Class | 获取当前类 单继承的父类。 | getInterfaces() | Class[] | 获取当前类 多实现的所有接口。 | getClasses() | Class[] | 获取当前类中,public修饰的内部类。 | getName() | String | 获取类的全名,包括包名和类名。 | getSimpleName() | String | 只获取当前类的类名。 | getPackage() | Package | 获取当前类所属的包。 通常会调用Package对象下的getName()方法查看包名。 |
2>.Field类
Field类 用于描述类中的属性。
方法 | 返回值 | 解释说明 |
---|
set(Object obj, Object value) | void | 赋值操作: 调用此方法,可以给某个实例对象的当前属性赋具体值。 obj表示想要操作的实例对象,value是赋给当前属性的具体值。 | get(Object obj) | Object | 取值操作: 调用此方法,可以取出某个实例对象,当前属性的实际值。 | setAccessible(true) | void | 实现暴力反射,调用此方法后,可以操作非public修饰的属性。 | getType() | Class | 用于获取当前属性的数据类型(包名.类名)。 | getModifiers() | int | 用于获取当前属性的修饰符(包括权限和特征), 每个修饰符都用一个整数来表示,详情看上面Class表的描述。 | getName() | String | 用于获取当前属性的属性名。 |
3>.Constructor类
Constructor类 用于描述类中的构造方法。
方法 | 返回值 | 解释说明 |
---|
newInstance(Object … initargs) | Object | 用于创建一个当前类的实例对象, 相当于调用了当前类相应的构造方法。 参数 initargs表示的是当前构造方法的形参表列。 | getParameterTypes() | Class[] | 用于获取当前方法的形参表列的数据类型(包名.类名)。 | getExceptionTypes() | Class[] | 获取当前方法抛出异常的数据类型(包名.类名)。 | setAccessible(true) | void | 实现暴力反射,调用此方法后,可以操作非public修饰的构造方法。 | getModifiers() | int | 用于获取当前属性的修饰符(包括权限和特征), 每个修饰符都用一个整数来表示,详情看上面Class表的描述。 | getName() | String | 用于获取当前属性的属性名。 |
4>.Method类
Method类 用于描述类中的方法。
方法 | 返回值 | 解释说明 |
---|
invoke(Object obj, Object… args) | Object | 让某个实例对象obj,调用当前方法。 后面的args是执行此方法时需要的实参。 而此invoke方法的返回值,就是执行此方法后的实际返回值。 | getReturnType() | Class | 用于获取当前方法的返回值的数据类型(包名.类名)。 | getParameterTypes() | Class[] | 用于获取当前方法的形参表列的数据类型(包名.类名)。 | setAccessible(true) | void | 实现暴力反射,调用此方法后,可以操作非public修饰的方法。 | getAnnotation(Class annotationClass) | 泛型T | 用于获取当前方法上的某个特定注解。 | getExceptionTypes() | Class[] | 获取当前方法抛出异常的数据类型(包名.类名)。 | getModifiers() | int | 用于获取当前方法的修饰符(包括权限和特征), 每个修饰符都用一个整数来表示,详情看上面Class表的描述。 | getName() | String | 用于获取当前方法的方法名。 |
5>.Annotation类
Annotation类 用于描述类中的注解。
-
注解可以放置的位置: 类的上面、属性上面、构造方法上面、普通方法上面、参数前面 -
注解中可以携带的信息(信息不能随意写,信息的类型只能是如下的类型。):
- 基本数据类型(不可以是包装类)
- String类型
- 枚举类型enum
- 注解类型@
- 数组类型[](数组的内部需要是如上的四种类型)
案例:
- 自定义一个 MyAnnotation 注解类型:
import java.lang.annotation.*;
@Target({ElementType.FIELD,ElementType.CONSTRUCTOR,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnnotation {
public abstract int annotationMethod();
String[] value();
}
- 创建一个 MyClass 类,在类中使用自定义注解@MyAnnotation:
public class MyClass {
@MyAnnotation(annotationMethod = 1,value = "name")
private String name;
@MyAnnotation(annotationMethod = 2,value = {"test","Method"})
public void test(){
}
@MyAnnotation(annotationMethod = 3,value = {"print","Method"})
public void print(){
}
}
- 编写一个测试类TestAnnotation,用于获取MyClass 类中,自定义注解@MyAnnotation内的信息:
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
public class TestAnnotation {
public static void main(String[] args) throws Exception {
Class clazz = MyClass.class;
Method method = clazz.getDeclaredMethod("print");
Annotation annotation = method.getAnnotation(MyAnnotation.class);
String[] values = ((MyAnnotation)annotation).value();
System.out.println("------ MyClass 类中,print方法上注解中的内容-----");
for (String s:values){
System.out.print(s+"\t");
}
}
}
6>.Properties类
Properties类,用于读取配置文件。
Properties类继承于HashTable,类型上说,是一个map类型的集合。但实际作用是用来读取文件信息的 ,类似于一个流(高级流)。读取的文件后缀名是. properties,文件中的内容,需以 key = value 形式存在。
案例:
import java.io.FileReader;
import java.util.Enumeration;
import java.util.Properties;
public class TestProperties {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
properties.load(new FileReader("src/.../test.properties"));
Enumeration enumeration = properties.propertyNames();
String key;
String value;
while (enumeration.hasMoreElements()){
key = (String) enumeration.nextElement();
value = properties.getProperty(key);
System.out.println(key+" = "+value);
}
}
}
三、总结
操作属性:
1. 获取成员属性:
Field[] getFields() :
用于获取所有的public修饰的成员变量,也可以是父类中的 public属性。
Field getField(String name) :
用于获取指定名称的、 public修饰的成员变量,也可以是父类中的 public属性。
Field[] getDeclaredFields() :
用于获取当前类中的所有成员变量,不考虑修饰符。
但只能获取本来类中的属性,不可获取父类中的属性 。
Field getDeclaredField(String name):
用于获取指定名称的成员变量,不考虑修饰符。
但只能获取本来类中的属性,不可获取父类中的属性 。
成员变量 Field:
重要操作:
1. void set(Object obj, Object value):
赋值操作:调用此方法,可以给某个实例对象的当前属性赋具体值。
obj表示想要操作的实例对象,value是赋给当前属性的具体值。
2. Object get(Object obj):
取值操作:调用此方法,可以取出某个实例对象,当前属性的实际值。
3. void setAccessible(true):
实现暴力反射,调用此方法后,可以操作非public修饰的属性。
构造方法:
2. 获取构造方法:
Constructor<?>[] getConstructors():
用于获取所有的public修饰的构造方法。
Constructor<T> getConstructor(类<?>... parameterTypes):
用于获取当前类中某个public修饰的构造方法。
Class<?>是该构造方法的形参表列,这里需传入参数.class类型。
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes):
用于获取类中的某个构造方法,不考虑修饰符。
Class<?>是该构造方法的形参表列,这里需传入参数.class类型。
Constructor<?>[] getDeclaredConstructors():
用于获取类中的所有构造方法,不考虑修饰符。
构造方法Constructor:
1.T newInstance(Object... initargs):
用于创建一个当前类的实例对象,相当于调用了当前类相应的构造方法。
参数 initargs表示的是当前构造方法的形参表列。
如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法.
2.Class[] getParameterTypes():
用于获取当前方法的形参表列的数据类型(包名.类名)。
3.setAccessible(true):
实现暴力反射,调用此方法后,可以操作非public修饰的构造方法。
操作方法:
3.获取普通方法:
Method[] getMethods():
用于获取所有的public修饰的方法,也可以是父类中的 public方法。
Method getMethod(String name, Class<?>... parameterTypes):
用于获取指定名称的、 public修饰的类内方法,也可以是父类中的 public方法。
Class<?>是该方法的形参表列,这里需传入参数.class类型。
Method[] getDeclaredMethods():
获取当前类中的所有方法,不考虑修饰符。但只能获取本类中的方法,不可获取父类中的方法 。
Method getDeclaredMethod(String name, Class<?>... parameterTypes):
用于获取指定名称的方法,不考虑修饰符,但只能获取本类中的方法,不可获取父类中的方法 。
方法对象 Method:
重要操作:
1.Object invoke(Object obj, Object... args):
让某个实例对象obj,调用当前方法。
后面的args是执行此方法时需要的实参。而此invoke方法的返回值,就是执行此方法后的实际返回值。
2. Class getReturnType() :
用于获取当前方法的返回值的数据类型(包名.类名)。
3.Class[] getParameterTypes() :
用于获取当前方法的形参表列的数据类型(包名.类名)。
四、案例
demo1:通过反射创建对象
题目要求:
/**
* 利用反射技术,编写一个可以创建JavaBean对象的方法:
* 根据传入的一个String型的类全名,
* 返回一个对应的实例对象。
*/
代码实现:
- 实体类Person:
public class Person {
private String name;
private Integer age;
private String sex;
public Person() { }
public Person(String name, Integer age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
- 方法工具类MySpring:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Scanner;
public class MySpring {
public static Object getBean(String className){
Object obj = null;
Scanner input = new Scanner(System.in);
try {
Class clazz = Class.forName(className);
obj = clazz.newInstance();
Field[] fields = clazz.getDeclaredFields();
for (Field field:fields){
String fieldName = field.getName();
String firstName = fieldName.substring(0,1).toUpperCase();
String otherName = fieldName.substring(1);
Class fieldClass = field.getType();
StringBuilder setMethodName = new StringBuilder("set");
setMethodName.append(firstName);
setMethodName.append(otherName);
Method method = clazz.getMethod(setMethodName.toString(),fieldClass);
System.out.print("请输入"+ className+"类的"+fieldName+"属性的值:");
String fieldValue = input.nextLine();
Constructor fieldConstructor = fieldClass.getConstructor(String.class);
method.invoke(obj,fieldConstructor.newInstance(fieldValue));
}
} catch (Exception e) {
System.out.println("类创建失败,没有找到此类:"+className);
e.printStackTrace();
}
System.out.println("对象创建成功!!!");
return obj;
}
}
- 测试类TestMain:
public class TestMain {
public static void main(String[] args) {
Person person = (Person)MySpring.getBean("demo1.Person");
System.out.println(person);
}
}
demo2:通过注解创建对象
题目要求:
/**
* 编写一个可以创建JavaBean对象的方法:
* 根据传入的一个String型的类全名,
* 根据其无参构造方法上的自定义注解,返回一个对应的实例对象。
*/
代码实现:
- 自定义注解MyAnnotation:
import java.lang.annotation.*;
/**
* 如何自定义一个注解:
* 1.通过 @interface 定义一个注解类型.
*
* 2.发现写法与接口非常相似(可以利用接口的特点来记忆注解)
* 可以描述public static final的属性,但实际应用中较为少见
* 可以描述public abstract的方法,但方法的返回值不能是void,要求返回值必须是注解内可以携带信息的那些数据类型中的
*
* 3.我们自己定义的注解如果想要拿来使用
* 光定义是不够的,还需要做很多细致的说明(需要利用Java提供好的元注解来进行说明)
* 元注解:也是注解,不过不是拿来使用的,而是用来说明注解的。
* @Target: 描述当前这个自定义注解可以放置的位置
* @Retention: 描述当前的这个注解存在什么作用域中
* 作用域包括:源代码文件--->编译-->字节码文件--->类加载--->在内存中执行
* SOURCE CLASS RUNTIME
* @Inherited: 描述当前这个注解,能否被子类对象继承(添加表示能被子类继承)
*/
@Target({ElementType.FIELD,ElementType.CONSTRUCTOR,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnnotation {
// public static final String field = "LYQ";
// 接口内,方法的返回值可以为void。
// 但注解不可以,注解内的方法必须有返回值,
// 返回值的类型和注解内可以携带信息的那些数据类型相同
public abstract int annotationMethod();
String[] value();
}
- 实体类Person:
public class Person {
private String name;
private Integer age;
private String sex;
@MyAnnotation(annotationMethod = 1,value = {"李祎晴","20","女"})
public Person() { }
public Person(String name, Integer age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
- 方法工具类MySpring:
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class MySpring {
/**
* 设计一个方法,
* 通过一个String型的类全名,返回一个类对象,
* 并且将注解内的值赋给对象中的属性
*/
public static Object getBean(String className) throws Exception {
Object obj = null;
//1.通过反射获取对象
Class clazz = Class.forName(className);
//2.获取此对象的无参构造器
Constructor constructor = clazz.getConstructor();
//3.通过无参构造器创建一个无参的空对象
obj = constructor.newInstance();
//4.将注解内的值赋给对象中的属性
// 首先获取无参构造器上的注解对象
Annotation annotation = constructor.getAnnotation(MyAnnotation.class);
//方法1:
//直接获取注解对象内的信息
// String[] values = ((MyAnnotation)annotation).value();
//方法2:
// 利用反射实现:首先获得注解类对象
Class annClass = annotation.getClass();
//获取注解类对象内的指定方法
Method annClassMethod = annClass.getMethod("value");
//执行方法,获取里面的值
String[] values = (String[]) annClassMethod.invoke(annotation);
//5.获取类对象中的所有属性
Field[] fields = clazz.getDeclaredFields();
for (int i = 0;i<fields.length;i++){
//获取属性名
String fieldName = fields[i].getName();
//6.利用属性拼接set方法名:set + 属性名首字母变大写 + 属性名后面的所有字母
String setMethodName = "set"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
//获得属性类型
Class fieldType = fields[i].getType();
//7.通过拼接好的set方法名和参数类型,找到对应的set方法
Method setMethod = clazz.getMethod(setMethodName,fieldType);
//执行找到的set方法,需要将注解内String类型的值,转换成形参需要的参数类型
setMethod.invoke(obj,fieldType.getConstructor(String.class).newInstance(values[i]));
}
System.out.println("对象创建成功!");
return obj;
}
}
- 测试类TestMain:
public class TestMain {
public static void main(String[] args) {
Person person;
try {
person = (Person) MySpring.getBean("demo2.Person");
System.out.println(person);
} catch (Exception e) {
e.printStackTrace();
}
}
}
demo3:配置文件创建对象
题目要求:
/**
* 需求:写一个"框架",
* 在不改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
* 实现:
* 1. 配置文件
* 2. 反射
* 步骤:
* 1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
* 2. 在程序中加载读取配置文件
* 3. 使用反射技术来加载类文件进内存
* 4. 创建对象
* 5. 执行方法resources
*
* 注意:maven工程的配置文件要放在resources下
*/
代码实现:
- 实体类Person:
public class Person {
private String name = "李晴晴";
private Integer age;
public String sex ="男";
public Person() { }
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("Person.eat()方法!");
}
private void sleep(){
System.out.println("private的 Person.sleep()方法!");
}
public void study(String name){
System.out.println("Person.study()方法! "+name+" 正在学习");
}
public void study(String name,int age){
System.out.println("Person.study()方法! "+age+"岁的"+name+" 正在学习");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
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;
}
}
- 实体类Student:
public class Student{
public void eat() {
System.out.println("Student.eat()方法!");
}
}
- 配置文件 test.properties:
# demo3/TestUtil1 的测试样例样例:
className1 = demo3.Student
methodName1 = eat
# demo2/TestUtil2 的测试样例样例:
className2 = demo3.Person
methodName2 = study
arg2 = LYQ
- 第一个测试类TestUtil1:
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
public class TestUtil1 {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
ClassLoader classLoader = TestUtil1.class.getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("test.properties");
properties.load(inputStream);
String classname = properties.getProperty("className1");
System.out.println("className: "+classname);
String methodName = properties.getProperty("methodName1");
System.out.println("methodName: "+methodName);
Class clazz = Class.forName(classname);
Object object = clazz.newInstance();
Method method = clazz.getMethod(methodName);
System.out.println("\n\n-------- 执行方法 --------");
method.invoke(object);
}
}
5.第二个测试类TestUtil2:
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
public class TestUtil2 {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
ClassLoader classLoader = TestUtil2.class.getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("test.properties");
properties.load(inputStream);
String classname = properties.getProperty("className2");
System.out.println("className: "+classname);
String methodName = properties.getProperty("methodName2");
System.out.println("methodName: "+methodName);
String arg = properties.getProperty("arg2");
System.out.println("arg: "+arg);
Class clazz = Class.forName(classname);
Object object = clazz.newInstance();
String argss = "";
Method[] methods = clazz.getMethods();
for (Method method:methods){
if (method.getName().equals(methodName)){
Class[] methodClasses = method.getParameterTypes();
for (Class c:methodClasses){
argss = c.getName();
}
}
}
Method method = clazz.getMethod(methodName,Class.forName(argss));
System.out.println("\n\n-------- 执行方法 --------");
method.invoke(object,arg);
}
}
|