Java基础——注解与反射(反射的多种实例及机制)

注解 Annotation

定义

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。

Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc(注释) 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。

组成部分:

Annotation.java

package java.lang.annotation;
public interface Annotation {

    boolean equals(Object obj);

    int hashCode();

    String toString();

    Class<? extends Annotation> annotationType();
}

ElementType.java (指定类型)

package java.lang.annotation;

public enum ElementType {
    TYPE,               /* 类、接口(包括注释类型)或枚举声明  */

    FIELD,              /* 字段声明(包括枚举常量)  */

    METHOD,             /* 方法声明  */

    PARAMETER,          /* 参数声明  */

    CONSTRUCTOR,        /* 构造方法声明  */

    LOCAL_VARIABLE,     /* 局部变量声明  */

    ANNOTATION_TYPE,    /* 注释类型声明  */

    PACKAGE             /* 包声明  */
}

RetentionPolicy.java (指定作用域)

package java.lang.annotation;
public enum RetentionPolicy {
    SOURCE,            /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了  */

    CLASS,             /* 编译器将Annotation存储于类对应的.class文件中。默认行为  */

    RUNTIME            /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}

SOURCE<CLASS<RUNTIME

说明:

(01) Annotation 就是个接口。

“每 1 个 Annotation” 都与 “1 个 RetentionPolicy” 关联,并且与 “1~n 个 ElementType” 关联。可以通俗的理解为:每 1 个 Annotation 对象,都会有唯一的 RetentionPolicy 属性;至于 ElementType 属性,则有 1~ n 个。

(02) ElementType 是 Enum 枚举类型,它用来指定 Annotation 的类型。

“每 1 个 Annotation” 都与 “1~n 个 ElementType” 关联。当 Annotation 与某个 ElementType 关联时,就意味着:Annotation有了某种用途。例如,若一个 Annotation 对象是 METHOD 类型,则该 Annotation 只能用来修饰方法。

(03) RetentionPolicy 是 Enum 枚举类型,它用来指定 Annotation 的策略。通俗点说,就是不同 RetentionPolicy 类型的 Annotation 的作用域不同。

内置注解

  • @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
  • @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
  • @SuppressWarnings - 指示编译器去忽略注解中声明的警告。

元注解

元注解的作用就是负责注解其他注解

  • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。(作用域)
  • @Documented - 标记这些注解是否包含在用户文档(javadoc)中。
  • @Target - 标记这个注解应该是哪种 Java 成员(作用于谁)
  • @Inherited - 标记这个子类可以继承父类中的该注解
  • @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
  • @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
  • @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

自定义注解

使用@interface自定义注解

自动继承了java.lang.annotation.Annotation接口

➢分析:
@ interface用来声明一个注解,格式: @ interface注解名{定义内容}

  • 其中的每一个方法实际上是声明了一个配置参数.
  • 方法的名称就是参数的名称.
  • 返回值类型就是参数的类型(返回值只能是基本类型,Class , String , enum ).
  • 可以通过default来声明参数的默认值
  • 如果只有一个参数成员,一般参数名为value
  • 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值.

示例:

@Target({ElementType.TYPE,ElementType.METHOD})//表示作用于类、方法
@Retention(RetentionPolicy.RUNTIME)//表示作用域为运行态
@interface MyAnnotation{
    //注解的参数: 数据类型 + 参数名()
    /*注:
    	1.此处“参数名()”并不是方法,括号必须加
    	2.如果参数只有一个,可命名为value()。则在使用时可省略参数名直接传参
    	3.若定义了注解带参数,则必须提供参数
    	4.可使用default关键字定义默认值,此时可不必需提供参数
    	5.int 类型默认值如果为-1,则表示不存在。(类似index=-1)
    */
    
    String name();
  //String name() default "默认值";  
  //String name() default -1;
}

反射 Reflection

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

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

在这里插入图片描述

获得反射对象

在Object类中定义了一下方法,此方法将被所有子类继承

public final Class getClass()

以上的方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。

反射机制的相关类

与Java反射相关的类如下:

类名用途
Class类代表类的实体,在运行的Java应用程序中表示类和接口
Field类代表类的成员变量(成员变量也称为类的属性)
Method类代表类的方法
Constructor类代表类的构造方法

Class类

