首页 > 其他分享 >注解与反射

注解与反射

时间:2024-08-04 09:32:41浏览次数:12  
标签:反射 System class println 注解 Class out

注解与反射

1. 注解(Annotation)

可以被其他程序读取

在哪里使用:

可以附加在 package,class,method,field等上面,相当于给他们添加了额外的辅助信息。

可以通过反射机制编程实现对这些元数据的访问。

1.1常见内置注解

@Override: 重写的注解。

@Deprecated:废弃的注解。已过时或者有更好的方式,不推荐使用。

@SuppressWarnings("all"):镇压警告。可以放在类名或者方法上

1.2元注解

元注解的用就是负责注解其他注解。Java定义了4个标准的 meta-annotation类型,它们被用来提供对其他annotation类型作说明。

@Target:用于描述注解的使用范围(即被描述的注解可以用在什么地方)

@Retention表示需要在什么级别保存该注释信息,用于描述注解的生命周期

SOURCE (源码时有效)< CLASS() < RUNTIME

Docunment:说明该注解被包含在javadoc

@Inherited:说明子类可以继承父类中的该注解

@Target(value = ElementType.METHOD)//方法上有效
@Retention(value = RetentionPolicy.RUNTIME)//运行时有效
public @interface testAnnotation {

}

Target其中的value参数ElementType是一个枚举类

public enum ElementType {
   /** Class, interface (including annotation type), or enum declaration */
   TYPE,

   /** Field declaration (includes enum constants) */
   FIELD,

   /** Method declaration */
   METHOD,

   /** Formal parameter declaration */
   PARAMETER,

   /** Constructor declaration */
   CONSTRUCTOR,

   /** Local variable declaration */
   LOCAL_VARIABLE,

   /** Annotation type declaration */
   ANNOTATION_TYPE,

   /** Package declaration */
   PACKAGE,

   /**
    * Type parameter declaration
    *
    * @since 1.8
    */
   TYPE_PARAMETER,

   /**
    * Use of a type
    *
    * @since 1.8
    */
   TYPE_USE
}
1.3 自定义注解

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口

@Target(value = {ElementType.TYPE,ElementType.METHOD})//方法上有效
@Retention(value = RetentionPolicy.RUNTIME)//运行时有效
public @interface testAnnotation1 {
    //注解的参数: 参数类型 + 参数名()
    String name() default "";
    int age() default 0;
    int id() default -1; //默认值为-1,代表不存在
}

对于注解存在默认值,则可以不写参数的value value才能省略注解中括号的参数名

@testAnnotation1(name = "自定义注解")
public class annotationTest {
    @testAnnotation1(name="方法上自定义注解")
    public void test(){
        System.out.println("测试元注解");
    }
}

2. 反射(Reflection)

2.1 java反射机制概述

动态语言:运行时代码可以根据某些条件改变自身结构

静态语言:运行时结构不变的语言

反射是java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并且能直接操作任意对象的内部属性及方法

Class c = Class.forName("java.lang.String")

反射

  • 优点:可以动态创建对象和编译,体现很大的灵活性
  • 缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行的操作。

加载完类之后,在内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。

public class ReflectionTest1 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class c1 = Class.forName("BaseLearn.Reflection.User");

        //System.out.println(c1);

        Class c2 = Class.forName("BaseLearn.Reflection.User");
        Class c3 = Class.forName("BaseLearn.Reflection.User");
        Class c4 = Class.forName("BaseLearn.Reflection.User");

        //一个类在内存中只有一个Class对象
        //一个类被加载后,类的整个结构都会被封装在Class对象中
        //hashCode值相同
        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());
        System.out.println(c4.hashCode());
    }
}
2.2 理解Class类并获取Class实例

Class对象是Reflection的根源,针对任何想动态加载、运行的类,唯有先获得相应的Class对象。

image-20240729141628761

//Class类的创建方式有哪些
public class ReflectionTest2 {
    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());
        //方式2:forName获得
        Class c2 = Class.forName("BaseLearn.Reflection.student");
        System.out.println(c2.hashCode());
        //方式3:通过类名.class获得
        Class c3 = student.class;
        System.out.println(c3.hashCode());
        //方法4:基本内置类型的包装类都有一个TYPE属性
        Class c4 = Integer.TYPE;
        System.out.println(c4);
        //获得父类类型
        Class c5 = c1.getSuperclass();
        System.out.println(c5);
    }
}

