Java中的Junit、类加载时机与机制、反射、注解及枚举

news2024/9/28 6:43:43

目录

Java中的Junit、类加载时机与机制、反射、注解及枚举

Junit

Junit介绍与使用

Junit注意事项

Junit其他注解

类加载时机与机制

类加载时机

类加载器介绍

获取类加载器对象

双亲委派机制和缓存机制

反射

获取类对象

获取类对象的构造方法

使用反射获取的构造方法创建对象

获取类对象的成员方法

使用反射获取的成员方法

获取类对象的成员属性

使用反射获取的成员属性

反射实用案例

注解

介绍

定义注解和属性

注解的使用

解析注解

元注解

枚举

基本使用

枚举中的常用方法


Java中的Junit、类加载时机与机制、反射、注解及枚举

Junit

Junit介绍与使用

在Java中,Junit是一个单元测试框架,可以代替main方法去执行其他的方法,其可以单独执行一个方法,测试该方法是否能跑通

因为Junit是一个第三方工具包,与Lombok一样需要先导入对应的jar包并进行解压,不同的是,Junit有两个包,并且相互依赖,所以都需要进行解压。这里可以使用一个文件夹名为lib,将两个解压包放在lib文件下,再对lib文件夹进行整体Add as Library

使用时,在需要进行测试的方法上方写上@Test注解,此时就会在对应方法所在行左侧显示运行按钮,点击即可运行对应的方法而不会执行其他方法

例如下面的代码:

public class Test01 {
    @Test
    public void method() {
        System.out.println("测试Junit");
    }
}

一个类中可以有多个有@Test方法:

public class Test01 {
    @Test
    public void method() {
        System.out.println("测试Junit");
    }

    @Test
    public void method01() {
        System.out.println("测试Junit-2");
    }
}

如果有多个@Test方法,想要运行所有的@Test方法,就可以点击类名所在行的运行按钮

Junit注意事项

  1. @Test不能修饰静态方法
  2. @Test不能修饰带参数的方法
  3. @Test不能修饰带返回值的方法

例如:

public class Test01 {
    @Test
    public static void method02() {
        System.out.println("测试Junit-3");
    }
}

// 报错:Method 'method02' annotated with '@Test' should be non-static 

Junit其他注解

  1. @Before:被@Before修饰的方法会在每一个@Test方法执行前执行,但是本身不可以单独执行。特点:有多个@Test修饰的方法时会执行多次
  2. @After:被@Before修饰的方法会在每一个@Test方法执行后执行,但是本身不可以单独执行。特点:有多个@Test修饰的方法时会执行多次
  3. @BeforeClass:被@BeforeClass修饰的方法会在所有@Test方法执行前执行,但是本身不可以单独执行。特点:不论是否有多个@Test修饰的方法,该方法始终只会执行一次,并且被修饰的方法必须为静态方法
  4. @AfterClass:被@AfterClass修饰的方法会在所有@Test方法执行后执行,但是本身不可以单独执行。特点:不论是否有多个@Test修饰的方法,该方法始终只会执行一次,并且被修饰的方法必须为静态方法

例如:

public class Test01 {
    @Test
    public void method() {
        System.out.println("测试Junit");
    }

    @Test
    public void method01() {
        System.out.println("测试Junit-2");
    }


    @Before
    public void before() {
        System.out.println("before");
    }

    @After
    public void after() {
        System.out.println("after");
    }

    @BeforeClass
    public static void beforeClass() {
        System.out.println("beforeClass");
    }

    @AfterClass
    public static void afterClass() {
        System.out.println("afterClass");
    }
}

输出结果:
beforeClass
before
测试Junit
after
before
测试Junit-2
after
afterClass

类加载时机与机制

类加载时机

在Java中,以下五种情况中的一种情况出现,JVM就会将class文件加载进入内存:

  1. 实例化对象
  2. 实例化子类对象或者接口实现类对象(此时创建子类对象会初始化父类)
  3. 执行main方法
  4. 直接使用类名调用静态成员
  5. 使用反射创建class对象

因为class也属于文件,所以JVM也需要使用IO流中的操作将class文件读取到内存,但并不是JVM直接加载,而是使用一个名为ClassLoader的类加载器进行加载

类加载器介绍

下面的类加载器将基于JDK8分析

JVM会使用ClassLoader的类加载器加载类,在Java中,类加载器有以下三种:

  1. BootStrapClassLoader:根类加载器,也称为引导类加载器,主要加载Java中的核心类,例如System类、String类等(在路径jre/lib/rt.jar下)
  2. ExtClassLoader:扩展类加载器,负责jre的扩展目录中的jar包的加载(在路径jdk\jre\lib\ext下)
  3. AppClassLoader:系统类加载器,负责在JVM启动时加载来自Java命令的class文件(可以理解为自定义类),以及classPath环境变量所指定的jar包(可以理解为第三方jar包)

