熬夜刚完的注解与反射

注解与反射

  • 1.反射的概述
  • 2.反射基本使用
    • 1.获取Class的三种方法
    • 2.通过反射获取构造方法
    • 3.通过反射获取到属性字段
    • 4.通过反射获取方法
  • 3.反射main方法
  • 4.使用反射读取配置文件,调用方法
  • 5.使用反射跳过泛型机制检测
  • 6.注解
    • 1.注解概念
    • 2.jdk自带注解常用
    • 3.自定义开发注解
    • 4.元注解
    • 5.注解+反射案例
    • 1.模仿Spring的自动装配功能
    • 2.获取字段上注解的属性值
  • 7.枚举
    • 用法一:常量
    • 用法二:枚举
    • 用法三:枚举类中添加方法
    • 用法四:使用接口组织枚举

1.反射的概述

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.

反射是比较重要的一个知识,现在的大多数框架都使用得到了反射+注解的形式,对我们的程序进行封装,让我们开箱即用,大大的减少了我们的开发时间,提升了我们的开发效率,使用反射+注解使得我们的程序变得更加的灵活多变

反射就是把java类中的属性和方法映射成与之对应的Java对象,一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把这些组成一个完整类的成员,拆分为一个一个的java对象。
熬夜刚完的注解与反射_第1张图片

熬夜刚完的注解与反射_第2张图片

Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是说jvm中每个实例都应该属于某个Class对象与之对应。(包括基本数据类型)
Class 没有公共构造方法Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。

**我们来看下Class类的一些方法:**当然太多,等下挑常用的讲

