目录
Java中的Junit、类加载时机与机制、反射、注解及枚举
Junit
Junit介绍与使用
在Java中,Junit是一个单元测试框架,可以代替main
方法去执行其他的方法,其可以单独执行一个方法,测试该方法是否能跑通
因为Junit是一个第三方工具包,与Lombok一样需要先导入对应的jar包并进行解压,不同的是,Junit有两个包,并且相互依赖,所以都需要进行解压。这里可以使用一个文件夹名为lib
,将两个解压包放在lib
文件下,再对lib
文件夹进行整体Add as Library
使用时,在需要进行测试的方法上方写上@Test
注解,此时就会在对应方法所在行左侧显示运行按钮,点击即可运行对应的方法而不会执行其他方法
例如下面的代码:
java
public class Test01 {
@Test
public void method() {
System.out.println("测试Junit");
}
}
一个类中可以有多个有@Test
方法:
java
public class Test01 {
@Test
public void method() {
System.out.println("测试Junit");
}
@Test
public void method01() {
System.out.println("测试Junit-2");
}
}
如果有多个@Test
方法,想要运行所有的@Test
方法,就可以点击类名所在行的运行按钮
Junit注意事项
@Test
不能修饰静态方法@Test
不能修饰带参数的方法@Test
不能修饰带返回值的方法
例如:
java
public class Test01 {
@Test
public static void method02() {
System.out.println("测试Junit-3");
}
}
// 报错:Method 'method02' annotated with '@Test' should be non-static
Junit其他注解
@Before
:被@Before
修饰的方法会在每一个@Test
方法执行前执行,但是本身不可以单独执行。特点:有多个@Test
修饰的方法时会执行多次@After
:被@Before
修饰的方法会在每一个@Test
方法执行后执行,但是本身不可以单独执行。特点:有多个@Test
修饰的方法时会执行多次@BeforeClass
:被@BeforeClass
修饰的方法会在所有@Test
方法执行前执行,但是本身不可以单独执行。特点:不论是否有多个@Test
修饰的方法,该方法始终只会执行一次,并且被修饰的方法必须为静态方法@AfterClass
:被@AfterClass
修饰的方法会在所有@Test
方法执行后执行,但是本身不可以单独执行。特点:不论是否有多个@Test
修饰的方法,该方法始终只会执行一次,并且被修饰的方法必须为静态方法
例如:
java
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
文件加载进入内存:
- 实例化对象
- 实例化子类对象或者接口实现类对象(此时创建子类对象会初始化父类)
- 执行
main
方法 - 直接使用类名调用静态成员
- 使用反射创建
class
对象
因为class
也属于文件,所以JVM也需要使用IO流中的操作将class
文件读取到内存,但并不是JVM直接加载,而是使用一个名为ClassLoader
的类加载器进行加载
类加载器介绍
下面的类加载器将基于JDK8分析
JVM会使用ClassLoader
的类加载器加载类,在Java中,类加载器有以下三种:
BootStrapClassLoader
:根类加载器,也称为引导类加载器,主要加载Java中的核心类,例如System
类、String
类等(在路径jre/lib/rt.jar
下)ExtClassLoader
:扩展类加载器,负责jre
的扩展目录中的jar
包的加载(在路径jdk\jre\lib\ext
下)AppClassLoader
:系统类加载器,负责在JVM启动时加载来自Java命令的class
文件(可以理解为自定义类),以及classPath
环境变量所指定的jar
包(可以理解为第三方jar
包)
在Java中,三种不同的类加载器会加载不同的类,而其三者的关系「从类加载机制层面」如下:
AppClassLoader
的父类加载器是ExtClassLoader
ExtClassLoader
的父类加载器是BootStrapClassLoader
需要注意,他们从代码级别上来看,没有子父类继承关系,但是他们都有一个共同的父类,即
ClassLoader
并且BootStrapClassLoader
是无法直接获取到的,因为其底层是C语言编写的
java
// 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代码中,可以通过下面的两个方法获取类加载器对象:
Class
对象中的方法:getClassLoader()
,使用时:类名.class.getClassLoader()
,作用是获取某一个使用的类加载器ClassLoader
类中的方法:getParent()
,使用时:ClassLoader对象.getParent()
,作用是获取指定类加载器的父类加载器
java
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
类
同样,对于成员变量、成员方法和构造方法来说也有对应的对象和类:
- 成员变量:对应的成员变量对象为
Field
对象,描述Field
对象的类称为Field
类 - 成员方法:对应的成员方法对象为
Method
对象,描述Method
对象的类称为Method
类 - 构造方法:对应的构造方法对象为
Constructor
对象,描述Constructor
对象的类称为Constructor
类
获取类对象
获取类对象是反射成立的第一步,而一共有三种方式获取类对象:
- 调用
Object
中的方法:Class <?> getClass()
,该方法返回一个类对象 - 通过
class
成员获取类对象:基本数据类型/引用数据类型.class
Class
类中的静态方法:static Class<?> forName(String className)
,该方法的参数为类的全限定名,该方法返回一个类对象
类的全限定名即为类所在的包名,即
package
后面的内容+指定类名
例如,下面的代码:
java
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中获取类的全限定名:
- 右键需要全限定名的类->选择
Copy Path/Reference...
->选择Copy Reference
- 在
forName
方法中输入需要全限定名的类名,按下Tab
或者Enter
如果按住Ctrl+鼠标左键点击类的全限定名可以跳转到指定类时,说明全限定名正确
在实际开发中最常用的获取类对象的方式是第二种,但是最通用的方式是第三种,因为第三种的参数是String
类型,后面可以结合配置文件xxx.properties
和Properties
集合中的load
方法加载类的全限定名
获取类对象的构造方法
获取类对象的构造方法一共有四种方法:
- 获取类对象中所有
public
构造方法:使用Class
类中的方法:Constructor<?>[] getConstructors()
,该方法返回一个构造方法类的对象 - 获取类对象中指定的
public
构造方法:Class
类中的方法:Constructor<T> getConstructor (Class<?>... parameterTypes)
,该方法的参数为指定的public
构造方法参数类型对应的类对象,返回一个构造方法类的对象。如果指定的public
构造方法没有参数,则可以不传递任何内容 - 获取类对象中所有构造方法(包括
public
和private
):Constructor<?>[] getDeclaredConstructors()
,该方法返回一个构造方法类的对象 - 获取类对象中指定的构造方法(包括
public
和private
):Constructor<T> getDeclaredConstructor (Class<?>... parameterTypes)
,该方法的参数为指定的构造方法的参数,返回一个构造方法类的对象。如果指定的构造方法没有参数,则可以不传递任何内容
下面示例使用的类:
java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
public Integer age;
private Person(String name) {
this.name = name;
}
}
使用如下:
java
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)
,参数传递对应对象初始值,如果获取到的是无参构造,则参数不传递,否则传递对应的值,该方法返回一个对应类的对象
java
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()
,简写为如下的代码:
java
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
表示不修改
java
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);
}
}
上面获取私有构造方法并创建对象也被称为暴力反射
获取类对象的成员方法
获取类对象的成员方法一共有四种方法:
- 获取类对象中所有
public
成员方法:使用Class
类中的方法:Method[] getMethods()
,该方法返回一个成员方法类的对象 - 获取类对象中指定的
public
成员方法:Class
类中的方法:Method getMethod (String name, Class<?>... parameterTypes)
,该方法的第一个参数为指定的public
成员方法名,第二个参数为指定的public
成员方法参数类型对应的类对象,返回一个成员方法类的对象。如果指定的public
成员方法没有参数,则第二个参数可以不传递任何内容 - 获取类对象中所有成员方法(包括
public
、private
和protected
):Method[] getDeclaredMethods()
,该方法返回一个成员方法类的对象 - 获取类对象中指定的成员方法(包括
public
和private
):Method getDeclaredMethod (String name, Class<?>... parameterTypes)
,该方法的参数为指定的成员方法的参数,返回一个成员方法类的对象。如果指定的public
成员方法没有参数,则第二个参数可以不传递任何内容
需要注意,
getMethods
方法会获取到本类和其父类的所有public
方法,但是getDeclaredMethods
方法只会获取到本类中的private
、public
和protected
方法
例如下面的代码:
java
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
java
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
java
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);
}
}
获取类对象的成员属性
获取类对象的构造方法一共有四种方法:
- 获取类对象中所有
public
成员属性:使用Class
类中的方法:Field[] getFields()
,该方法返回一个成员属性类的对象 - 获取类对象中指定的
public
成员属性:Class
类中的方法:Field getField(String name)
,该方法的参数为指定的public
成员属性名,返回一个成员属性类的对象 - 获取类对象中所有成员属性(包括
public
和private
):Field[] getDeclaredFields()
,该方法返回一个成员属性类的对象 - 获取类对象中指定的成员属性(包括
public
、private
和protected
):Field getDeclaredField(String name)
,该方法的参数为指定的public
成员属性名,返回一个成员属性类的对象
例如:
java
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
对象,该对象中的值即为成员属性的值
java
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
java
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);
}
}
反射实用案例
在配置文件中,配置类的全限定名,以及配置一个方法名,通过解析配置文件,让配置好的方法执行起来,配置文件的内容如下:
java
className=包名.Person
methodName=walk
步骤:
- 创建
properties
配置文件,配置信息,需要注意,这个配置文件不能直接放到模块或者项目下,否则在out
文件夹中不存在该文件,最常见的做法是在指定目录下创建一个名为resources
文件夹,并将这个文件夹标记为Resources Root
,然后将配置文件放在这个文件夹内部 - 读取配置文件,解析配置文件。读取配置文件可以使用
properties
集合中的load
方法,解析配置文件时不建议直接在创建IO流对象时传递properties
文件的地址,这样导致该地址为死地址,而且out文件夹下不会存在resources
文件夹。推荐方法:因为配置文件也属于文件,在Java中加载该文件时会产生对应的对象,使用ClassLoader
获取当前类的类加载器对象,再使用该对象调用getResourceAsStream ("配置文件名")
方法获取InputStream
对象。这种方式会自动扫描resources
下的文件,可以简单理解为扫描out
路径下的配置文件 - 根据解析出来的
className
,创建Class
对象 - 根据解析出来的
methodName
,获取对应的方法 - 执行方法
IDEA中「将这个文件夹标记为Resources Root
」的步骤图:
如果上面的方式中没有显示Resources Root
,则可以考虑下面的步骤:
参考代码如下:
java
// 配置文件
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
类同层次
注解常见的作用如下:
- 说明:对代码进行说明,生成doc文档(API文档)
- 检查:检查代码是否符合条件,例如:
@Override
、@FunctionalInterface
- 分析:对代码进行分析,起到了代替配置文件的作用
JDK中常见的注解:
@Override
:检测此方法是否为重写方法- JDK5版本,支持父类的方法重写
- JDK6版本,支持接口的方法重写
@Deprecated
:表示方法已经过时,不推荐使用,但是依旧可以使用@SuppressWarnings
:消除警告,例如消除所有警告:@SuppressWarnings("all")
在IDEA中,一般被警告的方法默认会有黄色底色,例如下图:
定义注解和属性
在Java中,可以使用下面的格式定义注解:
java
public @Interface 注解名 {
// 属性
}
在注解体内的属性,本质是抽象方法,但是在使用时,与成员属性相同,使用成员属性名=值
的方式
属性的定义有两种方式:
数据类型 属性名()
:定义一个没有默认值的属性,使用注解时就必须赋值数据类型 属性名() default 值
:定义一个有默认值的属性,使用注解时可以不需要赋值
可以作为属性的类型:
- 所有基本数据类型
String
类型- 枚举类型
- 注解类型
Class
类型- 上面所有类型的一维数组(不可以是二维数组)
例如:
java
public @interface Book {
//书名
String bookName();
//作者
String[] author();
//价格
int price();
//数量
int count() default 10;
}
注解的使用
使用注解本质就是为每一个属性(抽象方法)赋值,一般使用位置有下面几种:
- 类名上
- 方法上
- 成员变量上
- 局部变量上
- 参数位置
使用格式如下:
- 普通属性:
@注解名(属性名 = 值, 属性名 = 值...)
- 属性中有数组:
@注解名(属性名 = {元素1,元素2...})
注解使用时需要注意:
- 空注解(注解中没有任何的属性)可以直接使用
- 不同的位置可以使用一样的注解,但是同样的位置不能使用一样的注解
- 使用注解时,如果此注解中有属性没有默认值,则注解中对应的属性一定要赋值。如果有多个属性,用
,
隔开;如果注解中的属性值有默认值,那么不用显示写,也不用重新赋值 - 如果注解中的属性有数组,使用
{}
- 如果注解中只有一个属性,并且属性名叫
value
,那么使用注解的时候,属性名不用写,直接写值(包括普通类型和数组)
例如:
java
// 自定义注解
public @interface Book {
//书名
String bookName();
//作者
String[] author();
//价格
int price();
//数量
int count() default 10;
}
// 测试
@Book(bookName = "寓言故事",author = {"张三","李四"},price = 10,count = 20)
public class BookShelf {
}
解析注解
解析注解即为取出注解对应属性的值,注解涉及的接口是:AnnotatedElement
接口,实现类有: AccessibleObject
、Class
、Constructor
、Executable
、Field
、Method
、Package
、Parameter
解析思路如下:
- 判断指定位置上有没有使用指定的注解:使用方法:
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
,如果存在注解则返回为true
,否则返回false
- 如果有,则获取指定的注解:使用方法:
getAnnotation(Class<T> annotationClass)
- 通过注解获取指定的值
例如:
java
// 自定义注解
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
文件在内存中运行,所以导致方法无法获取到对应的注解
元注解
元注解也是注解,这个注解用来管理其他注解,一般管理下面的方面:
- 控制注解的使用位置
- 控制注解是否能在类上使用
- 控制注解是否能在方法上使用
- 控制注解是否能在构造上使用等
- 控制注解的生命周期(加载位置)
- 控制注解是否能在源码中出现
- 控制注解是否能在
class
文件中出现 - 控制注解是否能在内存中出现
使用元注解:
- 注解
@Target
:控制注解的使用位置,其属性是个枚举数组ElementType[] value();
,枚举的成员可以类名直接调用。常见的成员有:TYPE
:控制注解能使用在类上FIELD
:控制注解能使用在属性上METHOD
:控制注解能使用在方法上PARAMETER
:控制注解能使用在参数上CONSTRUCTOR
:控制注解能使用在构造上LOCAL_VARIABLE
:控制注解能使用在局部变量上
- 注解
@Retention
:控制注解的生命周期(加载位置),其属性是一个枚举对象RetentionPolicy
,常见的成员有:SOURCE
:控制注解能在源码中出现(默认)CLASS
:控制注解能在class
文件中出现RUNTIME
:控制注解能在内存中出现
需要注意,
@Target
如果不指定属性,默认是全部可用
使用元注解就可以解决前面解析注解部分无法读取到注解的问题:
java
@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
枚举
基本使用
枚举属于五大引用数据类型中的一种:类、数组、接口、注解、枚举
定义枚举格式如下:
java
public enum 枚举类名{
}
在Java中,所有枚举的父类都是Enum
枚举的特点如下:
- 每一个枚举都是
static final
,但是定义枚举是不能显示写出 - 每一个枚举由逗号分隔
- 写完所有的枚举值之后,最后一个枚举后方需要加
;
- 枚举值名字最好大写
- 使用时使用枚举类名直接调用枚举成员
- 枚举中的成员都是当前枚举类类型的对象
- 枚举类中的构造方法都是
private
修饰
基本使用如下:
java
public enum Status {
RUNNING,
WAITING,
STOPPED;
}
如果想为每一个枚举赋值,可以在枚举类中定义成员和构造方法,并对外提供获取方法就可以获取到每一个枚举值对应的值
java
public enum Status {
RUNNING("运行"),
WAITING("等待"),
STOPPED("暂停");
private String name;
private Status(String name) {
this.name = name;
}
}
测试如下:
java
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)
| 将一个字符串转成枚举类型 |
例如下面的代码:
java
// 枚举
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);
}
}