文章目录
- 一、反射机制概述
- 1、作用
- 2、相关类
- 二、反射
- 1、获取Class的三种方式
- 2、通过反射机制实例化对象
- 3、forName方法的另一个应用
- 4、获取类路径下文件的绝对路径
- 5、资源绑定器ResourceBundle
- 6、类加载器
- 三、反射与反编译
- 1、获取Field
- 2、反编译Field
- 3、通过反射机制访问对象的属性
- 4、可变长参数☀☀☀
- 5、反射与反编译Method
- 6、用反射机制调用对象的方法
- 7、反射与反编译Constructor
- 8、用反射机制调用构造方法
- 9、获取某类的父类以及实现了哪些接口
- 四、注解
- 1、概述
- 2、JDK的内置注解
- 3、注解中定义属性
- 4、反射注解
一、反射机制概述
1、作用
通过Java中的反射机制,可以操作字节码文件,即读和修改字节码文件.class
2、相关类
java.lang.reflect.*
细分为:
- java.lang.Class 代表字节码文件
- java.lang.reflect.Method 代表字节码文件中的方法字节码
- java.lang.reflect.Constructor 代表字节码文件中的构造方法字节码
- java.lang.reflect.Field 代表字节码文件中的属性字节码
二、反射
1、获取Class的三种方式
☀获取Class的第一种方式:
java.lang.Class类中的静态方法forName(),方法的参数是一个字符串,字符串需要的是一个完整的类名,即必须包含包名,如java.lang.String
try{
Class c1 = Class.forName("java.lang.String");
Class c = Class.forName("java.util.Date")
}catch(ClassNotFoundException e){
e.printStackTrace();
}
c1代表String.class文件=,另外ClassNotFoundException异常的父类是ReflectOperationException,再父就是Exception
☀获取Class的第二种方式:
Java中任何一个对象都有一个方法getClass()(即Object中的方法)
String s = "abc";
Class c2 = s.getClass();
//和第一中方式做对比
//true,即对象内存地址一样
System.out.println(c1 == c2);
字节码装载到JVM的时候,只装载一份
☀获取Class的第三种方式:
Java中任何一种类型,包括基本数据类型,都有.class属性
Class c3 = String.class;
Class d = Date.class;
Class i = int.class;
//true
System.out.println(c2 == c3);
2、通过反射机制实例化对象
Class c = Class.forName("java.util.Date");
Object obj = c.newInstance();
newInstance()方法已过时,会调用对应类的无参构造,完成对象的创建。若这时手写了有参而未加无参构造,则这时异常:java.lang.InstantiationException
以上单独看有些鸡肋,搭配properties配置文件则很灵活:
☀反射机制的灵活性
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Date;
import java.util.Properties;
public class ReflectTest {
public static void main(String[] args) {
try {
FileReader reader = new FileReader("thread/classInfo.properties");
Properties pro = new Properties();
pro.load(reader);
String className = pro.getProperty("className");
Class c = Class.forName(className);
Object o = c.newInstance();
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}catch (ClassNotFoundException e){
e.printStackTrace();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
注意:IDEA中的当前目录是项目的根目录
以上代码的核心部分是:
如此,创建、批量创建Java对象的时候,就不用改代码,只需修改配置文件就能实例化不同的对象。符合OCP原则,即对扩展开放,对修改关闭。
3、forName方法的另一个应用
若只希望某个类的静态代码块执行,不希望其他程序执行,则可用forName(“完整类名”),这个方法的执行会导致类加载
,而类加载的时候,静态代码块执行。
public class MyClass{
...
static{
System.out.println("静态代码块执行");
}
}
------
Class.forName("com.java.reflectMyclass");
4、获取类路径下文件的绝对路径
以上下代码使用相对路径(IDEA当前路径为project根路径),可移植性差。
FileReader reader = new FileReader("thread/classInfo.properties");
由此:
类路径:凡是在src下的都是类路径下,src是类的根路径
String path = Thread.currentThread().getContextClassLoader().getResource("classInfo.properties").getPath();
- getContextClassLoader()是线程对象的方法,获取当前线程的类加载器对象
- getResource()方法是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源,故该方法传参为src下的文件名字符串
由此,拿到了一个文件的绝对路径,最重要的是这是一个随移植环境而变得绝对路径
还可以直接以流得形式返回:
InputStream reader2 = Thread.currentThread().getContextClassLoader().getResourceAsStream("classInfo.properties");
5、资源绑定器ResourceBundle
资源绑定器用于获取属性配置文件中的内容,此时属性配置文件xx.properties必须放在类路径下。
ResourceBundle bundle = ResourceBundle.getBundle("classInfo");
String className = bundle.getString("className");
注意点:
- 资源绑定器只能绑定xx.properties文件,注意文件扩展名
- 这个文件必须在类路径下
- getBundle方法传参不写文件后缀名properties
6、类加载器
ClassLoader,专门负责加载类的一个命令/工具,JDK中自带了三个类加载器:
- 启动类加载器:专门加载…jdk\jre\lib\rt.jar
- 扩展类加载器:专门加载…jdk\lib\ext*.jar
- 应用类加载器:专门加载classpath中的jar包(class文件)
代码执行前,会将需要的类全部加载到JVM中,先通过启动类加载器,有没加载到,通过扩展类加载器,还没直到,到应用类加载器。
Java中,为了保证类加载的安全,使用了双亲委派机制,优先从启动类加载器中加载(父),父无法加载,再从扩展加载器中加载(母)。否则,若应用类加载器中也植入了一个String类,则出现了安全问题。
三、反射与反编译
1、获取Field
import java.lang.reflect.*;
public class ReflectTest2 {
public static void main(String[] args) {
try {
Class studentClass = Class.forName("Student");
Field[] fields = studentClass.getFields(); //获取类中的Field
System.out.println(fields.length);
System.out.println(fields[0]); //1
String fieldName = fields[0].getName(); //只有一个public修饰的no
Field[] fields1 = studentClass.getDeclaredFields(); //获取所有Field
for(Field field:fields1){
System.out.println("属性名:" + field.getName());
Class fieldType = field.getType(); //获取属性的类型
String typeStr = fieldType.getName();
//获取属性的修饰符,返回int,每个数字为修饰符的代号。修饰符可能有多个,如private finally
int i = field.getModifiers();
//类Modifier中有静态方法static String toString(int mod)可返回修饰符字符串
String modifiersStr = Modifier.toString(i);
System.out.println("修饰符为:" + modifiersStr);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class Student{
/**
* 定义4个Field,用不同的访问控制权限修饰符
*/
public int no;
private String name;
protected int age;
boolean sex;
}
运行结果:
另外,类Class中也有getName方法:
Class studentClass = Class.forName("Student");
//come.java.Student
String className = studentClass.getName();
//Student
String simpleName = studentClass.getSimpleName();
2、反编译Field
import java.lang.reflect.*;
import java.lang.reflect.Modifier;
public class ReflectTest3 {
public static void main(String[] args) {
try {
//创建StringBuilder用来拼接字符串
StringBuilder s = new StringBuilder();
Class studentClass = Class.forName("Student");
//下面就是从上往下边一点点拼接Student类的源码
s.append(Modifier.toString(studentClass.getModifiers()) + "class" + " " + studentClass.getSimpleName() + "{ \n");
Field[] fields = studentClass.getDeclaredFields();
//拿到每一个Field的修饰符、类型、变量名,再拼起来
for(Field field:fields){
s.append("\t");
String modifierStr = Modifier.toString(field.getModifiers());
String typeStr = field.getType().getName();
String fieldStr = field.getName();
s.append(modifierStr + " " + typeStr + " " + fieldStr + ";");
s.append("\n");
}
s.append("}");
System.out.println(s);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
//最好直接append,创建这么多字符串浪费空间
运行结果:
靠,有bug,获取类型用getSimpleName():
String typeStr = field.getType().getSimpleName();
正常了:
3、通过反射机制访问对象的属性
Class studentClass = Class.forName("Student");
Object obj = studentClass.newInstance();
//getDeclaredFields()拿到的是一个Field集合,getDeclaredField(属性名)拿到的是Field
Field noField = studentClass.getDeclaredField("no");
noField.set(obj,9527);
System.out.println("noField的值:"+ noField.get(obj));
//访问私有属性会报错
Field nameField = studentClass.getDeclaredField("name");
nameField.set(obj,"llg");
System.out.println("私有属性name值:"+ nameField.get(obj));
运行报错:java.lang.IllegalAccessException
这时可以通过setAccessible(true)方法打破封装,但这样也同时带来了安全问题
Field nameField = studentClass.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(obj,"llg");
System.out.println("私有属性name值:"+ nameField.get(obj));
4、可变长参数☀☀☀
语法:
类型…(三个点)
举例:
public static void m(int...args){
System.out.println("doSome!");
}
------------
调用时:
m();
m(10);
m(10,20,30);
注意:可变长度的参数必须在列表的最后一个
args有length属性,说明它可以当作一个数组来看待
public static void m(int...args){
for(int i:args){
System.out.println(i);
}
}
----
调用时也可以传一个数组进去:
int[] num = {1,2,3,4};
m(num);
可变长参数的语法总结:
- 可变长参数要求参数的个数是0~N个
- 可变长参数在参数列表只能出现在最后一个位置,也即只能有一个可变长度参数
- 可变长度参数可以当作一个数组看待
5、反射与反编译Method
/**
* 用户业务类
*/
public class UserService {
public boolean login(String name,String password){
if("admin".equals(name) && "123qweASD".equals(password)){
return true;
}else{
return false;
}
}
public void logout(){
System.out.println("用户退出登录!");
}
}
反射Method:
Class userServiceClass = Class.forName("UserService");
Method[] methods = userServiceClass.getDeclaredMethods();
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());
}
}
运行结果:
反编译Method如下:
/**
* 反编译
*/
class Decompile{
public static void main(String[] args) throws ClassNotFoundException {
StringBuilder s = new StringBuilder();
Class userServiceClass = Class.forName("UserService");
s.append(Modifier.toString(userServiceClass.getModifiers()) + " " + "class" + " "+ userServiceClass.getSimpleName() + "{");
s.append("\n");
Method[] methods = userServiceClass.getMethods();
for(Method method:methods){
//频繁创建字符串会占用很多空间,我直接append了
s.append("\t");
s.append(Modifier.toString(method.getModifiers()));
s.append(" ");
s.append(method.getReturnType().getSimpleName());
s.append(" ");
s.append(method.getName());
s.append("(");
Class[] parameterTypes = method.getParameterTypes();
for(Class parameterType:parameterTypes){
s.append(parameterType.getSimpleName());
s.append(",");
}
//修复bug:对于有形参的,删掉上面加的最后多余的逗号
if(parameterTypes.length != 0){
s.deleteCharAt(s.length()-1);
}
s.append("){}\n");
}
s.append("}");
System.out.println(s);
}
}
运行效果:
这玩意很难一次完美反编译出来,运行一下哪里有bug再调代码就好。
6、用反射机制调用对象的方法
Class userServiceClass = Class.forName("UserService");
Object obj = userServiceClass.newInstance();
//注意传参,即区分一个方法是靠方法名+参数
Method loginMethod = userServiceClass.getDeclaredMethod("login",String.class,String.class);
/**
* invoke方法
* loginMethod是要调用的方法的Method对象
* obj是调用方法的对象
* 后面是传入的实参列表
*/
Object retValue = loginMethod.invoke(obj,"admin","123qweASD");
System.out.println(retValue); //true
反射机制+配置文件 = 灵活性,体现的OPC原则,对扩展开放,对修改关闭
7、反射与反编译Constructor
先写个素材类:
反射就不写了,一些方法直接在反编译中体现:
/**
* 反编译
*/
class DecompileVip{
public static void main(String[] args) throws Exception {
StringBuilder s = new StringBuilder();
Class vipClass = Class.forName("Vip");
s.append(Modifier.toString(vipClass.getModifiers()) + " " + "class" + vipClass.getSimpleName() +"{");
Constructor[] constructors = vipClass.getDeclaredConstructors();
for(Constructor constructor:constructors){
s.append("\n");
s.append("\t");
s.append(Modifier.toString(constructor.getModifiers()));
s.append(" ");
s.append(vipClass.getSimpleName()); //构造方法名即类名
s.append("(");
Class[] parameterTypes = constructor.getParameterTypes();
for(Class parameterType:parameterTypes){
s.append(parameterType.getSimpleName());
s.append(",");
}
//修复上面引入的bug,删除最后一个多余的,逗号
if(parameterTypes.length != 0){
s.deleteCharAt(s.length()-1);
}
s.append("){" + "\n" + "\t" + "}");
s.append("\n");
}
s.append("}");
System.out.println(s);
}
}
运行效果:
8、用反射机制调用构造方法
Class VipClass = Class.forName("Vip");
Constructor constructor = VipClass.getDeclaredConstructor(int.class,String.class,boolean.class);
//这种调用无参构造的方式已过时
Object obj = VipClass.newInstance();
/**
* 调用有参构造来new对象
*/
Object obj2 = constructor.newInstance(110,"llg",true);
/**
* 调用无参构造
*/
Constructor constructor1 = VipClass.getDeclaredConstructor();
Object obj3 = constructor1.newInstance();
9、获取某类的父类以及实现了哪些接口
Class stringClass = Class.forName("java.lang.String");
Class superClass = stringClass.getSuperclass(); //获取父类
System.out.println(superClass.getSimpleName());
Class[] interfaces = stringClass.getInterfaces(); //获取这个类实现的接口
for(Class api:interfaces){
System.out.println("String类实现的接口有:" + api);
}
运行结果:
四、注解
1、概述
- 注释,注解,Annotation
- 注解Annotation是一种引用数据类型,编译后也生成xx.class文件
- 定义的语法格式为:
[修饰符列表] @ interface 注释类型名{
}
- 注解的使用语法是:@注解类型名
- 注解可以出现在类上、属性上、方法上、变量上等,还可以出现在注解类型上,默认情况下,注解可以出现在任意位置
2、JDK的内置注解
❀ java.lang包下的注解
@Override 表示一个方法声明打算重写超类中的另一个方法声明,源码:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
这是一个标识性注解,给编译器做参考的,和运行阶段无关,编译器看到这个注解,就会检查这个方法是否重写了父类的方法,若不是,则报错。故@Override只能注解方法
❀元注解
用来标注注解类型的注解,称元注解,常见的元注解有Target和Retention。
- Target用来标注“被标注的注解”可以出现在哪些位置,如@Target(ElementType.METHOD)即被标注的注解只能出现在方法中,
- Retention用来标注“被标注的注解”最终保存在哪里,如:
@Retention(RetentionPolicy.SOURCE) 表示该注解只能保留在Java源文件中
@Retention(RetentionPolicy.CLASS) 表示该注解只能保留在class文件中
@Retention(RetentionPolicy.RUNTIME) 表示该注解只能保留在class文件中,且可以被反射机制所读取
源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
-------
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
❀表示过时的注解@Deprecated
方法或类等元素已过时的时候,可以用@Deprecated,从而传达给别人,已过时,当前有更好的解决方案。
源码解析:
3、注解中定义属性
public @interface MyAnnotation {
//带括号,看着像方法,但这是MyAnnotation的一个属性
String name();
//这是一个有默认值的属性,使用注解时可以不用赋值
int age() default 22;
}
使用时:
@MyAnnotation(name="llg")
public static void doSome(){
}
当注解中的属性只有一个的时候,使用注解就可以不用加属性名了
public @interface MyAnnotation {
String value();
}
使用时:
@MyAnnotation("code9527")
...
注解中的属性的类型可以是:byte、short、int、long、float、double、char、String、Class、枚举类型以及以上每一种类型对应的数组形式,如:int[ ] value
public @interface OtherAnnotation {
int age();
String[] email();
Hobby[] hobbyArray();
}
-------------
public enum Hobby {
RAP,SING,RUN
}
-------------
@OtherAnnotation(age = 22,email = {"code@qq.com","9527@qq.com"},hobbyArray = {Hobby.SING,Hobby.RAP}) //若数组中只有一个元素,{}可省略
public static void doSome(){
}
4、反射注解
Class c = Class.forName("Test7");
//判断该类上是否有注解”OtherAnnotation“
System.out.println(c.isAnnotationPresent(OtherAnnotation.class));
if(c.isAnnotationPresent(OtherAnnotation.class)){
//获取注解对象
//getAnnotation方法返回的是Annotation类型,这里强转以匹配前面我写的变量类型
OtherAnnotation otherAnnotation = (OtherAnnotation) c.getAnnotation(OtherAnnotation.class);
int ageValue = otherAnnotation.age();
}
注意注解反射的前提是标记了@Retention(RetentionPolicy.RUNTIME)
整理完了,完结撒花!!! 2022-12-10 17:08