Modifier and Type Method and Description
asSubclass(类 clazz) 这个 对象来表示由指定的类对象表示的类的子类。
T cast(Object obj) 施放一个目的是通过本表示的类或接口 对象。
boolean desiredAssertionStatus() 如果要在调用此方法时初始化该类,则返回将分配给此类的断言状态。
static 类 forName(String className) 返回与给定字符串名称的类或接口相关联的 对象。
static 类 forName(String name, boolean initialize, ClassLoader loader) 使用给定的类加载器返回与给定字符串名称的类或接口相关联的 对象。
AnnotatedType[] getAnnotatedInterfaces() 返回一个 AnnotatedType对象的数组, AnnotatedType使用类型指定由此 AnnotatedType对象表示的实体的超级
AnnotatedType getAnnotatedSuperclass() 返回一个 AnnotatedType对象,该对象表示使用类型来指定由此 对象表示的实体的 类。
A getAnnotation(类 annotationClass) 返回该元素的,如果这样的注释 *,*否则返回null指定类型的注释。
Annotation[] getAnnotations() 返回此元素上 存在的注释。
A[] getAnnotationsByType(类 annotationClass) 返回与此元素相关 联的注释
String getCanonicalName() 返回由Java语言规范定义的基础类的规范名称。
[] getClasses() 返回包含一个数组 表示所有的公共类和由此表示的类的成员接口的对象 对象。
ClassLoader getClassLoader() 返回类的类加载器。
getComponentType() 返回 数组的组件类型的Class。
Constructor getConstructor(类... parameterTypes) 返回一个 Constructor对象,该对象反映 Constructor对象表示的类的指定的公共 函数。
Constructor[] getConstructors() 返回包含一个数组 Constructor对象反射由此表示的类的所有公共构造 对象。
A getDeclaredAnnotation(类 annotationClass) 如果这样的注释 直接存在 ,则返回指定类型的元素注释,否则返回null。
Annotation[] getDeclaredAnnotations() 返回 直接存在于此元素上的注释。
A[] getDeclaredAnnotationsByType(类 annotationClass) 如果此类注释 直接存在或 *间接存在,*则返回该元素的注释(指定类型)。
[] getDeclaredClasses() 返回一个反映所有被这个 对象表示的类的成员声明的类和 对象的数组。
Constructor getDeclaredConstructor(类... parameterTypes) 返回一个 Constructor对象,该对象反映 Constructor对象表示的类或接口的指定 函数。
Constructor[] getDeclaredConstructors() 返回一个反映 Constructor对象表示的类声明的所有 Constructor对象的数组
Field getDeclaredField(String name) 返回一个 Field对象,它反映此表示的类或接口的指定已声明字段 对象。
Field[] getDeclaredFields() 返回的数组 Field对象反映此表示的类或接口声明的所有字段 对象。
方法 getDeclaredMethod(String name, 类... parameterTypes) 返回一个 方法对象,它反映此表示的类或接口的指定声明的方法 对象。
方法[] getDeclaredMethods() 返回包含一个数组 方法对象反射的类或接口的所有声明的方法,通过此表示 对象,包括公共,保护,默认(包)访问和私有方法,但不包括继承的方法。
getDeclaringClass() 如果由此 对象表示的类或接口是另一个类的成员,则返回表示其声明的类的 对象。
getEnclosingClass() 返回底层类的即时封闭类。
Constructor getEnclosingConstructor() 如果此对象表示构造函数中的本地或匿名类,则返回表示底层类的立即封闭构造函数的Constructor对象。
方法 getEnclosingMethod() 如果此对象表示方法中的本地或匿名类,则返回表示基础类的即时封闭方法的方法对象。
T[] getEnumConstants() 返回此枚举类的元素,如果此Class对象不表示枚举类型,则返回null。
Field getField(String name) 返回一个 Field对象,它反映此表示的类或接口的指定公共成员字段 对象。
Field[] getFields() 返回包含一个数组 Field对象反射由此表示的类或接口的所有可访问的公共字段 对象。
Type[] getGenericInterfaces() 返回 Type表示通过由该对象所表示的类或接口直接实现的接口秒。
Type getGenericSuperclass() 返回 Type表示此所表示的实体(类,接口,基本类型或void)的直接超类
[] getInterfaces() 确定由该对象表示的类或接口实现的接口。
方法 getMethod(String name, 类... parameterTypes) 返回一个 方法对象,它反映此表示的类或接口的指定公共成员方法 对象。
方法[] getMethods() 返回包含一个数组 方法对象反射由此表示的类或接口的所有公共方法 对象,包括那些由类或接口和那些从超类和超接口继承的声明。
int getModifiers() 返回此类或接口的Java语言修饰符,以整数编码。
String getName() 返回由 对象表示的实体(类,接口,数组类,原始类型或空白)的名称,作为 String
软件包 getPackage() 获取此类的包。
ProtectionDomain getProtectionDomain() 返回 ProtectionDomain
URL getResource(String name) 查找具有给定名称的资源。
InputStream getResourceAsStream(String name) 查找具有给定名称的资源。
Object[] getSigners() 获得这个类的签名者。
String getSimpleName() 返回源代码中给出的基础类的简单名称。
getSuperclass() 返回 表示此所表示的实体(类,接口,基本类型或void)的超类
String getTypeName() 为此类型的名称返回一个内容丰富的字符串。
TypeVariable<类>[] getTypeParameters() 返回一个 TypeVariable对象的数组,它们以声明顺序表示由此 GenericDeclaration对象表示的通用声明声明的类型变量。
boolean isAnnotation() 如果此 对象表示注释类型,则返回true。
boolean isAnnotationPresent(类 annotationClass) 如果此元素上 存在指定类型的注释,则返回true,否则返回false。
boolean isAnonymousClass() 返回 true当且仅当基础类是匿名类时。
boolean isArray() 确定此 对象是否表示数组类。
boolean isAssignableFrom(类 cls) 确定由此 对象表示的类或接口是否与由指定的Class 表示的类或接口相同或是超类或 接口。
boolean isEnum() 当且仅当该类在源代码中被声明为枚举时才返回true。
boolean isInstance(Object obj) 确定指定的Object是否与此 Object表示的对象分配
boolean isInterface() 确定指定 对象表示接口类型。
boolean isLocalClass() 返回 true当且仅当基础类是本地类时。
boolean isMemberClass() 返回 true当且仅当基础类是成员类时。
boolean isPrimitive() 确定指定 对象表示一个基本类型。
boolean isSynthetic() 如果这个类是一个合成类,返回true ; 返回false其他。
T newInstance() 创建由此 对象表示的类的新实例。
String toGenericString() 返回描述此 的字符串,包括有关修饰符和类型参数的信息。
String toString() 将对象转换为字符串。

2.反射基本使用

User:案例类

package test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;


/**
 * Created with IntelliJ IDEA.
 *
 * @Author: compass
 * @Date: 2021-10-23-0:00
 * @Version:1.0
 * @Description:
 */
public class User {

    private int id;
    private int age;
    private String name;
    private String sex;

    public static List<User> getUserList(){
        List<User> userList = new ArrayList<>();
        userList.add(new User(1,29,"小明","男"));
        userList.add(new User(2,21,"小王","男"));
        userList.add(new User(3,18,"小李","女"));
        userList.add(new User(4,23,"小华","女"));
        userList.add(new User(7,50,"小花","女"));
        userList.add(new User(5,50,"小k","男"));
        userList.add(new User(6,60,"小浪","女"));

        return userList;
    }
    public User(int id, int age, String name, String sex) {
        this.id = id;
        this.age = age;
        this.name = name;
        this.sex=sex;
    }
    public User(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }
    public User( String name) {

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

    }
    public String getSex() {
        return sex;
    }

    public void setSex(int id) {
        this.sex = sex;
    }
    public int getId() {
        return id;
    }

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

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    /**
     *  获取一个User对象
     * @return
     */
    public User getUser(){
        return new User(1,29,"小明","男");
    }

}