哪些类型可以有Class对象

  • class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
  • interface: 接口
  • []: 数组
  • enum:枚举
  • annotation:注解
  • primitive type:基本数据类型
  • void
Class c1 = Objects.class;
Class c2 = Comparator.class;
Class c3 = String[].class;
Class c4 = Override.class;
Class c5 = Integer.class;
Class c6 = void.class;
Class c7 = Class.class;
//只要元素类型与维度一样,就是通一个Class
int[] int1 = new int[10];
int[] int2 = new int[100];
int[][] ints = new int[10][10];

System.out.println(int1.getClass().hashCode());
System.out.println(int2.getClass().hashCode());
System.out.println(ints.getClass().hashCode());
//结果
//1554874502
//1554874502
//1846274136
2.3 类的加载与ClassLoader

image-20240729144215220

类的加载与ClassLoader的理解

  1. 加载: 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区运行时数据结构,然后生成一个代表这个类的java.lang.class对象

  2. 链接:将java类中的二进制代码合并到JVM的运行状态中的过程

    1. 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
    2. 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法去中进行分配
    3. 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
  3. 初始化:

    1. 执行类构造器()方法的过程,类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造器)
    2. 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
    3. 虚拟机会保证一个类的()方法在多线程环境中正确加锁和同步
public class ReflectionTest4 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(A.m);
    }
}
/*
1. 加载到内存中,会产生一个类对应class对象
2. 链接 链接结束后,设置类变量默认初始值 m=0
3. 初始化:
	<clinit>() {
		System.out.println("A类静态代码块初始化");
       	      m=300;
       	      m=100;
	}
*/
class A {
    static {
        System.out.println("A类静态代码块初始化");
        m=300;
    }

    static int m = 100;

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

image-20240729151104051

什么时候会发生类初始化

  1. 类的主动引用(一定会发生类的初始化)
    • 当虚拟机启动,先初始化main方法所在的类
    • new 一个类的对象
    • 调用类的静态成员(除了final常量)和静态方法
    • 使用java.lang.reflect包的方法对类进行反射调用
    • 当初始化一个类,如果其父类没有被初始化,则会先初始化它的父类
  2. 类的被动引用(不会发生类的初始化)
  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
  • 通过数组定义类引用,不会触发此类的初始化
  • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池了)
//测试什么时候会初始化
public class ReflectionTest5 {
    static {
        System.out.println("主类被加载");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        //1. 主动引用
        //son son = new son();
        //反射也会产生主动引用
        //Class aClass = Class.forName("BaseLearn.Reflection.son");

        //System.out.println(son.b);

        //son[] arr = new son[10];
        System.out.println(son.M);
    }
}

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;
}

类加载器的作用:

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口

类缓存:

标准的JavaSE类加载器可以按照要求查找类,单一旦某个类被加载代类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象

2.4创建运行时类的对象
2.5 获取运行时类的完整结构

image-20240729154843177

2.6调用运行时类的指定结构

有了Class对象能做什么

创建类的对象:调用Class对象的newInstance()方法

  • 类必须有一个无参的构造器
  • 类的构造器的访问权限需要足够

难道没有无参构造器就不能创建对象了吗?

只要在操作的时候明确调用类中的构造器,并且将参数传递进去之后,才可以实例化操作

步骤:

  1. 通过Class 类的getDeclaredConstructor()方法取得本类的指定形参类型的构造器
  2. 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数
  3. 通过Constructor实例化对象newInstance