在Java中,三种不同的类加载器会加载不同的类,而其三者的关系「从类加载机制层面」如下:

AppClassLoader的父类加载器是ExtClassLoader

ExtClassLoader的父类加载器是BootStrapClassLoader

需要注意,他们从代码级别上来看,没有子父类继承关系,但是他们都有一个共同的父类,即 ClassLoader
并且 BootStrapClassLoader是无法直接获取到的,因为其底层是C语言编写的
// AppClassLoader类声明
public class AppletClassLoader extends URLClassLoader
// ExtClassLoader类声明
static class ExtClassLoader extends URLClassLoader

// 拥有共同的父类
public class URLClassLoader extends SecureClassLoader implements Closeable
public class SecureClassLoader extends ClassLoader

获取类加载器对象

在Java代码中,可以通过下面的两个方法获取类加载器对象:

  1. Class对象中的方法:getClassLoader(),使用时:类名.class.getClassLoader(),作用是获取某一个使用的类加载器
  2. ClassLoader类中的方法:getParent(),使用时:ClassLoader对象.getParent(),作用是获取指定类加载器的父类加载器
public class Test {
    public static void main(String[] args) {
        // 自定义类
        ClassLoader classLoader = Test.class.getClassLoader();
        System.out.println(classLoader);
        // 第三方类
        ClassLoader classLoader1 = IOUtil.class.getClassLoader();
        System.out.println(classLoader1);
        // 获取二者的父类
        System.out.println(classLoader.getParent());
        if (classLoader1 != null) {
            System.out.println(classLoader1.getParent());
        } else {
            System.out.println("父类加载器无法获取");
        }
        // 系统类
        ClassLoader classLoader2 = Scanner.class.getClassLoader();
        System.out.println(classLoader2);
    }
}

输出结果:
sun.misc.Launcher$AppClassLoader@18b4aac2
null
sun.misc.Launcher$ExtClassLoader@4554617c
父类加载器无法获取
null

双亲委派机制和缓存机制

双亲委派机制,也称为全盘负责委托机制,表示类加载器在加载类时,首先不会自己去尝试加载这个类,而是把请求交给父类加载器来完成,每一层的类加载器都会遵循这样的规则。

类加载器的缓存机制:一个类加载到内存之后,缓存中也会保存一份儿,后面如果再使用此类,如果缓存中保存了这个类,就直接返回他;如果没有才加载这个类,下一次如果有其他类在使用的时候就不会重新加载了,直接去缓存中拿

类加载器加载类的过程图如下:

图解析:

一个类准备加载进内存时,首先遇到的类加载器就是AppClassLoader,如果AppClassLoader中的缓存已经存在该类,则直接返回该类,否则向上找类加载器ExtClassLoader,同样,如果ExtClassLoader的缓存已经存在该类,则直接返回,否则向上找类加载器BootStrapClassLoader,如果BootStrapClassLoader缓存依旧没有该类,则判断类是否是该类加载需要加载的,不是继续向下直到遇到属于加载该类的类加载器

所以,类加载器的双亲委派和缓存机制共同造就了加载类的特点:保证了类在内存中的唯一性

反射

在Java中,反射的作用是获取类对象,通过这个类对象获取对应类中的成员属性(重新赋值)、成员方法(调用方法)和构造方法(实例化对象)

类对象:在Java中,一切皆是对象,而class文件加载进内存就会生成对应的对象,该对象就被称为class对象

Class类:描述Class对象就是Class

同样,对于成员变量、成员方法和构造方法来说也有对应的对象和类:

  1. 成员变量:对应的成员变量对象为Field对象,描述Field对象的类称为Field
  2. 成员方法:对应的成员方法对象为Method对象,描述Method对象的类称为Method
  3. 构造方法:对应的构造方法对象为Constructor对象,描述Constructor对象的类称为Constructor

获取类对象

获取类对象是反射成立的第一步,而一共有三种方式获取类对象:

  1. 调用Object中的方法:Class <?> getClass(),该方法返回一个类对象
  2. 通过class成员获取类对象:基本数据类型/引用数据类型.class
  3. Class类中的静态方法:static Class<?> forName(String className),该方法的参数为类的全限定名,该方法返回一个类对象
类的全限定名即为类所在的包名,即 package后面的内容+指定类名

例如,下面的代码:

public class Test {
    public static void main(String[] args) throws Exception{
        // 1. 调用Object中的方法:Class <?> getClass(),该方法返回一个类对象
        Scanner scanner = new Scanner(System.in);
        Class<? extends Scanner> aClass = scanner.getClass();
        System.out.println(aClass);
        // 2. 通过class成员获取类对象:基本数据类型/引用数据类型.class
        Class<Scanner> scannerClass = Scanner.class;
        System.out.println(scannerClass);
        // 3. Class类中的静态方法:static Class<?> forName(String className),该方法的参数为类的全限定名,该方法返回一个类对象
        Class<?> aClass1 = Class.forName("com.epsda.advanced.test_reflect.Test");
        System.out.println(aClass1);
    }
}