1.获取Class的三种方法

// 1.使用Object的方法getClass(),Object是所有类的父类,那么任何一个对象,都应该有getClass()方法
        User u = new User();
        Class<? extends User> uClass = u.getClass();

//2.任何数据类型都包含了一个class属性,类型.class的方式
        Class<User> aClass = User.class;
        
//3.常用的 Class.classFrom(String arg),传递一个String类型的从类路径到该类的具体路径
// Class.forName("test.User") : 这个代码会导致我们的静态代码块加载,如果只是想执行某个类的静态代码块,那么可以使用该方法
        Class<?> bClass = Class.forName("test.User");

获取到某个类的Class对象有什么用?

我们可以通过该类的class对象,通过newInstance() 方法对该类进行实例化,不用我们手动去new,注意该方法,默认调用的是无参构造方法,如果一个类没有任何构造方法,默认有一个无参构造,如果你重写了有参构造,记得无参构造一定要带上,否则newInstance() 方法将创建对象失败。

User user = (User)bClass.newInstance();

2.通过反射获取构造方法

   @Test
    void test1() throws Exception{
// 获取共有的构造方法(public进行修饰的方法),并且返回一个 Constructor数组
        Constructor<?>[] constructors = User.class.getConstructors();
        for (Constructor constructor: constructors){
            System.out.println(constructor);
        }
        System.out.println("----------------------------------------------");

// 获取指定某个构造方法,不传递任何参数,默认获取无参构造,如果要获取有参的需要跟上参数类型,必须和构造方法中参数顺序一致
        Constructor<User> constructor1 = User.class.getConstructor(); // 获取无参构造
        System.out.println("无参构造="+constructor1);

        System.out.println("----------------------------------------------");

        Constructor<User> constructor2 = User.class.getConstructor(String.class); // 获取有参构造 User(String name)
        System.out.println("有参构造="+constructor2);

        System.out.println("----------------------------------------------");

// 获取私有、受保护、默认、公有的构造方法,并且返回一个 Constructor数组
        Constructor<?>[] dc1 = User.class.getDeclaredConstructors();
        for (int i = 0; i < dc1.length; i++) {
            System.out.println("private="+dc1[i]);
        }

        System.out.println("----------------------------------------------");

// 获取私有、受保护、默认、公有的构造方法,获取单个的(自定义)
        Constructor dc2 = User.class.getDeclaredConstructor(int.class,String.class);
        System.out.println(dc2);

// 如果该构造方法是私有的,不能直接进行访问,需要先设置为可访问的
        if (!dc2.isAccessible()){ // 先判断该方法是否可以进行访问,如果不可以,进行修改,设置为可访问的
            dc2.setAccessible(true); // 将private修饰的方法修饰为可访问的
        }
// 调用 获取到的构造方法,可以跟参数(不过要和获取到的构造方法中的参数对应,否则抛出:IllegalArgumentException)
        User user1 = (User)dc2.newInstance(1, "张三");
        System.out.println("name="+user1.getName());

    }

3.通过反射获取到属性字段

 @Test // 通过class对象获取到属性
    void test2() throws Exception{
        // 获取到User类中的所有(公共)属性 ,返回一个 Field[]数组
        Field[] fields = User.class.getFields();
        for (Field field : fields) {
            System.out.println("filed = "+field);
        }

        System.out.println("----------------------------------------");

        // 获取到User类中单个属性,返回一个Field对象
        Field emailFiled = User.class.getField("email");
        System.out.println("emailFiled = "+emailFiled);

        System.out.println("----------------------------------------");

        // 获取到User类中的所有属性(私有、受保护、默认)
        Field[] df1 = User.class.getDeclaredFields();
        for (Field field : df1) {
            System.out.println("field = "+field);
        }

        System.out.println("----------------------------------------");

        // 获取到User类中的单个的属性(私有、受保护、默认)
        Field emailAttributes = User.class.getDeclaredField("email");
        System.out.println("emailAttributes ="+emailAttributes);

        System.out.println("----------------------------------------");

        // 通过反射实例化一个对象,并且获取到该类中的字段(email),并且给其赋值
        Class<User> userClass = User.class; // 获得一个User.class对象
        User user = userClass.newInstance();
        Field[] declaredFields = userClass.getDeclaredFields(); // 获取到user类中所有的属性
        for (int i = 0; i < declaredFields.length; i++) { // 遍历User类中所有的属性,找到email属性,为其赋值
          if (declaredFields[i].toString().contains("email")){
              declaredFields[i].setAccessible(true);//设置为可访问的,以免程序出错
              declaredFields[i].set(user,"[email protected]");
          }
        }
        System.out.println(user.email);
    }