通过反射调用类中的方法,通过Method类完成

  1. 通过Class类的getMethod(String name,Class...parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型
  2. 之后使用Object invoke(Object obj,Object[] args)进行调用,并在方法中传递要设置的obj对象的参数信息。

注意

  • Method和Field,Constructor对象都有setAccessible()方法,其作用是启动和进制安全检查的开关。参数值为true ,则指示反射的对象在使用时取消Java语言访问检查
    • 提高反射的效率,如果代码中必须要用反射,而该句代码需要频繁的调用,设置为true
    • 使得原本无法访问的私有成员也可以访问
Class c1 = Class.forName("BaseLearn.Reflection.User");
        //构造一个对象
        //User user = (User) c1.newInstance(); //本质时调用了无参构造器

        //System.out.println(user);

        //通过构造器创建对象
//        Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class);
//        User user = (User) declaredConstructor.newInstance("hhj", 20);
//        System.out.println(user);


        //通过反射调用普通方法
        User user1 = (User) c1.newInstance();
        System.out.println("111"+user1.getName());
        //通过反射获取一个方法
        Method setName = c1.getDeclaredMethod("setName", String.class);
        setName.invoke(user1,"kkk");
        System.out.println("222"+user1.getName());
2.6.1获取泛型信息

image-20240729210548414

2.6.2获取注解信息

标签:反射,System,class,println,注解,Class,out
From: https://www.cnblogs.com/solutide/p/18341438

相关文章

  • [Spring]自定义注解
    SpringBoot自定义注解实现在学习SpringBoot过程中,学习了一些SpringBoot特有的注解,大多是为了使用方便将多个注解进行了整合。既然学习到了注解,就来重新认识一下Spring的自定义注解实现过程,在之后学习新注解的实现原理时会更加游刃有余。SpringBoot实现自定义注解Java元注解Jav......
  • @ConfigurationProperties注解获取为null的问题或者获取不到值的问题
    结论:set方法不能被static修饰、不能被private修饰、不能被protect修饰,不能被abstract修饰,不能是Object和Class理论依据:1、springboot源码 JavaBeanBinder文件下的isCandidate方法privatebooleanisCandidate(Methodmethod){intmodifiers=method.g......
  • 反射内存卡经典应用场景
    ARINC429模块在航空电子系统中扮演着至关重要的角色,‌其应用范围广泛且深入,‌确保了飞机各系统间数据的高效、‌准确和可靠传输。‌以下是对ARINC429模块典型应用场景的详细阐述。一、‌引言ARINC429,‌作为航空电子领域广泛采用的一种数字信息传输标准,‌自1977年提出以来,‌便以......
  • java注解与反射(非常详细, 带有很多样例)
    下面是详细地讲解Java中的注解与反射,并提供了很多的示例来帮助理解。Java注解(Annotations)1.注解的基本概念注解(Annotation)是Java5引入的一种用于为代码元素(类、方法、字段、参数等)添加元数据的机制。这些元数据可以在编译时、类加载时或运行时被读取并使用。注解......
  • 基于hibernate-validator实体字段唯一性检查 ,UniqueKey注解
    基于hibernate-validator实体字段唯一性检查,UniqueKey注解前言经常会在新增或修改时,检查某个字段或者多个字段的唯一性,如果重复就需要返回错误信息,重复代码写多了就准备写校验注解解决这个问题,分为两个版本,hibernate和mybatisplus1.mybatisplus注解/***唯一约束*<p>......
  • Java反射机制及其应用
    Java反射机制及其应用引言Java反射机制是Java语言的一项强大特性,它允许程序在运行时查询、访问和修改类、接口、方法、构造函数等的属性和行为。反射机制在动态代理、框架开发、依赖注入等领域有着广泛的应用。本文将介绍反射的基本概念、如何使用反射,以及反射在动态代理......
  • JAVA里的反射(详解)
    1.反射1.1反射的概述:专业的解释(了解一下):是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意属性和方法;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。通俗的理解:(掌握)利用反射创建的对象可以无......
  • 注解(Annotation)在Java开发中的应用
    注解(Annotation)在Java开发中的应用引言注解(Annotation)是Java语言的一个特性,用于为代码提供元数据。注解可以被编译器或运行时环境用来处理代码,例如编译时检查、运行时处理等。Spring框架广泛使用了注解来简化配置和提高开发效率。本文将讲解注解的基本概念、如何自定义注......
  • java注解与反射(非常详细, 带有很多样例)
    下面是详细地讲解Java中的注解与反射,并提供了很多的示例来帮助理解。Java注解(Annotations)1.注解的基本概念注解(Annotation)是Java5引入的一种用于为代码元素(类、方法、字段、参数等)添加元数据的机制。这些元数据可以在编译时、类加载时或运行时被读取并使用。注解不会直......
  • mybatis中映射和注解在数据库进行增删改查的方法
    此两种方法都是基于maven项目的基础上运行主要步骤如下:1、在mevan中的pom文件添加依赖文件: 2、在src目录下编写编写配置文件(mybatis-cfg.xml) 3、编写实体类(Student.java) 4、编写映射文件(StudentMapper.xml) 5、编写测试文件 6、调用JUnit5进行调试核心文件含义:......