反射和注解


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 类来完成,包括以下两步:

  1. 通过 Class 类的 getMethod(String name,Class…parameterTypes) 方法获得一个 Method 对象。
  2. 使用 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();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值