4.通过反射获取方法



    @Test // 同过class对象获取到类中的所有方法,以及调用方法
    void test3() throws Exception{

        // 获取到User类中所有的public方法,包括父类的
        Method[] methods = User.class.getMethods();
        for (Method method : methods) {
            System.out.println(" method = "+method);
        }

        System.out.println("----------------------------------------");

        // 获取到User类中单个的public方法,包括父类的
        Method equals = User.class.getMethod("equals", Object.class);
        System.out.println("equals ="+equals);

        System.out.println("----------------------------------------");

        // 获取到User类中所有的(私有、受保护、默认)方法
        Method[] methods2 = User.class.getDeclaredMethods();
        for (Method method : methods2) {
            System.out.println(" method = "+method);
        }

        System.out.println("----------------------------------------");

        // 获取到User类中单个的(私有、受保护、默认)方法
        Method select = User.class.getDeclaredMethod("select", int.class);
        System.out.println("select ="+select);

        // 获取到某个类中的方法,并且查询到select方法,然后执行
        // invoke(调用对象,参数) :注意参数一定要匹配
        Class<User> userClass = User.class;
        User user = userClass.newInstance();
        Method[] dm = userClass.getDeclaredMethods();
        for (int i = 0; i < dm.length; i++) { // 这里怎么多条件只是练习下api而已
            if (dm[i].getName().equals("select")&&dm[i].getParameterCount()==1&& dm[i].getReturnType().getName().equals("java.lang.String")){
                //if (dm[i].toString().contains("select(int)")) // 这样写也是ok的
                dm[i].setAccessible(true); //设置为可访问的
                // executeResult:就是我们方法调用完后的执行结果
                Object executeResult = dm[i].invoke(user, 123456);
                System.out.println("执行结果 = "+executeResult);

            }
        }

    }

3.反射main方法

  public class MyTest {
    public static void main(String[] args) {

        System.out.println("hello world");
    }  
  }
@Test // 反射main方法
    void test4() throws Exception{
        Class<MyTest> testClass = MyTest.class;
        Method main = testClass.getDeclaredMethod("main",String[].class);
        main.setAccessible(true);
        Object args = new String[]{"1"};
        main.invoke(testClass.newInstance(),args);
    }

4.使用反射读取配置文件,调用方法

info.properties:内容 注意idea项目中的当前位置是,当前过程路径下。

user=test.User
method=select

    @Test // 读取配置文件,创建对象,并且调用方法
    void test5() throws Exception{
        Properties properties = new Properties();
        // 获取到配置文件流
        FileReader reader = new FileReader("src/main/resources/info.properties");
        // 将配置文件流加载到 Properties 中
        properties.load(reader);

        // 从info.properties中获取到user的信息,并且得到User的class对象
        Class<?> aClass = Class.forName(properties.get("user").toString());
        // 利用反射实例化User对象
        Object object = aClass.newInstance();
        // 获取到User类中的select方法
        Method select = aClass.getDeclaredMethod(properties.get("method").toString(), int.class);
        // 设置为可访问的
        select.setAccessible(true);
        // 调用select方法,并且获取返回结果
        Object executeResult = select.invoke(object,1);
        System.out.println("executeResult = "+executeResult);

    }

5.使用反射跳过泛型机制检测

    @Test  // 使用反射跳过泛型机制检测
    void test6() throws  Exception{
      ArrayList<String> list = new ArrayList<>();
      list.add("admin");
      list.add("root");

      //list.add(new User(10,20,"admin")); //因为泛型机制的原因,此处不能添加User类型的数据,只能添加String类型的数据
      //获取ArrayList的Class对象,反向的调用add()方法,添加数据
      Class listClass = list.getClass(); 
      //获取add()方法
      Method method = listClass.getMethod("add", Object.class);
      //调用add()方法
      method.invoke(list, new User(10,20,"admin"));
      
      for(Object value : list){
          System.out.println("value = "+value);
      }

    }

6.注解

1.注解概念

注解(注释,标注,Annotation)的作用 ?
如果要对于注解的作用进行分类,我们可以根据它所起的作用,大致可分为三类:
编写文档:通过代码里标识的元数据生成文档。
代码分析:通过代码里标识的元数据对代码进行分析。
编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查。

注解出现的位置
Annotation(注解)是JDK5.0及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。从某些方面看,Annotation 就像修饰符一样被使用,并应用于包、类型、构造方法、方法、成员变量、参数、本地变量的声明中。这些信息被存储在Annotation的“name=value”结构对中。

注解成员属性
Annotation的成员在Annotation类型中以无参数的方法的形式被声明。其方法名和返回值定义了该成员的名字和类型。在此有一个特定的默认 语法:允许声明任何Annotation成员的默认值。一个Annotation可以将name=value对作为没有定义默认值的Annotation 成员的值,当然也可以使用name=value对来覆盖其它成员默认值。这一点有些近似类的继承特性,父类的构造函数可以作为子类的默认构造函数,但是也 可以被子类覆盖。