对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。
对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定某个结构(classinterface/enum/annotation/primitive type/void/)的有关信息。

  • Class本身也是一个类
  • Class对象只能由系统建立对象
  • 一个加载的类在JVM中只会有一个Class实例
  • 一个Class对象对应的是一个加载到JVM中的一个.class文件
  • 每个类的实例都会记得自己是由哪个Class实例所生成
  • 通过Class可以完整地得到一个类中的所有被加载的结构
  • Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象

常用方法:

  • 获得类相关的方法
方法用途
getClassLoader()获得类的加载器
getClasses()返回一个数组,数组中包含该类中所有公共类和接口类的对象
getDeclaredClasses()返回一个数组,数组中包含该类中所有类和接口类的对象
forName(String className)根据类名返回类的对象
getName()获得类的完整路径名字
newInstance()创建类的实例
getPackage()获得类的包
getSimpleName()获得类的名字
getSuperclass()获得当前类继承的父类的名字
getInterfaces()获得当前类实现的类或是接口
  • 获得类中属性相关的方法
方法用途
getField(String name)获得某个公有的属性对象
getFields()获得所有公有的属性对象
getDeclaredField(String name)获得某个属性对象
getDeclaredFields()获得所有属性对象
  • 获得类中注解相关的方法
方法用途
getAnnotation(Class<A> annotationClass)返回该类中与参数类型匹配的公有注解对象
getAnnotations()返回该类所有的公有注解对象
getDeclaredAnnotation(Class<A> annotationClass)返回该类中与参数类型匹配的所有注解对象
getDeclaredAnnotations()返回该类所有的注解对象
  • 获得类中构造器相关的方法
方法用途
getConstructor(Class…<?> parameterTypes)获得该类中与参数类型匹配的公有构造方法
getConstructors()获得该类的所有公有构造方法
getDeclaredConstructor(Class…<?> parameterTypes)获得该类中与参数类型匹配的构造方法
getDeclaredConstructors()获得该类所有构造方法
  • 获得类中方法相关的方法
方法用途
getMethod(String name, Class…<?> parameterTypes)获得该类某个公有的方法
getMethods()获得该类所有公有的方法
getDeclaredMethod(String name, Class…<?> parameterTypes)获得该类某个方法
getDeclaredMethods()获得该类所有方法
  • 类中其他重要的方法
方法用途
isAnnotation()如果是注解类型则返回true
isAnnotationPresent(Class<? extends Annotation> annotationClass)如果是指定类型注解类型则返回true
isAnonymousClass()如果是匿名类则返回true
isArray()如果是一个数组类则返回true
isEnum()如果是枚举类则返回true
isInstance(Object obj)如果obj是该类的实例则返回true
isInterface()如果是接口类则返回true
isLocalClass()如果是局部类则返回true
isMemberClass()如果是内部类则返回true

Field类

Field代表类的成员变量(成员变量也称为类的属性)。

方法用途
equals(Object obj)属性与obj相等则返回true
get(Object obj)获得obj中对应的属性值
set(Object obj, Object value)设置obj中对应属性值

Method类

Method代表类的方法。

方法用途
invoke(Object obj, Object… args)传递object对象及参数调用该对象对应的方法

Constructor类

Constructor代表类的构造方法。

方法用途
newInstance(Object… initargs)根据传递的参数创建类的对象

反射机制

类的加载过程内存分析

加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象.
链接: 将Java类的二进制代码合并到JVM的运行状态之中的过程。

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

初始化

  • 执行类构造器< clinit>()方法的过程。类构造器< clinit> ()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
  • 虚拟机会保证一 个类的< clinit> ()方法在多线程环境中被正确加锁和同步。

类初始化详解:

类的主动引用(一定会发生类的初始化)

  • 当虚拟机启动,先初始化main方法所在的类
  • new一个类的对象
  • 调用类的静态成员(除了final常量)和静态方法
  • 使用java.lang.reflect包的方法对类进行反射调用
  • 当初始化-一个类,如果其父类没有被初始化,则先会初始化它的父类

类的被动引用(不会发生类的初始化)

  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:
  • 当通过子类引用父类的静态变量,不会导致子类初始化
  • 通过数组定义类引用,不会触发此类的初始化
  • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)

反射实例

反射操作类

创建类的对象:

调用Class对象的newlnstance()方法

  1. 类必须有一个无参数的构造器
    2)类的构造器的访问权限需要足够

创建类的实例化对象

1)通过Class类的getDeclaredConstructor(Class... parameter Types)取得本类的指定形参类型的构造器

2)向构造器的形参中传递一一个对象 数组进去,里面包含了构造器中所需的各个参数。
3)通过Constructor实例化对象

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

