注解与反射

注解与反射

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获取注解信息
posted @ 2024-08-04 08:28  solutide  阅读(4)  评论(0编辑  收藏  举报