title: 反射和注解
date: 2022-05-17 22:22:50
categories:
- 个人博客
- Java基础
tags: - Java
注解
什么是注解?
定义:注解是放在 Java 源码的类、方法、字段、参数前的一种特殊注释。但是注释会被编译器直接忽略,而注解可以被编译器打包进入 class 文件。
作用:(1)对程序做出解释 (2)对作用域中内容进行检查和约束
格式: @ + “注解名” ,注解也可以有参数,如 @SuppressWarnings(value = “unchecked”)
内置注解
@Override:用于修饰方法,表示这个方法打算重写父类中的同名方法。
@Deprecated:可用于修饰方法,属性,类,表示所修饰内容已经被废弃,不鼓励程序员使用该内容。
@SuppressWarnings:用来抑制编译时的警告信息,必须添加一个参数才能正确使用,如
@SuppressWarnings(value = "all") // 全部抑制
@SuppressWarnings(value = "unchecked")
元注解
定义:负责解释其他注解的注解。
4 个标准元注解
-
@Target:用于描述注解的使用范围,即注解可以被用在什么地方。
-
@Retention:表示需要在什么级别保存该注解信息,用于描述注解的生命周期。
- SOURCE 源代码级别
- CLASS 字节码级别
- RUNTIME 运行时
RUNTIME > CLASS > SOURCE
-
@Documented 表示是否将我们的注解生成在 JAVAdoc 中。
-
@Inherited 表示子类可以继承父类的注解。
自定义注解
格式:public @interface 注解名{}。
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
String name(); //这里不是成员变量,而是参数 参数名后有括号()
int age() default 1;
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
String value(); //只有一个参数成员,默认参数名为 value
}
反射机制
动态语言和静态语言
动态语言:在运行时可以改变其结构的语言,通俗说就是在运行时代码可以根据某些条件改变自身结构
主要的动态语言有:Object-C,C#,JavaScript,Python等
静态语言:运行时结构不变的语言
静态语言有:C,C++,Java
Java 因为反射机制,可以称之为"准动态语言"。使用反射可以让 Java 获得动态语言的特性!
Java Reflection
反射机制允许程序在执行期间借助于 Reflection API 获得任何类的内部信息,并直接操作任意对象的内部属性及方法。
反射的过程与实例化一个对象的过程相反。
实例化一个对象的过程:引入类 -> 通过 new 实例化 -> 获取实例化对象
反射的过程:实例化对象 -> getClass() 方法 -> 获取包含类完整信息的 Class对象
反射的优点和缺点
优点:可以实现动态创建对象和编译,体现出很大的灵活性
缺点:带来一定的性能开销,且存在安全隐患。
Class 对象
通过反射得到的对象是一个 Class 类型的对象。这个对象具有以下特征:
- Class 本身是一个类
- Class 对象只能由系统创建
- 一个加载的类在 JVM 中只有一个 Class 实例
- 一个 Class 对象对应的是一个加载到 JVM 中的 class 文件
- 通过 Class 可以获取一个类的所有完整信息
获得反射对象
创建三个对象 Person,Student,Person 是 Student 的父类。
获取反射对象包括以下四种方式:
- 对象.getClass()
- Class.forName(“包名”)
- 类名.class
- 包装类.TYPE
class Person{
public 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 = "学生";
}
}
public class Reflect {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Student();
System.out.println(person.name);
// 通过对象获得
Class c1 = person.getClass();
System.out.println(c1.hashCode());
// 通过 Class.forname() 获得
Class c2 = Class.forName("Student");
System.out.println(c2.hashCode());
// 方式三 类名.class获得
Class c3 = Student.class;
System.out.println(c3.hashCode());
// 方式 4 通过包装类的 TYPE 获得
Class<Integer> c4 = Integer.TYPE;
System.out.println(c4.getTypeName());
// 获取父类类型
Class c5 = c1.getSuperclass();
System.out.println(c5.getTypeName());
}
}
类的加载过程
类的初始化
类的主动引用一定会发生类的初始化,具体包括:
-
虚拟机启动,会先初始化 main 方法所在的类
-
new 一个对象
-
调用类的静态成员和静态方法 (final常量除外)
-
对类方法进行反射调用
-
初始化一个类,如果其父类没有被初始化,就会先初始化其父类
类的被动引用不会发生类的初始化
- 当访问一个静态域时,只有声明这个域的类会被初始化。例如:子类调用父类的静态变量,子类不会被初始化
- 通过数组定义类的引用
- 引用常量
public class Initialize {
static {
System.out.println("main 方法所在类被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
Son son = new Son(); // new 方法实例化对象 main Father Son 类依次被加载
Class.forName("Son"); // 反射
//不会产生类引用的方法
System.out.println(Son.member); // 依次加载 Main 和 Father 不加载 Son
Son[] sons = new Son[10]; // 只有 Main 被加载,数组只是给分配了一系列空间
System.out.println(Son.N); // 只有 Main 被加载,常量在链接阶段就被调入了常量池中
}
}
class Father{
static int member = 2;
static {
System.out.println("父类被加载");
}
}
class Son extends Father{
static {
System.out.println("子类被加载");
}
static int n = 100;
static final int N = 10;
}
类加载器
类加载器的作用:将 class 文件字节码内容加载到内存中,将静态数据转换成方法区的运行时数据结构。然后在堆中创建一个 Class 对象,作为方法区中类数据的访问接口。
类加载器有以下三种:
- 引导类加载器:由 C++ 编写,是 JVM 自带的类加载器,负责加载 Java 核心类库。在 Java 中无法直接获取该加载器。
- 扩展类加载器:负责加载 jre/lib/ext 目录下 jar 包的加载。
- 系统加载器:负责 java-classpath 所指目录下的类与 jar 包的加载工作。
获取三个类加载器并打印
public class Loader {
public static void main(String[] args) throws ClassNotFoundException {
// 获取系统类的加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
// 获取扩展类加载器
ClassLoader extensionLoader = systemClassLoader.getParent();
System.out.println(extensionLoader);
// 获取引导类加载器
ClassLoader bootStrapLoader = extensionLoader.getParent();
System.out.println(bootStrapLoader);
// 打印当前类的类加载器
ClassLoader classLoader = Class.forName("Loader").getClassLoader();
System.out.println(classLoader); // 系统类加载器
//打印 Object 类的类加载器
ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader1); // null 引导类加载器,因为无法直接获取 打印 null
}
}
获取运行时类的完整结构
创建一个 Information 类,通过反射获取 String 类的完整结构。
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Information {
private static Class aClass;
public static void main(String[] args) throws NoSuchMethodException {
Class aClass = String.class;
// 获得类的名字
System.out.println(aClass.getName());
System.out.println(aClass.getSimpleName());
// 获得类的属性
Field[] fields = aClass.getFields(); // 获取所有 public 属性
fields = aClass.getDeclaredFields(); // 获取全部属性
for (Field field : fields) {
System.out.println(field);
}
// 获得类的方法
Method[] methods = aClass.getMethods(); // 获得本类及父类的所有 public 方法
methods = aClass.getDeclaredMethods(); // 获取本类的所有方法
for (Method method : methods) {
System.out.println(method);
}
// 获得构造器
Constructor[] constructors = aClass.getConstructors(); // public 构造器
constructors = aClass.getDeclaredConstructors(); // 获取所有的构造器
Constructor constructor = aClass.getConstructor(String.class);// 获取指定的构造器
System.out.println(constructor);
}
}
动态创建对象执行方法
首先创建一个 User 对象
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User() {
this.name = "Andy";
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
public User(String name) {
this.name = name;
}
}
创建一个 Dynamic 类,通过反射动态创建 User 类,并调用其方法
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Dynamic {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class<?> aClass = Class.forName("User");
// 创建一个对象
User user = (User) aClass.newInstance(); // 调用了 User 的无参构造方法
System.out.println(user);
// 通过构造器创建对象
Constructor<?> constructor = aClass.getConstructor(String.class);
User user1 = (User) constructor.newInstance("mingming");
System.out.println(user1);
// 通过反射调用方法
Method setName = aClass.getDeclaredMethod("setName",String.class);
setName.invoke(user1,"xiaomeng"); // invoke的意思是激活 method.invoke(Object obj,Object[] args)
System.out.println(user1.getName());
// 通过反射获取属性
Field name = aClass.getDeclaredField("name");
name.setAccessible(true); // 设置可访问性,否则 name.set 会报错
name.set(user1,"daughter");
System.out.println(user1.getName());
}
}
在反射中,调用类中的方法,通过 Method 类来完成,包括以下两步:
- 通过 Class 类的 getMethod(String name,Class…parameterTypes) 方法获得一个 Method 对象。
- 使用 method.invoke(Object obj,Object[] args) 方法来调用。第一个参数是操作对象,第二个是参数
注意:当需要获取的 Method,Field,Constructor 类为 private 时,需要设置访问可见性
object.setAccessible(true); // object 为 Method、Field 或 Constructor 的实例
setAccessible(true) 指示反射的对象在使用时应该取消 Java 语言的访问检查。
当反射对象频繁被调用时,可以使用此方法,提高执行效率。
反射性能分析
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
// 分析反射的性能
public class Analysis {
public static void normal() {
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));
}
public static void reflect() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class aClass = Class.forName("User");
User user = (User) aClass.newInstance();
Method method = aClass.getMethod("getName", null);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
method.invoke(user, null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射方法耗时:" + (endTime - startTime));
}
public static void access() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class aClass = Class.forName("User");
User user = (User) aClass.newInstance();
Method method = aClass.getMethod("getName", null);
method.setAccessible(true);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
method.invoke(user, null);
}
long endTime = System.currentTimeMillis();
System.out.println("使用setAccessible(true)方法后,反射方法耗时:" + (endTime - startTime));
}
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
normal();
reflect();
access();
}
}
运行代码,得到普通方法耗时 6 ms,反射耗时 1057 ms ,使用 setAssesible(true) 后反射耗时 823 ms。
反射操作泛型
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 Template {
public void test01(Map<String,Integer> map, List<Integer> list){
System.out.println("test01");
}
public Map<String,Integer> test02(){
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Method method = Template.class.getMethod("test01", Map.class, List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes(); // 获取泛型参数信息
for (Type genericParameterType : genericParameterTypes) {
System.out.println(genericParameterType);
if(genericParameterType instanceof ParameterizedType){ //如果传入参数类型
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments(); // 获取实际参数类型
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
System.out.println("*********************************************");
method = Template.class.getMethod("test02", null);
// 获取泛型返回值信息
Type genericReturnType = method.getGenericReturnType();
if(genericReturnType instanceof ParameterizedType){ //如果传入参数类型
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments(); // 获取实际参数类型
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
}
反射操作注解
反射操作注解的一个经典操作就是 ORM。
用一个实体类对应数据库中的一张表。
在类中,属性中加入注解。通过反射获取注解的值,将注解的值拼接成 SQL 语句,进而操作数据库。
import java.lang.annotation.*;
import java.lang.reflect.Field;
public class operateAnnotation {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class<?> aClass = Class.forName("Book");
Annotation[] annotations = aClass.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
MyType annotation = aClass.getAnnotation(MyType.class); // 获取指定类的注解
System.out.println(annotation.value()); // 获取注解的值
Field name = aClass.getDeclaredField("name"); // 获取属性
MyField annotation1 = name.getAnnotation(MyField.class); // 获取属性的 指定注解
System.out.println(annotation1);
System.out.println(annotation1.length());
}
}
@MyType("db_book")
class Book {
@MyField(name="db_id",type = "int",length = 10)
private int id;
@MyField(name="db_name",type = "varchar",length = 10)
private String name;
public Book(int id, String name) {
this.id = id;
this.name = name;
}
public Book() {}
public int getId() {
return id;
}
public String getName() {
return name;
}
}
// 创建一个类注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyType {
String value();
}
// 创建一个属性注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyField{
String name();
String type();
int length();
}