设置指定的属性

通过反射,修改类中的属性,通过Field类完成。

①通过Class类的getField(String name,Class... parameterTypes)方法取得一个Field对象
②之后使用Field对象.set(Object obj, Object value)进行修改,并向方法中传递要修改的obj对象的参数信息。

Object invoke(Object obj, Object … args)
➢Object 对应原方法的返回值,若原方法无返回值,此时返回null
➢若原方法若为静态方法,此时形参Object obj可为null .
➢若原方法形参列表为空,则Object[] args为null
➢若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的
setAccessible(true)方法,将可访问private的方法。

(注:set(Object obj, Object value)与invoke类似)

setAccessible
➢Method和Field、 Constructor对象都有setAccessible()方法。
➢setAccessible作用是启动和禁用访问安全检查的开关。
➢参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
➢提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
➢使得原本无法访问的私有成员也可以访问
➢参数值为false则指示反射的对象应该实施Java语言访问检查

package com.Liulian.ReflectionTest;

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

public class TestReflection01 {

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {

        // 获得Class对象   使用 CLass.forName()、  对象实例.getClass() 等方法
        Class c1 = Class.forName("com.Liulian.ReflectionTest.Reflector01");

        /* 获得对象的方法二
        Reflector01 r1 = new Reflector01();
        Class<? extends Reflector01> r1Class = r1.getClass();
        Reflector01 reflector01 = r1Class.newInstance();
        */

        // 构造一个对象
        //Object o = c1.newInstance();// 调用了类的无参构造器,如果类没有定义无参构造器则抛出异常
        Reflector01 r1 = (Reflector01) c1.newInstance();// 这里已知类类型,可直接转成目标类

        // 通过构造器构造一个对象
        Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
        Reflector01 myrefltobj01 = (Reflector01) constructor.newInstance("榴莲男孩", 18, 666);
        System.out.println(myrefltobj01.getAge());// 输出18,成功创建该实例

        // 通过反射获取方法
        Method[] myrefletmeth = c1.getDeclaredMethods();// 获取所有方法
        for (Method method : myrefletmeth) {
            System.out.println(method);
        }

        Method stringmethod = c1.getDeclaredMethod("method01",String.class);// 获取指定的方法
        Object invoke = stringmethod.invoke(myrefltobj01,"Hello,World!");// invoke(对象,参数) 激活该方法

        // 通过反射获取属性
        Field[] myrefletfids = c1.getDeclaredFields();
        for (Field myrefletfid : myrefletfids) {
            System.out.println(myrefletfid);
        }
        Field age = c1.getDeclaredField("age");
        age.setAccessible(true);// 设置访问权限,关闭程序的安全检测
        age.set(myrefltobj01,16);// 不能直接访问私有属性    set(对象,参数)方法 设置属性值
        System.out.println(myrefltobj01.getAge());



    }

}

class Reflector01 {

    public String name;
    private int age;
    public int numbers;
    public Reflector01() {
        System.out.println("无参构造被执行");
    }

    public Reflector01(String name, int age, int numbers) {
        this.name = name;
        this.age = age;
        this.numbers = numbers;
        System.out.println("有参构造被执行");
    }

    public void method01(String words){
        System.out.println(words);
    }

    public void method01(int numbers){
        System.out.println(numbers);
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    void Privatemothod(){
        System.out.println("这是不是公共方法");
    }
}

输出结果:

有参构造被执行
18
public void com.Liulian.ReflectionTest.Reflector01.setAge(int)
public void com.Liulian.ReflectionTest.Reflector01.method01(int)
public void com.Liulian.ReflectionTest.Reflector01.method01(java.lang.String)
void com.Liulian.ReflectionTest.Reflector01.Privatemothod()
public int com.Liulian.ReflectionTest.Reflector01.getAge()
Hello,World!
public java.lang.String com.Liulian.ReflectionTest.Reflector01.name
private int com.Liulian.ReflectionTest.Reflector01.age
public int com.Liulian.ReflectionTest.Reflector01.numbers
16

反射操作泛型

Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器Javac使用的,确保数据的安全性和免去强制类型转换问题, 但是, 一旦编译完成,所有和泛型有关的类型全部擦除
为了通过反射操作这些类型, Java新增了ParameterizedType , GenericArrayTypeTypeVariableWildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型.