输出结果:
class java.util.Scanner
class java.util.Scanner
class com.epsda.advanced.test_reflect.Test

在IDEA中获取类的全限定名:

  1. 右键需要全限定名的类->选择Copy Path/Reference...->选择Copy Reference
  2. forName方法中输入需要全限定名的类名,按下Tab或者Enter
如果按住Ctrl+鼠标左键点击类的全限定名可以跳转到指定类时,说明全限定名正确

在实际开发中最常用的获取类对象的方式是第二种,但是最通用的方式是第三种,因为第三种的参数是String类型,后面可以结合配置文件xxx.propertiesProperties集合中的load方法加载类的全限定名

获取类对象的构造方法

获取类对象的构造方法一共有四种方法:

  1. 获取类对象中所有public构造方法:使用Class类中的方法:Constructor<?>[] getConstructors(),该方法返回一个构造方法类的对象
  2. 获取类对象中指定的public构造方法:Class类中的方法:Constructor<T> getConstructor (Class<?>... parameterTypes),该方法的参数为指定的public构造方法参数类型对应的类对象,返回一个构造方法类的对象。如果指定的public构造方法没有参数,则可以不传递任何内容
  3. 获取类对象中所有构造方法(包括publicprivate):Constructor<?>[] getDeclaredConstructors(),该方法返回一个构造方法类的对象
  4. 获取类对象中指定的构造方法(包括publicprivate):Constructor<T> getDeclaredConstructor (Class<?>... parameterTypes),该方法的参数为指定的构造方法的参数,返回一个构造方法类的对象。如果指定的构造方法没有参数,则可以不传递任何内容

下面示例使用的类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    private String name;
    public Integer age;

    private Person(String name) {
        this.name = name;
    }
}

使用如下:

public class Test01 {
    public static void main(String[] args) throws Exception{
        Class<Person> personClass = Person.class;

        // 获取所有public构造方法
        Constructor<?>[] constructors = personClass.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }

        // 获取指定public构造方法
        Constructor<Person> constructor = personClass.getConstructor(String.class, Integer.class);
        System.out.println(constructor);

        // 获取所有构造方法
        Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }

        // 获取指定的构造方法
        Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor(String.class);
        System.out.println(declaredConstructor);
    }
}

使用反射获取的构造方法创建对象

使用Constructor类中的方法: T newInstance(Object...initargs),参数传递对应对象初始值,如果获取到的是无参构造,则参数不传递,否则传递对应的值,该方法返回一个对应类的对象

public class Test02 {
    public static void main(String[] args) throws Exception{
        Class<Person> personClass = Person.class;
        Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor(String.class, Integer.class);
        Person person = declaredConstructor.newInstance("张三", 16);
        System.out.println(person);
    }
}

如果获取到的是无参构造,则可以直接使用类对象.newInstance(),简写为如下的代码:

public class Test03 {
    public static void main(String[] args) throws Exception{
        // 无参构造默认反射方式
        Class<Person> personClass = Person.class;
        Constructor<Person> constructor = personClass.getConstructor();
        Person person = constructor.newInstance();
        System.out.println(person);

        // 简写为
        Person person1 = personClass.newInstance();
        System.out.println(person);
    }
}
但是,上面的简写形式已经被弃用(修饰为 @Deprecated),不过依旧可以使用

如果获取到的是私有构造方法,则需要使用Constructor的父类AccessibleObject中的方法void setAccessible(boolean flag)将私有构造方法的权限修改为public,参数有两个值:true代表修改,false表示不修改

public class Test04 {
    public static void main(String[] args) throws Exception{
        Class<Person> personClass = Person.class;
        Constructor<Person> personConstructor = personClass.getDeclaredConstructor(String.class);
        // 修改权限
        personConstructor.setAccessible(true);
        Person person = personConstructor.newInstance("张三");
        System.out.println(person);
    }
}

上面获取私有构造方法并创建对象也被称为暴力反射

获取类对象的成员方法

获取类对象的成员方法一共有四种方法:

  1. 获取类对象中所有public成员方法:使用Class类中的方法:Method[] getMethods(),该方法返回一个成员方法类的对象
  2. 获取类对象中指定的public成员方法:Class类中的方法:Method getMethod (String name, Class<?>... parameterTypes),该方法的第一个参数为指定的public成员方法名,第二个参数为指定的public成员方法参数类型对应的类对象,返回一个成员方法类的对象。如果指定的public成员方法没有参数,则第二个参数可以不传递任何内容
  3. 获取类对象中所有成员方法(包括publicprivateprotected):Method[] getDeclaredMethods(),该方法返回一个成员方法类的对象
  4. 获取类对象中指定的成员方法(包括publicprivate):Method getDeclaredMethod (String name, Class<?>... parameterTypes),该方法的参数为指定的成员方法的参数,返回一个成员方法类的对象。如果指定的public成员方法没有参数,则第二个参数可以不传递任何内容