注解生命周期分类

源码注解:注解只在源码中存在,编译成.class文件就不存在了。
编译时注解:注解在源码和.class文件中都存在。(例如:JDK的三个注解)
运行时注解:在运行阶段还起作用,甚至会影响运行逻辑的注解。

注解不会影响程序代码的执行
Annotation 能被用来为某个程序元素(类、方法、成员变量等)关联任何的信息。需要注意的是,这里存在着一个基本的规则:Annotation不能影响程序代码的执行,无论增加、删除 Annotation,代码都始终如一的执行。另外,尽管一些Annotation通过java的反射api方法在运行时被访问,而java语言解释器在工作时忽略了这些Annotation。正是由于java虚拟机忽略了Annotation,导致了Annotation类型在代码中是“不起作用”的; 只有通过某种配套的工具才会对Annotation类型中的信息进行访问和处理

2.jdk自带注解常用

@Override:表示子类覆盖了父类的方法
@Deprecation:表示方法已经过时,方法上有横线,使用时会有警告。
@author: 标明开发该类模块的作者
@version: 标明该类模块的版本
@see: 参考转向,也就是相关主题
@param: 对方法中某参数的说明
@return: 对方法返回值的说明
@exception: 对方法可能抛出的异常进行说明
@throws: 抛出的异常
@since:描述文本
@FunctionalInterface: 表示该接口是一个函数式接口,并且可以作为Lambda表达式参数传入

@SuppviseWarnings: 表示关闭一些警告信息(通知java编译器忽略特定的编译警告)

SuppviseWarnings:详细参数如下:

属性值 描述
all 抑制所有警告
boxing 抑制装箱、拆箱操作时候的警告
cast 抑制映射相关的警告
dep-ann 抑制启用注释的警告
deprecation 抑制过期方法警告
fallthrough 抑制确在switch中缺失breaks的警告
finally 抑制finally模块没有返回的警告
hiding 抑制相对于隐藏变量的局部变量的警告的步骤
incomplete-switch 忽略没有完整的switch语句
nls 忽略非nls格式的字符
null 忽略对null的操作
rawtypes 使用generics时忽略没有指定相应的类型
restriction 抑制与不鼓励或禁止引用的使用相关的警告
serial 忽略在serializable类中没有声明serialVersionUID变量
static-access 抑制不正确的静态访问方式警告
ic-access 抑制子类没有按最优方法访问内部类的警告
unchecked 抑制没有进行类型检查操作的警告
unqualified-field-access 抑制没有权限访问的域的警告
unused 抑制没被使用过的代码的警告

3.自定义开发注解

