Java注解与反射

本文详细介绍了Java中的注解(Annotation)及其使用,包括内置注解、元注解和自定义注解,以及SpringBoot中常见的注解。同时,文章探讨了反射机制,阐述了其优缺点,并展示了如何通过反射获取Class对象、调用方法和操作属性。此外,还讨论了类的加载和初始化过程。
摘要由CSDN通过智能技术生成

一、注解(Annotation)

注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,是我们可以在稍后某个时刻非常方便地使用这些元数据。

注解的定义看起来很像接口的定义。与其他Java接口一样,注解也将会编译成class文件。

image-20220206104226088
内置注解
  • @Override
    表示当前的方法定义将覆盖超类中的方法。

  • @Deprecated
    在使用一些方法时会出现横线。表示废弃,这个注释可以修辞方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为他很危险或有更好的选择

  • @SuperWarnings

    这个注解主要是用来抑制警告信息的,我们在写程序时,可能会报很多黄线的警告,但是不影响运行,我们就可以用这个注解来抑制隐藏它。与前俩个注解不同的是我们必须给注解参数才能正确使用他。

参数说明
deprecation使用了过时的类或方法的警告
unchecked执行了未检查的转换时的警告 如:使用集合时未指定泛型
fallthrough当在switch语句使用时发生case穿透
path在类路径、源文件路径中有不存在路径的警告
serial当在序列化的类上缺少serialVersionUID定义时的警告
finally任何finally子句不能完成时的警告
all关于以上所有的警告
元注解

我们在自定义注解时,需要使用java提供的元注解,就是负责注解的其他注解。java定义了四个标准的meta-annotation类型,他们被用来提供对其他注解类型声明。

  • @Target
    这个注解的作用主要是用来描述注解的使用范围,说白了就是我们自己定义的注解可以使用在哪个地方。

    image-20211017103931954

  • @Retention
    这个注解的作用就是我们需要告诉编译器我们需要在什么级别保存该注释信息,用于描述注解的生命周期。

    image-20211017104112705

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
	//参数默认为空
	String value() default "";
}
  • @Documented表示是否将我们的注解声称在Javadoc中
  • @Inherited子类可以继承父类的注解
自定义注解

定义注解时,会需要一些元注解。如@Target和@Retention。@Target用来定义你的注解将应用于什么地方(例如是一个方法或者一个域)。@Retention用来定义该注解在哪一个级别可用。

image-20211017104656368

SpringBoot常用注解
注解备注
@SpringBootApplication这个注解是Spring Boot最核心的注解,用在 Spring Boot的主类上,标识这是一个 Spring Boot 应用,用来开启 Spring Boot 的各项能力。实际上这个注解是@Configuration,@EnableAutoConfiguration,@ComponentScan三个注解的组合。由于这些注解一般都是一起使用,所以Spring Boot提供了一个统一的注解@SpringBootApplication。
@Controller用于定义控制器类,在spring项目中由控制器负责将用户发来的URL请求转发到对应的服务接口(service层),一般这个注解在类中,通常方法需要配合注解@RequestMapping。
@RestController用于标注控制层组件(如struts中的action),@ResponseBody和@Controller的合集。
@RequestMapping提供路由信息,负责URL到Controller中的具体函数的映射用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。

该注解有六个属性:
params:指定request中必须包含某些参数值是,才让该方法处理。
headers:指定request中必须包含某些指定的header值,才能让该方法处理请求。
value:指定请求的实际地址,指定的地址可以是URI Template 模式
method:指定请求的method类型, GET、POST、PUT、DELETE等
consumes:指定处理请求的提交内容类型(Content-Type),如application/json,text/html;
produces:指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回
@RequestParam用在方法的参数前面。
@RequestParam
String a =request.getParameter(“a”)。
@PathVariable@PathVariable(“xxx”)
通过 @PathVariable 可以将URL中占位符参数{xxx}绑定到处理器类的方法形参中@PathVariable(“xxx“)

@RequestMapping(value=”user/{id}/{name}”)
@GetMapping@GetMapping是一个组合注解,等价于@RequestMapping(method = RequestMethod.GET),它将HTTP Get请求映射到特定的处理方法上。
@PostMapping处理post请求

二、反射(Reflect)

反射指的是我们可以在运行期间加载、探知、使用编译期间完全未知的类。

反射是一个动态的机制,允许我们通过字符串来指挥程序实例化,操作属性、调用方法。使得代码提高了灵活性,但是同时也带来了更多的资源开销。

