Java注解反射复习笔记
看完狂神的注解反射教程后为了避免遗忘写的复习笔记
原视频链接
注解
基础概念
- 注解的作用
- 不是程序本身,可以对程序作出解释
- 可以被其他程序(e.g.编译器)读取
- 格式
@ + 注释名
,还可以添加参数值- e.g.
@SupressWarnings(value = "unchecked")
- 使用范围
- 附加在package, class, method, field等上面,相当于添加了额外的辅助信息
内置注解
@Override
等
元注解
- 元注解的作用是辅助其他注解,Java定义了4个meta-annotation类型
- 这些元注解可以在
java.lang.annotation
包中找到- @Target:用于描述注解的使用范围;
- @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(SOURCE < CLASS < RUNTIME);
- @Documented:说明该注解将被包含在javadoc中;
- @Inherited:说明子类可以继承父类中的该注解。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 四大元注解
* @Target 用于描述注解的使用范围
* @Retention 表示需要在什么级别保存该注释信息,用于描述注解的生命周期(SOURCE < CLASS < RUNTIME)
* @Documented 说明该注解将被包含在javadoc中
* @Inherited 说明子类可以继承父类中的该注解
*/
// 定义一个注解
// target表示我们的注解可以运用在哪些地方
// retention表示我们的注解在什么地方还有效 runtime > class > source
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME) // 如果注解只有一个参数,建议使用value命名,并且只有value有省略写法
@interface MyAnnotation {}
public class Test02 {
@MyAnnotation
public static void test01() {
System.out.println("Hello world!");
}
}
自定义注解
- 使用@interface自定义注解,自动继承了java.lang.annotation.Annotation接口
- 使用@interface来声明注解,格式
public @interface 注解名{定义内容}
- 其中的每一个方法实际上是声明了一个配置参数
- 方法的名称就是参数的名称
- 返回值类型就是参数的类型(只能是基本类型,Class, String, Enum)
- 可以通过default来声明参数的默认值
- 如果只有一个参数成员,一般参数名为value
- 注解元素必需要有值,经常使用空字符串,0作为默认值
- 使用@interface来声明注解,格式
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2 {
// 注解的参数 : 参数类型 + 参数名();
// 注解通过default设置默认值,也可以不设置
String name() default "";
int age();
int id() default -1; // 如果默认值为-1,代表不存在
String[] schools() default {"Hello", "World"};
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3 {
// 如果注解只有一个参数,建议使用value命名,并且只有value有省略写法
String value();
}
public class Test03 {
@MyAnnotation2(name = "A", age = 20)
public void test() {}
@MyAnnotation3("Lorain")
public void test2() {}
}
反射
基础概念
反射机制可以让Java具有一定的动态性,变成一个“准动态语言”。
-
反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并且可以直接操作任意对象的内部属性及方法;
Class clazz = Class.forname("java.lang.String")
-
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,所以称为“反射”。
-
反射提供的功能
-
在运行时判断一个对象所属的类
-
在运行时构造任意一个类的对象
-
在运行时判断任意一个类所具有的成员变量和方法
-
运行时获取泛型信息
-
在运行时调用任意一个对象的成员变量和方法
-
运行时处理注解
-
生成动态代理
…
-
-
反射的优点和缺点
-
优点
可以实现动态创建对象和编译,体现出很大的灵活性
-
缺点
对性能有影响。使用发射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么它满足我们的要求。这类操作总是慢于直接执行相同的操作
-
-
反射相关的主要API
-
Class类
-
在Object类中定义了以下的方法,此方法将被所有类继承
public final Class getClass()
- 以上的方法返回值的类型是一个Class类,此类是反射的源头。
-
- 一个Class对象包含了特定的某个结构(class/interface/enum/annotation/primitive type/void[])的有关信息
- Class本身也是一个类
- Class对象只能由系统建立对象(我们只是获取)
- 一个加载的类在JVM中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个Class实例所生成的
- 通过Class可以完整的得到一个类中的所有被加载的结构
- Class类是Reflection的根源,针对任何你想动态加载,运行的类,唯有先获得相应的Class对象
Class类常用方法
获取Class类的实例的方法
class Person {
String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
class Student extends Person {
public Student() {
this.name = "student";
}
}
class Teacher extends Person {
public Teacher() {
this.name = "teacher";
}
}
/**
* 测试Class类实例(对象)的创建方式
*/
public class Test02 {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Student();
// 1. 通过对象获得
Class clazz1 = person.getClass();
// 2. 通过Class类静态方法forName获得
Class clazz2 = Class.forName("com.lorain.reflection.Student");
// 3. 通过类名.class获得(推荐)
Class clazz3 = Student.class;
// 4. 基本内置类型的包装类的TYPE属性
Class clazz4 = Integer.TYPE;
// 5. 获得父类类型
Class clazz5 = clazz1.getSuperclass();
System.out.println(clazz1);
System.out.println(clazz2);
System.out.println(clazz3);
System.out.println(clazz5);
System.out.println(clazz1.hashCode());
System.out.println(clazz2.hashCode());
System.out.println(clazz3.hashCode());
}
}
具有Class对象的类型
- class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- interface:接口
- []:数组
- enum:枚举
- annotation:注解
- primitive type:基本数据类型
- void
import java.lang.annotation.ElementType;
/**
* 所有类型的Class
*/
public class Test03 {
public static void main(String[] args) {
Class clazz1 = Object.class; // 类
Class clazz2 = Runnable.class; // 接口
Class clazz3 = String[].class; // 一维数组
Class clazz4 = int[][].class; // 二维数组
Class clazz5 = Override.class; // 注解
Class clazz6 = ElementType.class; // 枚举
Class clazz7 = Integer.class; // 基本数据类型
Class clazz8 = void.class; // void
Class clazz9 = Class.class; // Class
System.out.println(clazz1);
System.out.println(clazz2);
System.out.println(clazz3);
System.out.println(clazz4);
System.out.println(clazz5);
System.out.println(clazz6);
System.out.println(clazz7);
System.out.println(clazz8);
System.out.println(clazz9);
}
}
类内存加载分析
Java内存分析
类的加载过程
类的加载与ClassLoader的理解
-
加载:
将class字节码文件加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象
-
链接:
将Java类的二进制代码合并到JVM的运行状态(JRE)之中的过程
- 验证:确保加载的类的信息符合JVM规范,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都在方法区中进行分配
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
-
初始化:
- 执行类构造器
<clinit>()
方法的过程。类构造器<clinit>()
方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息,不是构造该类对象的构造器) - 当初始化一个类的时候,如果发现其父类还没初始化,则需要先触发其父类的初始化
- JVM会保证一个类的
<clinit>()
方法在多线程环境中被正确加锁和同步
- 执行类构造器
/**
* 1.加载到内存,会产生一个类对应Class对象
* 2.链接,链接结束后 m = 0
* 3.初始化:
* <clinit>() {
* System.out.println("A类静态代码块初始化...");
* m = 500;
* m = 100;
* }
* m = 100
*/
class A {
// 0 -> 500 -> 100
// 交换顺序则输出500,顺序执行 0 -> 100 -> 500
static {
System.out.println("A类静态代码块初始化...");
m = 500;
}
static int m = 100;
public A() {
System.out.println("A类无参构造初始化...");
}
}
public class Test05 {
public static void main(String[] args) {
A a = new A();
System.out.println(A.m);
}
}
什么时候会发生类初始化?
- 类的主动引用(一定会发生类的初始化)
- 当JVM启动时,先初始化main方法所在的类
- new一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
- 类的被动引用
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
- 通过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
class Father {
static int b = 2;
static {
System.out.println("父类被加载");
}
}
class Son extends Father {
static {
System.out.println("子类被加载");
m = 300;
}
static int m = 100;
static final int M = 1;
}
/**
* 测试类什么时候会初始化
*/
public class Test05 {
static {
System.out.println("main类被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
// 1.主动引用
// Son son = new Son();
// 反射也会产生主动引用
// Class.forName("com.lorain.reflection.Son");
// 不会产生类的引用的方法
// System.out.println(Son.b);
// Son[] arr = new Son[5];
System.out.println(Son.M);
}
}
类加载器的作用
-
类加载的作用:
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口
-
类缓存:
标准的JavaSE类加载器可以按照要求查找类,但一旦某个类被加载到类加载器中,他将维持加载(缓存)一段时间。不过JVM的GC可以回收这些Class对象
类加载器的作用是用来把类(class)装载进内存的。JVM规范定义了如下类型的类加载器:
public class Test06 {
public static void main(String[] args) throws ClassNotFoundException {
// 获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
// 获取系统类加载器的父类加载器-->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);
// 获取扩展类加载器的父类加载器-->根加载器C/C++编写,不能直接获得
ClassLoader grand = parent.getParent();
System.out.println(grand);
// 测试当前类是哪个加载器加载的
ClassLoader classLoader1 = Class.forName("com.lorain.reflection.Test06").getClassLoader();
System.out.println(classLoader1);
// 测试JDK内置的类是谁加载的
classLoader1 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader1);
// 如何获取系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));
// 双亲委派机制:Java在运行自定义的类的时候会逐层向上查找,确保没有重名的,如果有重名的,则会使用Java本身的覆盖自定义的
}
}
获取类的运行时结构
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test07 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class clazz = Class.forName("com.lorain.reflection.User");
// 获取类的名字
System.out.println(clazz.getName()); // 包名 + 类名
System.out.println(clazz.getSimpleName()); // 类名
System.out.println("-----------------------------------");
// 获取类的属性
Field[] fields = clazz.getFields(); // 只能找到public属性
for (Field field : fields) {
System.out.println(field);
}
fields = clazz.getDeclaredFields(); // 可以找到全部属性
for (Field field : fields) {
System.out.println(field);
}
// 获得指定属性的值
Field name = clazz.getDeclaredField("name");
System.out.println(name);
System.out.println("-----------------------------------");
// 获得类的方法
Method[] methods = clazz.getMethods(); // 获得本类及其父类的所有public方法
for (Method method : methods) {
System.out.println("【getMethods()】" + method);
}
methods = clazz.getDeclaredMethods(); // 获得本类的所有方法
for (Method method : methods) {
System.out.println("【getDeclaredMethods()】" + method);
}
// 获得指定方法
Method getName = clazz.getMethod("getName", null);
Method setName = clazz.getMethod("setName", String.class);
System.out.println(getName);
System.out.println(setName);
System.out.println("-----------------------------------");
// 获得指定构造器
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
constructors = clazz.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println("#" + constructor);
}
// 获得指定的构造器
Constructor constructor = clazz.getConstructor(String.class, int.class, int.class);
System.out.println("$" + constructor);
}
}
动态创建对象执行方法
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test08 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
// 获得Class对象
Class clazz = Class.forName("com.lorain.reflection.User");
// 构造一个对象
User user0 = (User) clazz.newInstance(); // 本质上是调用了类的无参构造器
System.out.println(user0);
// 通过构造器创建对象
Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class, int.class);
User user1 = (User) constructor.newInstance("binary", 013, 18);
System.out.println(user1);
// 通过反射调用普通方法
User user2 = (User) clazz.newInstance();
// 通过反射获取一个方法
Method setName = clazz.getDeclaredMethod("setName", String.class);
// invoke : 激活的意思
// invoke(对象, 方法的参数)
setName.invoke(user2, "test");
System.out.println(user2.getName());
// 通过反射操作属性
User user3 = (User) clazz.newInstance();
Field name = clazz.getDeclaredField("name");
// 不能直接操作私有属性,我们需要关闭程序的安全检测,属性或者方法的setAccessible(true)
name.setAccessible(true);
name.set(user3, "test2");
System.out.println(user3.getName());
}
}
- 性能对比
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test09 {
// 普通方式调用(4ms)
public static void test01() {
User user = new User();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
user.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方法" + (endTime - startTime) + "ms");
}
// 反射方式调用(1873ms)
public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User( );
Class clazz = user.getClass();
Method getName = clazz.getDeclaredMethod("getName", null);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user, null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射方法" + (endTime - startTime) + "ms");
}
// 反射方式调用,关闭检测(1007ms)
public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User( );
Class clazz = user.getClass();
Method getName = clazz.getDeclaredMethod("getName", null);
getName.setAccessible(true);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user, null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射方法(关闭检测)" + (endTime - startTime) + "ms");
}
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
test01();
test02();
test03();
}
}
获取泛型信息
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
public class Test10 {
public void test01(Map<String, User> map, List<User> list) {
System.out.println("test01");
}
public Map<String, User> test02() {
System.out.println("test02");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Method method01 = Test10.class.getMethod("test01", Map.class, List.class);
Type[] genericParameterTypes = method01.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("【test01】" + genericParameterType); // 1
if (genericParameterType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
Method method02 = Test10.class.getMethod("test02");
Type genericReturnType = method02.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
}
获取注解信息
import java.lang.annotation.*;
import java.lang.reflect.Field;
/**
* 反射操作注解
*/
// 类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableLorain {
String value();
}
// 属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldLorain {
String columnName();
String type();
int length();
}
@TableLorain("BINARY")
class Student2 {
@FieldLorain(columnName = "binary_id", type = "int", length = 10)
private int id;
@FieldLorain(columnName = "binary_age", type = "int", length = 10)
private int age;
@FieldLorain(columnName = "binary_name", type = "varchar", length = 10)
private String name;
public Student2() {
}
public Student2(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Test11 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class clazz = Class.forName("com.lorain.reflection.Student2");
// 通过反射获得注解
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
// 获得注解value的值
TableLorain tableLorain = (TableLorain) clazz.getAnnotation(TableLorain.class);
String value = tableLorain.value();
System.out.println(value);
// 获得类指定的注解
Field name = clazz.getDeclaredField("name");
System.out.println(name);
FieldLorain annotation = name.getAnnotation(FieldLorain.class);
System.out.println(annotation.columnName());
System.out.println(annotation.length());
System.out.println(annotation.type());
}
}