  • ParameterizedType :表示一种参数化类型,比如Collection<String>
  • GenericArrayType : 表示一种元素类型是参数化类型或者类型变量的数组类型
  • TypeVariable :是各种类型变量的公共父接口
  • WildcardType :代表一种通配符类型表达式

获取方法参数类型

方法对象.getGenericParameterTypes()/.getGenericReturnType()/.getGenericExcepctionTypes(),

分别获得参数类型/返回值类型/异常类型

package com.Liulian.ReflectionTest;

import java.lang.reflect.*;
import java.util.List;
import java.util.Map;

public class TestReflection02 {
    public static void main(String[] args) throws NoSuchMethodException {
        // 获取参数类型及泛型参数的类型
        Method method01 = Test1.class.getMethod("test01", Map.class, List.class);
        Type[] genericParameterTypes = method01.getGenericParameterTypes();// 获得泛型参数的类型
        for (Type genericParaneterType : genericParameterTypes) {
            System.out.println("#" + genericParaneterType);// 只拿到了方法参数的类型,无法获取参数为泛型时泛型的类型
            if (genericParaneterType instanceof ParameterizedType) {
                Type[] actualTypeArguments = ((ParameterizedType) genericParaneterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }

        }
		// 获取返回值类型
        System.out.println("==========");
        Method method02 =Test1.class.getMethod("test02");
        Type genericReturnType = method02.getGenericReturnType();
        System.out.println(genericReturnType);
        if (genericReturnType instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }
    }
}
class Test1{
    
    public void test01(Map<String,TestReflection01> map,List<TestReflection01> list){
        System.out.println("111");
    }

    public Map<String,Reflector01> test02(){
        System.out.println("123");
        return null;
    }
}

输出结果:

class java.lang.String
class com.Liulian.ReflectionTest.TestReflection01
#java.util.List<com.Liulian.ReflectionTest.TestReflection01>
class com.Liulian.ReflectionTest.TestReflection01
==========
java.util.Map<java.lang.String, com.Liulian.ReflectionTest.Reflector01>
class java.lang.String
class com.Liulian.ReflectionTest.Reflector01

反射操作注解

可用注解模拟数据库表头信息,用于后端开发

获取注解信息

    • Annotation[] annotations = Class对象.getAnnotations()获得该Class对象注解
    • annotations.value()获得注解属性的值
    • Field[] field = Class对象.getDeclaredFields()获得该Field对象
    • Annotation[] annotations = Field对象.getAnnotations()获得该Field对象注解
    • annotations.value()获得属性的值

注解定义:

// 类注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotationType{
    String value();
}

// 属性注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotationFields{
    String columnName();
    String type();
    int length();
}
package com.Liulian.ReflectionTest;

import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.lang.reflect.Type;

public class TestReflection03 {

    public static void main(String[] args) throws ClassNotFoundException {

        Class C1 = Class.forName("com.Liulian.ReflectionTest.Student");

        // 通过反射获取注解
        Annotation[] mat = C1.getAnnotations();
        for (Annotation annotation : mat) {
            System.out.println(annotation);
        }

        // 通过反射获取注解value的值
        MyAnnotationType mat1 = (MyAnnotationType)C1.getAnnotation(MyAnnotationType.class);
        System.out.println(mat1.value());

        // 通过反射获得属性的值
        Field[] maf = C1.getDeclaredFields();
        for (Field field : maf) {
            System.out.println(field);
            MyAnnotationFields myAnnotationFields = field.getAnnotation(MyAnnotationFields.class);
            System.out.println("列名"+myAnnotationFields.columnName());
            System.out.println("类型"+myAnnotationFields.type());
            System.out.println("长度"+myAnnotationFields.length());
        }

    }
}

@MyAnnotationType("db_Student")
class Student{
    @MyAnnotationFields(columnName = "st_id",type = "int",length = 20)
    private int id;
    @MyAnnotationFields(columnName = "st_name",type = "varchar",length = 3)
    private String name;
    @MyAnnotationFields(columnName = "st_number",type = "varchar",length = 11)
    private String number;

    public Student(int id, String name, String number) {
        this.id = id;
        this.name = name;
        this.number = number;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                '}';
    }
}

输出结果:

db_Student
private int com.Liulian.ReflectionTest.Student.id
列名st_id
类型int
长度20
private java.lang.String com.Liulian.ReflectionTest.Student.name
列名st_name
类型varchar
长度3
private java.lang.String com.Liulian.ReflectionTest.Student.number
列名st_number
类型varchar
长度11

参考文献

Java高级特性——反射

【狂神说Java】注解和反射

其他资料来源于网络

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值