需要注意, getMethods方法会获取到本类和其父类的所有 public方法,但是 getDeclaredMethods方法只会获取到本类中的 privatepublicprotected方法

例如下面的代码:

public class Test05 {
    public static void main(String[] args) throws Exception{
        Class<Person> personClass = Person.class;
        // 获取所有public方法
        Method[] methods = personClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }

        // 获取指定的public方法
        Method method = personClass.getMethod("setName", String.class);
        System.out.println(method);

        // 获取所有的方法
        Method[] declaredMethods = personClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod);
        }

        // 获取指定的方法
        Method walk = personClass.getDeclaredMethod("walk");
        System.out.println(walk);
    }
}

使用反射获取的成员方法

使用成员方法类对象调用Object类中的方法Object invoke(Object obj, Object... args)可以使用获取到的成员方法,第一个参数传递成员方法所在类的对象,第二个参数传递获取到的成员方法参数对应的值,该方法返回一个Object对象。该方法的返回值根据调用对象对应的方法是否有返回值决定,如果调用对象对应的方法有返回值,则与调用对象对应的方法的值相同,否则为null

public class Test06 {
    public static void main(String[] args) throws Exception{
        Class<Person> personClass = Person.class;
        Person person = personClass.newInstance();
        Method setName = personClass.getDeclaredMethod("setName", String.class);
        Method getName = personClass.getDeclaredMethod("getName");
        Object set = setName.invoke(person, "张三");
        Object get = getName.invoke(person);
        System.out.println(get);
    }
}

如果需要操作类对象中的私有方法,与私有构造方法一样,需要使用AccessibleObject中的方法void setAccessible(boolean flag)将私有构造方法的权限修改为public

public class Test06 {
    public static void main(String[] args) throws Exception{
        Class<Person> personClass = Person.class;
        Person person = personClass.newInstance();

        Method declaredMethod = personClass.getDeclaredMethod("walk");
        declaredMethod.setAccessible(true);
        declaredMethod.invoke(person);
    }
}

获取类对象的成员属性

获取类对象的构造方法一共有四种方法:

  1. 获取类对象中所有public成员属性:使用Class类中的方法:Field[] getFields(),该方法返回一个成员属性类的对象
  2. 获取类对象中指定的public成员属性:Class类中的方法:Field getField(String name),该方法的参数为指定的public成员属性名,返回一个成员属性类的对象
  3. 获取类对象中所有成员属性(包括publicprivate):Field[] getDeclaredFields(),该方法返回一个成员属性类的对象
  4. 获取类对象中指定的成员属性(包括publicprivateprotected):Field getDeclaredField(String name),该方法的参数为指定的public成员属性名,返回一个成员属性类的对象

例如:

public class Test07 {
    public static void main(String[] args) throws Exception{
        Class<Person> personClass = Person.class;

        // 获取所有public成员属性
        Field[] fields = personClass.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }

        // 获取指定的public成员属性
        Field age = personClass.getField("age");
        System.out.println(age);

        // 获取所有成员属性
        Field[] declaredFields = personClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
        }

        // 获取指定成员属性
        Field declaredField = personClass.getDeclaredField("name");
        System.out.println(declaredField);
    }
}

使用反射获取的成员属性

使用成员属性类对象调用Object中的方法:void set(Object obj, Object value)为获取到的成员属性赋值,第一个参数传递成员属性所在类的对象,第二个参数传递属性值

使用成员属性类对象调用Object中的方法:Object get(Object obj)获取指定成员属性的值,参数传递成员属性所在类的对象,该方法返回一个Object对象,该对象中的值即为成员属性的值

public class Test08 {
    public static void main(String[] args) throws Exception{
        Class<Person> personClass = Person.class;
        Person person = personClass.newInstance();
        Field age = personClass.getField("age");
        age.set(person, 18);
        Object o = age.get(person);
        System.out.println(o);
    }
}

同样,如果是私有成员,则需要使用AccessibleObject中的方法void setAccessible(boolean flag)将私有构造方法的权限修改为public

public class Test08 {
    public static void main(String[] args) throws Exception{
        Class<Person> personClass = Person.class;
        Person person = personClass.newInstance();

        Field declaredField = personClass.getDeclaredField("name");
        declaredField.setAccessible(true);
        declaredField.set(person, "张三");
        Object o1 = declaredField.get(person);
        System.out.println(o1);
    }
}

反射实用案例

在配置文件中,配置类的全限定名,以及配置一个方法名,通过解析配置文件,让配置好的方法执行起来,配置文件的内容如下:

className=包名.Person
methodName=walk

步骤:

  1. 创建properties配置文件,配置信息,需要注意,这个配置文件不能直接放到模块或者项目下,否则在out文件夹中不存在该文件,最常见的做法是在指定目录下创建一个名为resources文件夹,并将这个文件夹标记为Resources Root,然后将配置文件放在这个文件夹内部
  2. 读取配置文件,解析配置文件。读取配置文件可以使用properties集合中的load方法,解析配置文件时不建议直接在创建IO流对象时传递properties文件的地址,这样导致该地址为死地址,而且out文件夹下不会存在resources文件夹。推荐方法:因为配置文件也属于文件,在Java中加载该文件时会产生对应的对象,使用ClassLoader获取当前类的类加载器对象,再使用该对象调用getResourceAsStream ("配置文件名")方法获取InputStream对象。这种方式会自动扫描resources下的文件,可以简单理解为扫描out路径下的配置文件
  3. 根据解析出来的className,创建Class对象
  4. 根据解析出来的methodName,获取对应的方法
  5. 执行方法

IDEA中「将这个文件夹标记为Resources Root」的步骤图:

如果上面的方式中没有显示Resources Root,则可以考虑下面的步骤:

参考代码如下:

// 配置文件
className=com.epsda.advanced.test_reflect_exercise.Person
methodName=walk

// 自定义类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    private String name;
    private Integer age;

    public void walk() {
        System.out.println("人在行走");
    }
}

// 测试
package com.epsda.advanced.test_reflect_exercise;

import org.junit.Test;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * ClassName: Test09
 * Description: 测试
 *
 * @author 憨八嘎
 * @version 1.0
 */
public class Test09 {
    @Test
    public void method () throws Exception{
        ClassLoader classLoader = Test09.class.getClassLoader();
        InputStream resourceAsStream = classLoader.getResourceAsStream("test.properties");
        // 读取配置文件
        Properties properties = new Properties();
        properties.load(resourceAsStream);
        // 根据key取出其中的值
        String methodName = properties.getProperty("methodName");
        String className = properties.getProperty("className");
        // 创建Class对象
        Class<?> aClass = Class.forName(className);
        Object o = aClass.newInstance();
        Method declaredMethod = aClass.getDeclaredMethod(methodName);
        declaredMethod.invoke((Person)o);
    }
}

输出结果:
人在行走

目录结构:

注解

介绍

在Java中,注解也是一种引用数据类型,与类、接口、枚举和Record类同层次

注解常见的作用如下:

  1. 说明:对代码进行说明,生成doc文档(API文档)
  2. 检查:检查代码是否符合条件,例如:@Override@FunctionalInterface
  3. 分析:对代码进行分析,起到了代替配置文件的作用

JDK中常见的注解:

  1. @Override:检测此方法是否为重写方法
    • JDK5版本,支持父类的方法重写
    • JDK6版本,支持接口的方法重写
  2. @Deprecated:表示方法已经过时,不推荐使用,但是依旧可以使用
  3. @SuppressWarnings:消除警告,例如消除所有警告:@SuppressWarnings("all")

在IDEA中,一般被警告的方法默认会有黄色底色,例如下图:

定义注解和属性

在Java中,可以使用下面的格式定义注解:

public @Interface 注解名 {
    // 属性
}

在注解体内的属性,本质是抽象方法,但是在使用时,与成员属性相同,使用成员属性名=值的方式

属性的定义有两种方式:

  1. 数据类型 属性名():定义一个没有默认值的属性,使用注解时就必须赋值
  2. 数据类型 属性名() default 值:定义一个有默认值的属性,使用注解时可以不需要赋值

可以作为属性的类型:

  1. 所有基本数据类型
  2. String类型
  3. 枚举类型
  4. 注解类型
  5. Class类型
  6. 上面所有类型的一维数组(不可以是二维数组)

例如:

public @interface Book {
    //书名
    String bookName();
    //作者
    String[] author();
    //价格
    int price();
    //数量
    int count() default 10;
}

注解的使用

使用注解本质就是为每一个属性(抽象方法)赋值,一般使用位置有下面几种:

  1. 类名上
  2. 方法上
  3. 成员变量上
  4. 局部变量上
  5. 参数位置

使用格式如下:

  1. 普通属性:@注解名(属性名 = 值, 属性名 = 值...)
  2. 属性中有数组:@注解名(属性名 = {元素1,元素2...})

注解使用时需要注意:

  1. 空注解(注解中没有任何的属性)可以直接使用
  2. 不同的位置可以使用一样的注解,但是同样的位置不能使用一样的注解
  3. 使用注解时,如果此注解中有属性没有默认值,则注解中对应的属性一定要赋值。如果有多个属性,用,隔开;如果注解中的属性值有默认值,那么不用显示写,也不用重新赋值
  4. 如果注解中的属性有数组,使用{}
  5. 如果注解中只有一个属性,并且属性名叫value,那么使用注解的时候,属性名不用写,直接写值(包括普通类型和数组)