自定义注解的语法规则

  • 使用@interface关键字定义注解,注意关键字的位置
    使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。
  • 成员以无参数无异常的方式声明,注意区别一般类成员变量的声明, 其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称
  • 可以使用default为成员指定一个默认值,如上所示
  • 成员属性类型是受限的,合法的类型包括原始类型以及String、Class、Annotation、Enumeration (JAVA的基本数据类型有8种:
    byte(字节)、short(短整型)、int(整数型)、long(长整型)、float(单精度浮点数类型)、double(双精度浮点数类型)、char(字符类型)、boolean(布尔类型)
  • 注解类可以没有成员,没有成员的注解称为标识注解,例如JDK注解中的@Override、@Deprecation
  • 如果注解只有一个成员,并且把成员取名为value(),则在使用时可以忽略成员名和赋值号“=” 如果成员名 不为value,则使用时需指明成员名和赋值号"=",

自定义注解初识

// 自定义一个MyAnnotation注解,该注解中有一个className属性成员
public @interface MyAnnotation {
    String className();
}

在其他类中使用该注解(后面会说到如何去获取注解,以及注解中的属性)

@MyAnnotation(className = "java.lang.reflect.Field")
public class MyTest2 {

注解中的 default关键字:

// 自定义一个MyAnnotation注解
public @interface MyAnnotation {
    String className();
    int userId() default 1004; // 增加一个 userId属性,并且设置默认值为:1024
}

@MyAnnotation(className = "java.util.UUID") // 不写userId也可以,使用我们设置的默认值:1024
class MyAnnotationTest{
    
}

成员属性至有一个,且名称为 value的特性

// 自定义一个MyAnnotation注解
public @interface MyAnnotation {
   String value();
}

//注解中只有一个成员属性,且名称为value,可以不用谢属性名,当然也可以写为:value = "simpleType"
@MyAnnotation(value = "simpleType") 
class MyAnnotationTest{

}

当注解的成员属性是数组的时候

// 自定义一个MyAnnotation注解
public @interface MyAnnotation {
 
   String[] dataType(); //成员属性使用数组的形式
}

// 当注解成员属性是数组时:dataType = {属性值1,属性值2,属性值3,......}
@MyAnnotation(dataType = {"java","php","javaScript"})
class MyAnnotationTest{
 
}

注解中使用枚举类

// 自定义一个MyAnnotation注解
public @interface MyAnnotation {

    Color[] colorType();
}

// 当注解成员属性是数组时:dataType = {属性值1,属性值2,属性值3,......}
@MyAnnotation(colorType={Color.BLACK,Color.GREEN})
class MyAnnotationTest{

}

// 定义颜色枚举类型
enum  Color{
   READ,GREEN,BLACK,WHITE;
}

4.元注解

什么是元注解?简单的来说,就是注解的注解,也就是用来描述我们自定义的注解的注解,听起来有点像套娃的感觉,其实注解,就是一个标签,就像是商品一样,贴上了标签,对这个商品进行描述,比如说:价格呀,生产日期啊等等,我们的注解也是如此,可以对一个类,一个方法,一个属性等等进行描述,我们可以获取到他们上面是否包含有该注解,或者是获取到他们上面的注解来指向某些逻辑,就像是我们的spring框架一样,使用 @Autowired注解一样,让我们的对象进行自动装配,使用注解+反射的开发形式,大大的提高了开发效率,代码的灵活性,可重复性。

1.@Target

@Target说明了Annotation所修饰的对象范围:即注解的作用域,用于说明注解的使用范围(即注解可以用在什么地方,比如类的注解,方法注解,成员变量注解等等)简单的来说,就是你自己定义的注解可以出现在什么位置都是由@Target注解来决定的如果没有使用@Target进行描述:那么自定义的的注解可以应用于程序的任何位置

java.lang.annotation.ElementType这个枚举中规定@Target的取值范围以及作用

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
}

2.@Retention

@Retention定义了该Annotation被保留的时间长短:

  1. 某些Annotation仅出现在源代码中,编译器在编译时丢弃;
  2. 某些Annotation被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,
  3. 某些Annotation在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。
    使用这个meta-Annotation可以对 Annotation的“生命周期”限制。

@Retention的取值是在RetentionPoicy这个枚举中规定的

  • SOURCE:在源文件中有效(即源文件保留)
  • CLASS :在class文件中有效(即class保留)
  • RUNTIME:在运行时有效(即运行时保留)

3.@Inherited

@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的Annotation类型被用于一个class,则这个Annotation将被用于该class的子类。
注意:@Inherited Annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承Annotation,方法并不从它所重载的方法继承Annotation。当@Inherited Annotation类型标注的Annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继 承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的Annotation类型被发现, 或者到达类继承结构的顶层。

@Inherited:也就是说,子类可以继承父类的类上的注解

// 自定义注解
// 自定义一个MyAnnotation注解
@Retention(RetentionPolicy.RUNTIME)
@Inherited // 表示 MyAnnotation 这个注解可以被子类锁继承
public @interface MyAnnotation {

    String name();

}


//测试代码
@MyAnnotation(name = "admin")
public class MyTest2 {
    
    @Test
    void test1() throws Exception{
        // 通过反射获取到 MyTest3的class对象
        Class<MyTest3> aClass = MyTest3.class;
        Annotation[] annotations = aClass.getAnnotations();// 获取自身和父亲的注解
        // 得到注解的类型
        System.out.println(annotations[0].annotationType()); // interface test.MyAnnotation
    }
}
// 此处默认继承父类的 @MyAnnotation(name = "admin")
class MyTest3 extends MyTest2{

}

  1. AnnotatedElement 接口

此类非常重要,我们需要通过反射获取注解,那么久离不开这个接口,我们的Class类是实现了该接口的,那就说明,我们可以通过class对象来获取到class成员上的注解。

来我们看下API:

Modifier and Type Method and Description
T getAnnotation(类 annotationClass) 返回该元素的,如果这样的注释 *,*否则返回null指定类型的注释。
Annotation[] getAnnotations() 返回此元素上 存在的注释。
default T[] getAnnotationsByType(类 annotationClass) 返回与此元素相关 联的注释
default T getDeclaredAnnotation(类 annotationClass) 如果这样的注释 直接存在 ,则返回指定类型的元素注释,否则返回null。
Annotation[] getDeclaredAnnotations() 返回 直接存在于此元素上的注释。
default T[] getDeclaredAnnotationsByType(类 annotationClass) 如果此类注释 直接存在或 *间接存在,*则返回该元素的注释(指定类型)。
default boolean isAnnotationPresent(类 annotationClass) 如果此元素上 存在指定类型的注释,则返回true,否则返回false。

5.注解+反射案例

1.模仿Spring的自动装配功能

​ MyAnnotation :自定义注解

import java.lang.annotation.*;


/**
 * Created with IntelliJ IDEA.
 *
 * @Author: compass
 * @Date: 2021-10-24-19:53
 * @Version:1.0
 * @Description: 自定义注解
 */
// 自定义一个MyAnnotation注解
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target({ElementType.TYPE_USE,ElementType.FIELD})
public @interface MyAnnotation {

    String className()default "";
    Color[] coloType()default Color.RED; //默认红色
    String tableName()default "";
    String attribute()default "";
    String Autowired()default "yes"; // yes表示自动装配,no表示不自动装备

}
enum  Color{
    RED,GREEN,BLACK,BLUE;
}

2.Student:实体类

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: compass
 * @Date: 2021-10-24-21:48
 * @Version:1.0
 * @Description:
 */
public class Student {

    private int id;
    private String name;
    private String email;
    private int status;
    private static Student student;

    public String show(String message){
        System.out.println("Student类的 show(String message) 方法被调用");
        System.out.println("message = "+message);
        return " call success";
    }
    public Student(int id, String name, String email, int status) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.status = status;
    }   public Student() {

    }

    /**
     * 单例模式,双重检测机制(多线程下安全)返回一个student对象
     * @return 返回一个单例的student对象
     */
    public static Student  getInstance(){
        if (student==null){
            synchronized (Student.class){
                if (student==null){
                    student= new Student();
                }
            }
        }
        return student;

    }
    public int getId() {
        return id;
    }

    public void setStudent(Student student) {
        this.student = student;
    }
    public Student getStudent() {
        return student;
    }

    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }

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

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", status=" + status +
                '}';
    }
}

