十. 反射机制
-
反射机制可以操作字节码文件(读/修改字节码文件)
-
反射机制相关的类:
java.lang.Class:代表整个字节码/整个类
java.lang.reflect.Method:代表方法字节码/类中的方法
java.lang.reflect.Constructor:代表构造方法字节码/类中的构造方法
java.lang.reflect.Field:代表属性字节码/类中的成员变量
public class User{ //Class
int no; //Field
Public User(){} //Constructor
public void setNo(int no){ //Method
this.no = no;
}
}
- 获取一个类的字节码有三种方式:
- Class.forName()方法:静态方法,参数是一个完整类名(必须带有包名)的字符串
Class c1 = null;
Class c2 = null;
try {
//c1代表String.class文件,代表String类型
c1 = Class.forName("java.lang.String");
c2 = Class.forName("java.util.Date"); //c2代表Date类型
Class c3 = Class.forName("java.lang.Integer"); //c3代表Integer类型
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
- java中任何一个对象都有一个方法:getClass()
String s = "abc";
Class x = s.getClass();
System.out.println(x == c1); //true
Date d = new Date();
Class y = d.getClass();
System.out.println(y == c2); //true
- java中任何类型(包括基本数据类型),都有.class属性
Class z = String.class;
Class k = Date.class;
Class f = int.class;
System.out.println(x == z); //true
-
通过Class的newInstance()方法可以实例化对象
newInstance()方法内部调用无参构造,必须保证无参构造存在
package bean;
public class User {
public User() { //无参构造
System.out.println("hello world");
}
}
public class ReflectTest02 {
public static void main(String[] args) {
try {
Class c = Class.forName("bean.User"); //获取Class
Object obj = c.newInstance(); //调用User类的无参构造,输出hello world
System.out.println(obj); //输出obj内存地址bean.User@1b6d3586
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) { //如果User类只写有参构造,报错
e.printStackTrace();
}
}
}
- 反射机制具有灵活性:不修改源码,可以做到不同对象的实例化(符合OCP开闭原则,高级框架底层使用反射机制)
User user = new User(); //这种方式就写死了,只能创建User对象
FileReader reader = new FileReader("VideoLearn/src/reflect/classinfo.properties");
Properties pro = new Properties();
pro.load(reader);
reader.close();
String className = pro.getProperty("className");
System.out.println(className);
Class c = Class.forName(className);
Object obj = c.newInstance();
System.out.println(obj);
//classinfo.properties文件内容:
className=bean.User //创建不同对象时只需要修改此文件内容,源代码不用动
-
Class.forName()方法会导致类加载
如果只希望某个类的静态代码块执行,其他代码不执行,可以使用这种方法
public class ReflectTest04 {
public static void main(String[] args) {
try {
Class.forName("reflect.User"); //输出“静态代码块执行”
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User{
static {
System.out.println("静态代码块执行");
}
}
- 获取在类路径下(src下的都是类路径下,src是类的根路径)文件的绝对路径
String path = Thread.currentThread() //获取当前线程
.getContextClassLoader() //线程对象的方法,获取当前线程类加载器对象
//类加载器的方法,当前线程的类加载器默认从类的根路径下加载资源
.getResource("reflect/classinfo.properties")
.getPath();
//输出/D:/javase/out/production/javase/reflect/classinfo.properties
System.out.println(path);
InputStream reader = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("reflect/classinfo.properties"); //以流的形式返回
Properties pro = new Properties();
pro.load(reader);
reader.close();
String className = pro.getProperty("className");
System.out.println(className);
-
java.util包下提供的资源绑定器
只能获取属性配置文件(xxx.properties)的内容,且必须在类路径下
//扩展名.properties不写
ResourceBundle bundle = ResourceBundle.getBundle("reflect/classinfo");
String className = bundle.getString("className");
System.out.println(className);
-
关于JDK中自带的类加载器:
-
类加载器(ClassLoader)是专门负责加载类的命令/工具
-
JDK中自带了三个类加载器:启动类加载器、扩展类加载器、应用类加载器
-
假设有代码:String s = “abc”;
代码在执行前会将所需要的类加载到JVM中,类加载器会找String.class文件
首先通过“启动类加载器”加载,启动类加载器专门加载rt.jar,rt.jar是JDK最核心的类库
当“启动类加载器”加载不到时,会通过“扩展类加载器”加载,扩展类加载器加载ext\*jar
如果“扩展类加载器”没有加载到,会通过”应用类加载器“加载,应用类加载器专门加载classpath中的类
-
双亲委派机制:保证类加载的安全
优先从启动类加载器中加载,称为“父”。如果无法加载到,再从扩展类加载器中加载,称为“母”。如果都加载不到,才会从应用类加载器中加载
-
-
反射属性Field
-
反射Field(了解)
public class Student {
public int no; //一行是一个Field对象
private String name;
protected int age;
boolean sex;
}
public class ReflectTest06 {
public static void main(String[] args) throws Exception{
Class studentClass = Class.forName("bean.Student");
String className = studentClass.getName(); //完整类名
System.out.println(className); //bean.Student
String simpleName = studentClass.getSimpleName(); //简易类名
System.out.println(simpleName); //student
Field[] fields = studentClass.getFields();
System.out.println(fields.length); //1
Field f = fields[0];
System.out.println(f.getName()); //no
Field[] fs = studentClass.getDeclaredFields();
System.out.println(fs.length); //4
for (Field field: fs) {
int i = field.getModifiers(); //获取属性的修饰符列表,返回修饰符代号
System.out.println(i);
String modifierString = Modifier.toString(i); //代号转换为字符串
System.out.println(modifierString);
Class fieldType = field.getType();
String fieldName = fieldType.getName(); //获取属性类型
String fName = fieldType.getSimpleName(); //简易属性类型
System.out.println(fName);
System.out.println(field.getName());
}
}
}
- 反编译Field(了解)
public static void main(String[] args) throws Exception{
Class studentClass = Class.forName("bean.Student");
Field[] fields = studentClass.getDeclaredFields();
StringBuilder s = new StringBuilder(); //拼接字符串
s.append(Modifier.toString(studentClass.getModifiers()));
s.append(" class" + studentClass.getSimpleName() + "{\n");
for (Field field : fields) {
s.append("\t"); //制表符
s.append(Modifier.toString(field.getModifiers()));
s.append(" " + field.getType().getSimpleName());
s.append(" " + field.getName() + ";\n");
}
s.append("}");
System.out.println(s);
}
- 通过反射机制给属性赋值set/获取属性的值get(掌握)
Class studentClass = Class.forName("bean.Student");
Object obj = studentClass.newInstance(); //无参构造创建对象
Field noField = studentClass.getDeclaredField("no"); //获取no属性
noField.set(obj, 20); //赋值
System.out.println(noField.get(obj)); //读取
//访问私有属性
Field nameField = studentClass.getDeclaredField("name");
//反射机制的缺点:打破封装,可能导致不安全
nameField.setAccessible(true); //打破封装,在外部也可以访问private
nameField.set(obj, "heisenberg");
System.out.println(nameField.get(obj));
-
反射方法Method
-
可变长度参数语法:int… args(一定是三个点)
- 可变长度参数要求的参数个数是0~n个
- 可变长度参数在参数列表中必须在最后一个位置上,且可变长度参数只能有1个
- 可变长度参数可以当做数组看待
public static void main(String[] args) {
m();
m(10);
m(10, 20);
m3(12, 34, 56, 78);
m3(new int[]{9, 8, 7, 6});
}
public static void m(int... args1){
System.out.println("m方法执行");
}
//public static void m2(String... args1, int... args2){} //编译报错
public static void m2(String s, int... args1){} //编译通过
public static void m3(int... args1){
for (int i = 0; i < args1.length; i++) {
System.out.println(args1[i]);
}
}
- 反射Method(了解)
public class UserService {
public boolean login(String name, String pwd){
if ("heisenberg".equals(name) && "123".equals(pwd)){
return true;
}return false;
}
public void logout(){
System.out.println("系统已退出");
}
}
Class userServiceClass = Class.forName("bean.UserService");
Method[] methods = userServiceClass.getDeclaredMethods();
//System.out.println(methods.length); //输出2
for (Method method: methods) {
//获取修饰符列表
System.out.println(Modifier.toString(method.getModifiers()));
//获取返回值类型
System.out.println(method.getReturnType().getSimpleName());
//获取方法名
System.out.println(method.getName());
//方法的修饰符列表
Class[] parameterTypes = method.getParameterTypes();
for (Class parameterType: parameterTypes){
System.out.println(parameterType.getSimpleName());
}
}
- 使用反射机制调用方法(重点)
Class userServiceClass = Class.forName("bean.UserService");
Object obj = userServiceClass.newInstance(); //创建对象
Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class); //获取Method
//invoke方法调用login方法,返回Object类型数据
Object returnValue = loginMethod.invoke(obj, "heisenberg", "123");
System.out.println(returnValue); //true
public class Student {
public Student() {}
public Student(int no) {
this.no = no;
}//重写toString方法
}
public static void main(String[] args) throws Exception{
Class c = Class.forName("bean.Student");
Constructor con = c.getDeclaredConstructor(); //获取无参构造方法
Object obj = con.newInstance(); //调用无参构造
System.out.println(obj);
Constructor con2 = c.getDeclaredConstructor(int.class); //获取有参构造
Object obj2 = con2.newInstance(20); //调用有参构造
System.out.println(obj2);
}
- 获取一个类的父类,获取实现的接口
Class c = Class.forName("java.lang.String");
Class superClass = c.getSuperclass(); //获取父类
System.out.println(superClass.getName());
Class[] interfaces = c.getInterfaces(); //获取实现接口
for (Class in: interfaces){
System.out.println(in.getName());
}
-
注解
-
注解又叫注释类型,Annotation
-
注解是一种引用数据类型,编译后也生成xxx.class文件
-
注解语法格式:[修饰符列表] @interface 注解类型名{}
-
注解使用时语法格式:@注解类型名
-
注解可以出现类上、属性上、方法上、变量上、注解类型上等
-
java.lang包下的注解:
@Override:标识性注解,只能注解方法。给编译器作参考(凡带有此注解的方法必须是重写父类的方法,否则编译报错),和运行阶段无关
@Deprecated:表示标记的内容已过时
-
元注解:用来标注“注解类型”的注解
常见元注解:Target:用来标注“被标注的注解”可以出现在哪些位置上
Retention:用来标注“被标注的注解”最终保存在哪里
-
//Override源代码:
@Target(ElementType.METHOD) //表示只能出现在方法上
@Retention(RetentionPolicy.SOURCE) //表示该注解只被保留在java源文件中
public @interface Override {
}
//Retention(RetentionPolicy.CLASS):表示该注解被保留在class文件中
//Retention(RetentionPolicy.RUNTIME):表示该注解被保留在class文件中,且能被反射机制读取
-
注解中定义属性
给属性赋值语法:@MyAnnotation(属性名=属性值,属性名=属性值,…)
public @interface MyAnnotation {
String name(); //属性
String color();
int age() default 25; //设置默认值25
}
public class MyAnnotationTest {
//@MyAnnotation //编译报错:注解中有属性时,必须给属性赋值
@MyAnnotation(name = "heisenberg", color = "red") //编译通过,有默认值的属性可以不赋值
public void doSome(){}
}
- 如果注解的属性名是value,且只有一个属性,赋值时属性名可以忽略
public @interface MyAnnotation {
String value();
}
public class MyAnnotationTest {
@MyAnnotation("5") //可以省略
public void doSome(){}
}
public @interface MyAnnotation {
String[] value(); //属性是String数组
}
public class MyAnnotationTest {
@MyAnnotation({"5", "20"}) //数组也可以省略
public void doSome(){}
}
public @interface MyAnnotation {
String value();
int no();
}
public class MyAnnotationTest {
@MyAnnotation("5", no = 20) //编译报错
public void doSome(){}
}
public @interface MyAnnotation {
String value();
int no() default 20;
}
public class MyAnnotationTest {
@MyAnnotation("5") //编译通过
public void doSome(){}
}
- 注解中属性类型可以是8种基本数据类型、String、Class、枚举类型,以及它们的数组形式
public enum Season {
SPRING,SUMMER,AUTUMN,WINTER
}
public @interface MyAnnotation {
Season value1(); //枚举类型
Class value2(); //Class类型
}
public class MyAnnotationTest {
@MyAnnotation(value1 = Season.SUMMER, value2 = String.class) //注意枚举写法
public void doSome(){}
}
- 属性是数组时,如果数组中只有一个元素,大括号可以省略
public @interface MyAnnotation {
Season[] value1();
}
public class MyAnnotationTest {
//@MyAnnotation(value1 = {Season.SUMMER, Season.SPRING}) //正常写法
@MyAnnotation(value1 = Season.SUMMER) //编译通过,{}可以省略
public void doSome(){}
}
- 反射注解
//该注解只能标注类、方法
@Target({ElementType.TYPE, ElementType.METHOD})
//该注解可以被反射
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "Beijing";
}
@MyAnnotation()
public class MyAnnotationTest {
public static void main(String[] args) throws Exception{
Class c = Class.forName("annotation.MyAnnotationTest");
//判断类上是否有@MyAnnotation
System.out.println(c.isAnnotationPresent(MyAnnotation.class)); //true
if (c.isAnnotationPresent(MyAnnotation.class)){
MyAnnotation ma = (MyAnnotation) c.getAnnotation(MyAnnotation.class); //获取注解对象、类型转换
System.out.println(ma);
System.out.println(ma.value()); //获取对象属性,输出Beijing
}
Class s = Class.forName("java.lang.String");
System.out.println(s.isAnnotationPresent(MyAnnotation.class)); //false
}
}