例如:

// 自定义注解
public @interface Book {
    //书名
    String bookName();
    //作者
    String[] author();
    //价格
    int price();
    //数量
    int count() default 10;
}

// 测试
@Book(bookName = "寓言故事",author = {"张三","李四"},price = 10,count = 20)
public class BookShelf {
}

解析注解

解析注解即为取出注解对应属性的值,注解涉及的接口是:AnnotatedElement接口,实现类有: AccessibleObjectClassConstructorExecutableFieldMethodPackageParameter

解析思路如下:

  1. 判断指定位置上有没有使用指定的注解:使用方法:boolean isAnnotationPresent(Class<? extends Annotation> annotationClass),如果存在注解则返回为true,否则返回false
  2. 如果有,则获取指定的注解:使用方法:getAnnotation(Class<T> annotationClass)
  3. 通过注解获取指定的值

例如:

// 自定义注解
public @interface Book {
    //书名
    String bookName();
    //作者
    String[] author();
    //价格
    int price();
    //数量
    int count() default 10;
}

// 自定义类
@Book(bookName = "寓言故事",author = {"张三","李四"},price = 10,count = 20)
public class BookShelf {
}

// 测试
public class Test01 {
    public static void main(String[] args) {
        //1.获取BookShelf的class对象
        Class<BookShelf> bookShelfClass = BookShelf.class;
        //2.判断bookShelf上有没有Book注解
        boolean b = bookShelfClass.isAnnotationPresent(Book.class);
        //3.判断,如果b为true就获取
        if (b) {
            Book book = bookShelfClass.getAnnotation(Book.class);
            System.out.println(book.bookName());
            System.out.println(Arrays.toString(book.author()));
            System.out.println(book.price());
            System.out.println(book.count());
        }
    }
}

上面的代码没有在控制台中打印运行结果,原因是注解并没有在内存中出现,而class文件在内存中运行,所以导致方法无法获取到对应的注解

元注解

元注解也是注解,这个注解用来管理其他注解,一般管理下面的方面:

  1. 控制注解的使用位置
    1. 控制注解是否能在类上使用
    2. 控制注解是否能在方法上使用
    3. 控制注解是否能在构造上使用等
  2. 控制注解的生命周期(加载位置)
    • 控制注解是否能在源码中出现
    • 控制注解是否能在class文件中出现
    • 控制注解是否能在内存中出现

使用元注解:

  1. 注解@Target:控制注解的使用位置,其属性是个枚举数组ElementType[] value();,枚举的成员可以类名直接调用。常见的成员有:
    • TYPE:控制注解能使用在类上
    • FIELD:控制注解能使用在属性上
    • METHOD:控制注解能使用在方法上
    • PARAMETER:控制注解能使用在参数上
    • CONSTRUCTOR:控制注解能使用在构造上
    • LOCAL_VARIABLE:控制注解能使用在局部变量上
  2. 注解@Retention:控制注解的生命周期(加载位置),其属性是一个枚举对象RetentionPolicy,常见的成员有:
    • SOURCE:控制注解能在源码中出现(默认)
    • CLASS:控制注解能在class文件中出现
    • RUNTIME:控制注解能在内存中出现
需要注意, @Target如果不指定属性,默认是全部可用

使用元注解就可以解决前面解析注解部分无法读取到注解的问题:

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
    //书名
    String bookName();
    //作者
    String[] author();
    //价格
    int price();
    //数量
    int count() default 10;
}

// 测试
public class Test01 {
    public static void main(String[] args) {
        //1.获取BookShelf的class对象
        Class<BookShelf> bookShelfClass = BookShelf.class;
        //2.判断bookShelf上有没有Book注解
        boolean b = bookShelfClass.isAnnotationPresent(Book.class);
        //3.判断,如果b为true,就获取
        if (b){
            Book book = bookShelfClass.getAnnotation(Book.class);
            System.out.println(book.bookName());
            System.out.println(Arrays.toString(book.author()));
            System.out.println(book.price());
            System.out.println(book.count());
        }
    }
}

输出结果:
寓言故事
[张三, 李四]
10
20

枚举

基本使用

枚举属于五大引用数据类型中的一种:类、数组、接口、注解、枚举

定义枚举格式如下:

public enum 枚举类名{
  
}

在Java中,所有枚举的父类都是Enum

枚举的特点如下:

  1. 每一个枚举都是static final,但是定义枚举是不能显示写出
  2. 每一个枚举由逗号分隔
  3. 写完所有的枚举值之后,最后一个枚举后方需要加;
  4. 枚举值名字最好大写
  5. 使用时使用枚举类名直接调用枚举成员
  6. 枚举中的成员都是当前枚举类类型的对象
  7. 枚举类中的构造方法都是private修饰