加载完类之后,在堆内存中,就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。 我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象地称之为:反射。

image-20220206112931896

实现Java反射机制的类都位于java.lang.reflect包中:

  1. Class类:代表一个类
  2. Field类:代表类的成员变量(类的属性)
  3. Method类:代表类的方法
  4. Constructor类:代表类的构造方法
  5. Array类:提供了动态创建数组,以及访问数组的元素的静态方法
优点和缺点

优点

  • 可以实现动态创建对象和编译,体现出很大的灵活性。

缺点

  • 对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作。
获取Class类的实例
  • 若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高。

    Class clazz = Person.class;

  • 已知某个类的实例,调用该实例的getClass()方法获取Class对象

    Class clazz = person.getClass();

  • 已知一个类的全类名。且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException

    Class clazz = Class.forName("demo01.Student");

  • 内置基本数据类型可以直接用类名.Type

  • 可以利用ClassLoader

package reflact;

import java.util.concurrent.Callable;

//测试Class类的创建方式有哪些
public class Test03 {
    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());

        //方式二:forname获得
        Class c2 = Class.forName("reflact.Student");
        System.out.println(c2.hashCode());

        //方式三:通过类名.class获得
        Class c3 = Student.class;
        System.out.println(c3.hashCode());

        //方式四:基本内置类型的包装类都有一个Type属性
        Class c4 = Integer.TYPE;
        System.out.println(c4);

        //获取父类类型
        Class c5 = c1.getSuperclass();
        System.out.println(c5);
    }
}

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="学生";
    }
}
class Teacher extends Person{
    public Teacher() {
        this.name="老师";
    }
}
Java内存

image-20220206112607216

类的加载过程

加载-链接-初始化

image-20220206190923778

image-20220206193756618

package reflact;

public class Test05 {
    public static void main(String[] args) {
        A a= new A();
        System.out.println(A.m);
    }
}

class A{
    static{
        System.out.println("A类静态代码初始化");
        m = 300;
    }
    static int m = 100;

    public A(){
        System.out.println("A类的无参构造初始化");
    }
}

/*
        1.加载到内存,会产生一个类对应Class对象
        2.连接,链接结束后m=0
        3.初始化
            <clinit>(){
                System.out.println("A类静态代码块初始化");
                m = 300;
                m = 100;
             }
         */

A类静态代码初始化
A类的无参构造初始化
100
image-20211017161701012
类的初始化
  • 类的主动引用(一定会发生类的初始化)
    • 当虚拟机启动,先初始化main方法所在的类
    • new一个类的对象
    • 调用类的静态成员(除了final常量)和静态方法
    • 使用java.lang.reflect包的方法对类进行反射调用
    • 当初始化一个类,如果其父类没有被初始化,则会先初始化它的父类
  • 类的被动引用(不会发生类的初始化)
    • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化。
    • 通过数组定义类引用,不会触发此类的初始化
    • 引用常量不会触发此类的初始化(常量在链接截断就存入调用类的常量池中了)
类加载器
image-20220206192503173 image-20211017161701012

image-20220206193720949

package reflact;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

//通过反射,动态创建对象
public class Test09 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //获取Class对象
        Class c1 = Class.forName("reflact.User");

        //构造一个对象
//        User user = (User)c1.newInstance();//本质是调用了类的无参构造器
//        System.out.println(user);

        //通过构造器创建对象
//        Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
//        User user2 = (User)constructor.newInstance("Tom",001,18);
//        System.out.println(user2);

        //通过反射调用方法
        User user3 = (User)c1.newInstance();

        //通过反射获取一个方法
        Method setName = c1.getDeclaredMethod("setName", String.class);

        //invoke:激活的意思
        //(对象,“方法的值”)
        setName.invoke(user3, "cat");
        System.out.println(user3.getName());

        //通过反射操作属性
        User user4 = (User) c1.newInstance();
        Field name = c1.getDeclaredField("name");

        //不能直接操作私有属性,需要关闭程序的安全检测,属性或者方法的setAccessible设置true
        name.setAccessible(true);
        name.set(user4,"Dog");
        System.out.println(user4.getName());
    }
}
反射操作泛型
  • Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除。
  • 为了通过反射操作这些类型,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
  • ParaneterizedType:表示一种参数化类型,比如Collection
  • GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
  • TypeVariable:是各种类型变量的公共父接口
  • WildcardType:代表一种通配符类型表达式

获取泛型信息

image-20211017195040624

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值