MyTest2:核心方法init()

import org.junit.jupiter.api.Test;
import java.io.FileReader;
import java.lang.reflect.Field;
import java.util.Properties;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: compass
 * @Date: 2021-10-24-20:02
 * @Version:1.0
 * @Description:
 */
@MyAnnotation(Autowired = "yes")
public class MyTest2 {
    @MyAnnotation(Autowired = "yes") // 如果把属性值改为 no,将调用失败
    private static Student student;

    public static void init(){
    // 虽然这种方式比较复杂,也许我没有设计的那么好,但是可以联合注解和反射做一个小练习,
    // 对注解和反射有个理解(当然可以把这段代码写在静态代码块中)
        try {
            Class<MyTest2> aClass = MyTest2.class;
            // 获取到 MyTest2 中的student属性
            Field studentFiled = aClass.getDeclaredField("student");
            studentFiled.setAccessible(true); // 将该字段先设置为可访问,以免后面使用到出错
            // 判断 MyTest2 中的student属性中是否有MyAnnotation注解
            if (studentFiled.isAnnotationPresent(MyAnnotation.class)){
                // 获取到注解中的属性值
                String flag = studentFiled.getAnnotation(MyAnnotation.class).Autowired();
                // 如果注解中的属性值==yes,那么久创建一个Student的实例对象,赋值给student
                if (flag.equals("yes")){
                    // 读取config.properties配置文件中的studentClassName属性值
                    Properties p = new Properties();
                    FileReader configFile = new FileReader("src/main/resources/config.properties");
                    p.load(configFile);
                    String className =  p.get("studentClassName").toString();
                    // 使用反射机制创建一个对象 赋值给student
                    Class<?> forName = Class.forName(className.trim());
                    student = (Student)forName.newInstance();

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("初始化异常");

        }
    }

    @Test
    void test1() throws Exception{
        init(); // 只要是调用成功我们可以直接执行以下代码的
        // 如果断言失败,请在idea中的 jvm option中设置:-ea
        assert student!=null; // 断言判断student是否为空,如果为空抛出 java.lang.AssertionError 异常
        String result = student.show("hello world!");
        System.out.println("call result = "+result);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fbRIdAaR-1635165117962)(C:\Users\14823\AppData\Roaming\Typora\typora-user-images\image-20211024230433211.png)]

当然还有别的更好的办法,话几个小时看了下反射和注解,如果有总结不太到位的地方请多多谅解。

2.获取字段上注解的属性值

import java.lang.annotation.*;


/**
 * Created with IntelliJ IDEA.
 *
 * @Author: compass
 * @Date: 2021-10-24-19:53
 * @Version:1.0
 * @Description: 自定义注解
 */
// 自定义一个MyAnnotation注解
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target({ElementType.TYPE, ElementType.FIELD,ElementType.METHOD})
public @interface MyAnnotation {

    String[] name()default "";
    String tableName()default "";
    String attribute()default "";
    String Autowired()default "yes"; // yes表示自动装配,no表示不自动装备
 


}
// 枚举类
enum  Color{
    RED,GREEN,BLACK,BLUE;
}


// 自定义一个MyAnnotation2注解
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target({ElementType.TYPE, ElementType.FIELD,ElementType.METHOD})
@interface MyAnnotation2 {


    Color[] coloType() default Color.RED; //默认红色


}

package test;

import org.junit.jupiter.api.Test;
import java.io.FileReader;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Properties;


public class MyTest2 {

    @MyAnnotation(Autowired = "yes") // 单个属性
    private static Student student;

    // 使用自定义注解中带有枚举类型数组的属性
    @MyAnnotation2(coloType = {Color.RED,Color.BLACK,Color.GREEN,Color.BLUE}) //枚举类型数组
    String colorType;

    @MyAnnotation(name = {"admin","root","jack"}) //String类型数组
    String name;
   
   

}

class MyTest3{

    // 获取字段属性上枚举类型数组的属性值
    @Test
    void test2() throws Exception {
        Class<MyTest2> aClass = MyTest2.class;
        Field field = aClass.getDeclaredField("colorType");
        MyAnnotation2 annotation = field.getAnnotation(MyAnnotation2.class);
        Color[] colors = annotation.coloType();
        for (Color color : colors) {
            System.out.println(color);
        }
    }

    // 获取字段上String类型数组的属性值
    @Test
    void test3() throws Exception {
        Class<MyTest2> aClass = MyTest2.class;
        Field field = aClass.getDeclaredField("name");
        boolean b = field.isAnnotationPresent(MyAnnotation.class);
        System.out.println(Arrays.toString(field.getDeclaredAnnotation(MyAnnotation.class).name()));
    }

    // 获取字段上单个属性值
    @Test
    void test4() throws Exception{
        Class<MyTest2> aClass = MyTest2.class;
        Field student = aClass.getDeclaredField("student");
        if ( student.isAnnotationPresent(MyAnnotation.class)){
            String value = student.getDeclaredAnnotation(MyAnnotation.class).Autowired();
            System.out.println("value="+value);
        }
    }
}



7.枚举

枚举是一种特殊的常量类,构造方法默认是强制私有化的。(感觉枚举也有点忘了,大概的记录一下,不需要的可以直接跳过)

枚举类型的每个自定义必须采用常量的命名方式,每个常量类型的字段属性写好注释,明确数据的用途【枚举代码编写规范】

用法一:常量

public enum Color {  
  RED, GREEN, BLANK, YELLOW  
} 

用法二:枚举

 
enum  Color{
    RED,GREEN,BLACK,BLUE;
}

@Test
    void test4(){
        Color color = Color.RED;
        switch (color){
            case RED:
                System.out.println("read");break;
            case BLACK:
                System.out.println("black");break;
            case BLUE:
                System.out.println("blue");break;
            default:
                System.out.println("green");
        }
    }

用法三:枚举类中添加方法

public enum Color {
    READ(1,"红色"),GREEN(2,"绿色"),BLACK(3,"黑色"),BLUE(4,"蓝色");
    private String name;
    private int index;

    // 构造方法
    Color(int index, String name) {
        this.index=index;
        this.name=name;
    }

    /**
     *  根据索引获取名称
     * @param index
     * @return
     */
    public static String getName(int index){
        for (Color value : Color.values()) {
            if (value.index==index){
               return  value.name;
            }
        }
        return null;
    }
    public String getName() {
        return name;
    }

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

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    @Override
    public String toString() {
        return "Color{" +
                "name='" + name + '\'' +
                ", index=" + index +
                '}';
    }
}

class MyTest{

    public static void main(String[] args)  {

        // 根据枚举类型字段获取一个枚举类型对象
        Color color = Color.valueOf("READ");
        System.out.println("index="+color.getIndex());
        System.out.println("name="+color.getName());

        // 根据获取到枚举类中的所有属性
        Color[] values = Color.values();
        for (Color value : values) {
            System.out.println("item="+value);
        }
        Color black1 = Color.BLACK;
        Color black2 = Color.BLACK;
        // 是常量,所有无论获取多少次都是相同的,比较可以使用 == ,不必使用 equals
        System.out.println(black1==black2);
        System.out.println(black1.equals(black2));
    }
}

用法四:使用接口组织枚举

 interface Food {
    // 水果
    enum FRUITS implements Food{
        APPLE,BANANA,GRAPE,PEAR
    }
    //喝的
    enum DRINK implements Food{
        WATER, COKE, COFFEE
    }
}
//实现枚举类型接口
class FoodImpl implements Food{

}

class MyTest2{
    public static void main(String[] args) {
        // 可以获取到接口中定义的枚举类型组,以及组中定义的枚举属性值
        Food[] drinks = FoodImpl.DRINK.values();
        System.out.println(Arrays.toString(drinks));

        // 可以获取到接口中定义的枚举类型组,以及组中定义的枚举属性值
        Food[] fruits = FoodImpl.FRUITS.values();
        System.out.println(Arrays.toString(fruits));

    }
}

你可能感兴趣的:(java基础,java,开发语言,后端)