基本使用如下:

public enum Status {
    RUNNING,
    WAITING,
    STOPPED;
}

如果想为每一个枚举赋值,可以在枚举类中定义成员和构造方法,并对外提供获取方法就可以获取到每一个枚举值对应的值

public enum Status {
    RUNNING("运行"),
    WAITING("等待"),
    STOPPED("暂停");

    private String name;

    private Status(String name) {
        this.name = name;
    }
}

测试如下:

public class Test {
    public static void main(String[] args) {
        System.out.println(Status.RUNNING);
        System.out.println(Status.RUNNING.getName());
    }
}

输出结果:
RUNNING
运行

枚举中的常用方法

方法名

说明

String toString()

返回枚举值的名字

values()

返回所有与的枚举值

valueOf(String str)

将一个字符串转成枚举类型

例如下面的代码:

// 枚举
public enum Status {
    RUNNING("运行"),
    WAITING("等待"),
    STOPPED("暂停");

    private String name;

    private Status(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

// 测试
public class Test01 {
    public static void main(String[] args) {
        Status status = Status.RUNNING;

        System.out.println(status.toString());
        System.out.println(status);

        Status[] values = Status.values();
        for (Status value : values) {
            System.out.println(value);
        }

        Status status1 = Status.valueOf("RUNNING");
        System.out.println(status1);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2172736.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无环SLAM系统集成后端回环检测模块(loop):SC-A-LOAM以及FAST_LIO_SLAM

最近在研究SLAM目标检测相关知识&#xff0c;看到一篇论文&#xff0c;集成了SC-A-LOAM作为后端回环检测模块&#xff0c;在学习了论文相关内容后决定看一下代码知识&#xff0c;随后将其移植&#xff0c;学习过程中发现我找的论文已经集成了回环检测模块&#xff0c;但是我的另…

Postgresql源码(136)syscache/relcache 缓存及失效机制

相关 《Postgresql源码&#xff08;45&#xff09;SysCache内存结构与搜索流程分析》 0 总结速查 syscache&#xff1a;缓存系统表的行。通用数据结构&#xff0c;可以缓存一切数据&#xff08;hash dlist&#xff09;。可以分别缓存单行和多行查询。 syscache使用CatCache数…

Hadoop框架及应用场景说明

Hadoop是一个开源的分布式系统基础架构。由多个组件组成&#xff0c;组件之间协同工作&#xff0c;进行大规模数据集的存储和处理。 本文将探讨Hadoop的架构以及应用场景。 一Hadoop框架 Hadoop的核心组件包含&#xff1a; 1. Hadoop分布式文件系统&#xff08;HDFS&#xff…

windows10使用bat脚本安装前后端环境之msyql5.7安装配置并重置用户密码

首先需要搞清楚msyql在本地是怎么安装配置、然后在根据如下步骤编写bat脚本&#xff1a; 思路 1.下载mysql5.7 zip格式安装包 2.新增data文件夹与my.ini配置文件 3.初始化数据库 4.安装mysql windows服务 5.启动并修改root密码&#xff08;新增用户初始化授予权限&#xff09…

浅拷贝深拷贝

&#x1f4cb;目录 &#x1f4da;引入&#x1f4da;浅拷贝&#x1f4d6;定义&#x1f4d6;实现方式&#x1f4d6;特点 &#x1f4da;深拷贝&#x1f4d6; 定义&#x1f4d6;实现方式&#x1f4d6;特点 &#x1f4da;拓展&#x1f4d6;Object类✈️toString()方法✈️equals()方…

预防工作场所的违规政策

违规政策是指未经管理层制定或批准的工作场所政策。 它们也可能直接违反公司政策。如果管理不善&#xff0c;这些政策可能会对您的业务产生负面影响。 最常见的流氓政策来源是 试图绕过现有政策框架的员工&#xff0c;或 经理们未经高层领导批准&#xff0c;擅自制定自己的…

《凡人歌》中的IT职业启示录

《凡人歌》是由中央电视台、正午阳光、爱奇艺出品&#xff0c;简川訸执导&#xff0c;纪静蓉编剧&#xff0c;侯鸿亮任制片&#xff0c;殷桃、王骁领衔主演&#xff0c;章若楠、秦俊杰、张哲华、陈昊宇主演的都市话题剧 &#xff0c;改编自纪静蓉的小说《我不是废柴》。该剧于2…

基础漏洞——SSTI(服务器模板注入)

一.SSTI&#xff08;服务器模板注入&#xff09;的出现,框架漏洞 首先可以通过SSTI(Server-Side Template Injection)从名字可以看出即是服务器端模板注入。有些框架一般都采用MVC的模式。 用户的输入先进入Controller控制器&#xff0c;然后根据请求类型和请求的指令发送给对…

解决 Java 中由于 parallelStream 导致的死锁

并发性是软件开发的福音&#xff0c;也是祸根。通过并行处理提高性能的承诺与错综复杂的挑战相伴而生&#xff0c;例如臭名昭著的死锁。死锁是多线程编程世界中的隐患&#xff0c;它甚至可以使最强大的应用程序陷入瘫痪。它描述了两个或多个线程永远被阻塞&#xff0c;相互等待…

FOC矢量控制

目录 前言一、FOC简介1.1 FOC是什么1.2 FOC框图介绍 二、FOC坐标变换2.1 电流采集2.2 Clarke变换2.3 Park变换 三、闭环控制3.1 电流环控制3.3 速度环控制3.4 位置环控制 四、SVPWM原理4.1 空间矢量合成4.2 SVPWM法则4.3 MOS开关方式4.4 矢量作用时间 前言 本文主要介绍无刷直流…

未来医疗:从医技数字化2.0到全局变革

斯蒂芬•申弗&#xff08;Stephen C. Schimpff&#xff09;的《医疗大趋势&#xff1a;明日医学》被认为是全球第一本系统介绍未来医疗的权威著作。在书中&#xff0c;作者认为基因组学、手术技术革新、干细胞、数字医疗等关键技术将驱动医疗变革的发生&#xff0c;全面提升人类…

OpenAI o1-preview:详细分析

OpenAI 终于打破沉默&#xff0c;发布了万众期待的 “o1-preview”。其中有很多内容值得解读。 作为一家以 LLM 为生的人工智能初创公司&#xff0c;我们想知道这个新模型的性能如何&#xff0c;以及它能如何帮助我们改进产品。 因此&#xff0c;我花了一整天的时间来实验这个…

(JAVA)队列 和 符号表 两种数据结构的实现

1. 队列 1.1 队列的概述 队列是一种基于先进先出&#xff08;FIFO&#xff09;的数据结构&#xff0c;是一种只能在一端进行插入&#xff0c;在另一端进行删除操作的特殊线性表。 它按照先进先出的原则存储数据&#xff0c;先进入的数据&#xff0c;在读取时先被读出来 1.2 …

蓝桥杯【物联网】零基础到国奖之路:十二. TIM

蓝桥杯【物联网】零基础到国奖之路:十二. TIM 第一节 理论知识第二节 cubemx配置 第一节 理论知识 STM32L071xx器件包括4个通用定时器、1个低功耗定时器&#xff08;LPTIM&#xff09;、2个基本定时器、2个看门狗定时器和SysTick定时器。 通用定时器&#xff08;TIM2、TIM3、…

详解JavaScript中属性getter和setter

6.6 属性getter和setter 属性值可以用1个或者2个方法替代&#xff0c;getter和setter. 由这两个定义的属性称作存取器属性(accessor property)&#xff0c;不同于数据属性&#xff0c;只有一个简单的值。有读写属性&#xff0c;只能写&#xff0c;只能读&#xff0c;可以读写…

数据结构 算法的时间复杂度 计算(两种规则 加法原则+乘法原则)

在分析时间复杂性时&#xff0c;加法和乘法原则是两个基本且重要的概念&#xff0c;它们分别用于处理算法中顺序执行和嵌套执行的代码段的时间复杂度计算。以下是对这两个原则的详细说明&#xff1a; 一、加法原则 定义&#xff1a; 加法原则适用于顺序执行的代码段。如果一…

从Linux系统的角度看待文件-基础IO

目录 从Linux系统的角度看待文件 系统文件I/O open write read 文件操作的本质 vim中批量注释的方法 从Linux系统的角度看待文件 关于文件的共识&#xff1a; 1.空文件也要占用磁盘空间 2.文件内容属性 3.文件操作包括文件内容/文件属性/文件内容属性 4.文件路径文…

LDO实习报告(免费)-有完整电路版图

LDO实习任务书 实习目的&#xff1a; 了解LDO电路研究现状和原理结构&#xff0c;熟悉模拟电路设计流程。 week1 &#xff1a; 调研LDO应用情况及研究现状。 week2 &#xff1a; 熟悉LDO基本原理及组成。 week3 &#xff1a; 构建LDO电路。 week4 &#xff1a; 对LDO进…

从日志到洞察:轻松实现服务器安全管理的神器

在当今复杂多变的网络环境中&#xff0c;服务器安全管理已成为一项不可或缺的任务。然而&#xff0c;面对海量的日志数据&#xff0c;如何快速精准地提取有价值的信息&#xff0c;并及时发现潜在的安全威胁&#xff1f;本文将为您介绍一款强大的服务器日志检索与查杀工具&#…

pilz皮尔兹PSSuniversal分散控制平台 Dezentrale Steuerungsplattform 手测

pilz皮尔兹PSSuniversal分散控制平台 Dezentrale Steuerungsplattform 手测