上一篇:16【TCP、UDP、网络通信】
下一篇:18【枚举、类加载器、动态代理】
文章目录
17【测试单元、反射、注解】
一、测试单元
1.1 Junit的概述
我们知道程序的入口是main方法,想要执行任何的代码都必须编写一个main方法;而一个类中只能有一个main方法,有时我们仅仅想要测试某段代码的运行而已,这样会导致类的数量变得很多,且都是测试类;
Junit是一个单元测试框架,只需要在我们自己定义的任何方法上面标注一个@Test
注解,那么这个方法就可以单独的独立运行了,再也不需要main方法了,Junit属于第三方工具,一般情况下需要导入jar包。不过,多数Java开发环境已经集成了JUnit作为单元测试工具,我们的idea开发工具就已经集成Junit单元测试框架了;
- Junit官网:https://junit.org/junit5/
1.2 Junit的简单使用
Junit的使用非常简单,我们定义一个任意的方法,在方法的上面标注一个@Test
注解即可;
package com.dfbz.demo01;
import org.junit.Test;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
// 标注一个@Test注解
@Test
public void demo01(){
System.out.println("我是Junit");
}
}
标注完@Test注解后,按住Alt+回车
,引入Junit依赖:
依赖引入完毕之后,左边出现一个可运行的按钮:
1.3 Junit常用注解
@Before
:在执行每个测试方法之前都会执行一次@After
:在执行每个测试方法之后都会执行一次@BeforeClass
:在执行每个测试方法之前执行一次,比@Before
更先执行,方法必须被static修饰@AfterClass
:在执行每个测试方法之后执行一次,比@After
更后执行,方法必须被static修饰
示例代码:
package com.dfbz.demo01;
import org.junit.*;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02 {
@BeforeClass
public static void init() { // 一般用于资源的初始化
System.out.println("BeforeClass...");
}
@Before
public void before() {
System.out.println("Before...");
}
/*
测试方法
*/
@Test
public void test1() {
System.out.println("test1...");
}
/*
测试方法
*/
@Test
public void test2() {
System.out.println("test2...");
}
@After
public void after() {
System.out.println("After");
}
@AfterClass
public static void destory() { // 一般用于释放系统资源
System.out.println("AfterClass...");
}
}
二、反射
2.1 类加载器
我们之前学习过JVM的内存划分,当Java程序运行时,会将指定的类首先加载到方法区,方法区保存该类一些基本信息,例如有哪些成员、变量、构造方法,以及这些变量的修饰符等,最终将类的实体存储在堆内存;
而将类加载到内存
这一过程则是由类加载器(ClassLoader)来完成的;Java中类加载器共分为3种:
- 启动类加载器(BootstrapClassLoader): 是嵌在JVM内核中的加载器,该加载器是用C++语言写的,主要负则加载JAVA_HOME/lib下的类库,启动类加载器无法被应用程序直接使用。
- 扩展类加载器(Extension ClassLoader): 该加载器器是用JAVA编写,且它的父类加载器是Bootstrap,是由sun.misc.Launcher$ExtClassLoader实现的,主要加载
JAVA_HOME/lib/ext
目录中的类库。开发者可以这几使用扩展类加载器。 - 系统类加载器(App ClassLoader): 系统类加载器,也称为应用程序类加载器,负责加载应用程序classpath目录下的所有jar和class文件(第三方jar)。它的父加载器为
Ext ClassLoader
。
通过类加载器获取资源:
通过类加载器可以获取src下的任意一个资源,此类加载器必须是App ClassLoader
类加载器
在src目录下新建一个hello.txt:
我是中国人
使用类加载器
package com.dfbz.demo01;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) throws IOException {
// 获取类加载器(类加载器类型为App classloader)
ClassLoader classLoader = Demo01.class.getClassLoader();
// 通过类加载获取类路径下的资源(src下)
InputStream is = classLoader.getResourceAsStream("hello.txt");
// 包装成缓冲字符流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String str = br.readLine();
System.out.println(str);
br.close();
}
}
2.2 Class类
反射是Java中的一种机制,可以通过Java代码对一个类进行解析;例如获取类的属性、方法、构造方法等
Java中一个类被加载到内存中后被java.lang.Class
类所描述,该类又称字节码类,我们可以通过该类获取所描述的属性、方法、构造方法等;也就是说使用反射就必须先获取到Class对象(字节码对象);
准备一个Cate类:
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Cate {
private String name; // 美食名称
private String taste; // 口味偏重
private Boolean recommend; // 是否推荐
}
- 获取Class对象:
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02 {
public static void main(String[] args) throws ClassNotFoundException {
// 获得Cate的Class的对象
Class c1 = Cate.class;
// 打印输出: class com.dfbz.demo01.Cate
System.out.println(c1);
// 创建学生对象
Cate Cate = new Cate();
// 获得学生类的Class对象
Class c2 = Cate.getClass();
// 打印输出: class com.dfbz.demo01.Cate
System.out.println(c2);
// 获得字符串的Class对象
Class c3 = Class.forName("com.dfbz.demo01.Cate");
// 打印输出: class com.dfbz.demo01.Cate
System.out.println(c3);
System.out.println(c1 == c2); // true
System.out.println(c2 == c3); // true
}
}
tips:不管哪种方式获取到的字节码对象始终是同一个,因此类只会被加载一次;
2.3.1 Class类相关方法
-
public String getSimpleName()
: 获得简单类名,只是类名,没有包 -
public String getName()
: 获取完整类名,包含包名+类名 -
public T newInstance()
:创建此 Class 对象所表示的类的一个新实例。要求:类必须有public的无参数构造方法
示例代码:
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
// 通过全类目的方式获取字节码对象
Class<Cate> cateClass = (Class<Cate>) Class.forName("com.dfbz.demo01.Cate");
// 获取简单类名
System.out.println(cateClass.getSimpleName()); // Cate
// 获取字节码对象的全类名
System.out.println(cateClass.getName()); // com.dfbz.demo01.Cate
// 通过字节码对象创建对象(底层调用的是空参构造方法)
Cate Cate = cateClass.newInstance();
}
}
2.3 Constructor类
我们获取到一个类的字节码对象时,可以通过该字节码对象获取类的成员变量、成员方法、构造方法等,java.lang.reflect.Constructor
类就是用于描述一个构造方法的;类中的每一个构造方法都是Constructor的对象,通过Constructor对象可以实例化对象。
2.3.1 Class中获取Constructor 相关方法
-
public Constructor getConstructor(Class... parameterTypes)
根据参数类型获取构造方法对象,只能获得public修饰的构造方法。如果不存在对应的构造方法,则会抛出java.lang.NoSuchMethodException
异常。 -
Constructor getDeclaredConstructor(Class... parameterTypes)
:根据参数类型获取构造方法对象**,能获取所有的构造方法(public、默认、protected、private )**。如果不存在对应的构造方法,则会抛出java.lang.NoSuchMethodException
异常。 -
Constructor[] getConstructors()
: 获取所有的public修饰的构造方法 -
Constructor[] getDeclaredConstructors()
:获取所有构造方法,包括public、默认、protected、private
准备一个Cate类,提供若干构造:
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Cate {
private String name; // 美食名称
private String taste; // 口味偏重
private Boolean recommend; // 是否推荐
// 有参构造
public Cate(String name, String taste, Boolean recommend) {
System.out.println("公共方法");
this.name = name;
this.taste = taste;
this.recommend = recommend;
}
// 受保护的构造
protected Cate(String name, String taste) {
System.out.println("受保护方法");
this.name = name;
this.taste = taste;
}
// 默认构造
Cate(String name) {
System.out.println("默认方法");
this.name = name;
}
// 私有构造
private Cate(String name, Boolean recommend) {
System.out.println("私有方法");
this.name = name;
this.recommend = recommend;
}
// 空参构造
public Cate() {
System.out.println("公共方法");
}
@Override
public String toString() {
return "Cate{" +
"name='" + name + '\'' +
", taste='" + taste + '\'' +
", recommend=" + recommend +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTaste() {
return taste;
}
public void setTaste(String taste) {
this.taste = taste;
}
public Boolean getRecommend() {
return recommend;
}
public void setRecommend(Boolean recommend) {
this.recommend = recommend;
}
}
示例代码:
package com.dfbz.demo02;
import com.dfbz.demo01.Cate;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.Constructor;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
static Class<Cate> clazz;
@BeforeClass
public static void before() {
clazz = Cate.class;
}
@Test
public void test1() { // 反射所有public 修饰的构造方法
System.out.println("获取所有public 修饰的构造方法");
// 获取到2个构造public修饰的构造方法
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor c : constructors) {
System.out.println(c);
}
System.out.println("-------------");
}
@Test
public void test2() { //反射所有的构造方法
System.out.println("获取所有的构造方法");
// 获取到5个构造方法,包括2个公共方法、1个默认方法、1个受保护方法、1个私有方法
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor c : constructors) {
System.out.println(c);
}
System.out.println("-------------");
}
}
2.3.2 Constructor 常用方法
-
T newInstance(Object... initargs)
: 根据指定参数创建对象。 -
void setAccessible(true)
:开启强制访问,除public修饰的构造方法外,其他构造方法反射都需要暴力反射
示例代码:
package com.dfbz.demo02;
import com.dfbz.demo01.Cate;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.Constructor;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03 {
static Class<Cate> clazz;
@BeforeClass
public static void before() {
clazz = Cate.class;
}
@Test
public void test1() throws Exception { // 反射 受保护的方法
//获取任何修饰符修饰的构造方法(根据参数列表获取)
Constructor<Cate> dc = clazz.getDeclaredConstructor(String.class, String.class);
// 开启强制访问(只要不是public修饰的方法都需要开启强制访问)
dc.setAccessible(true);
// 实例化对象,并传入参数
Cate cate = dc.newInstance("辣子鸡", "辣");
System.out.println(cate);
System.out.println("-------------");
}
@Test
public void test2() throws Exception { // 反射默认方法
//获取任何修饰符修饰的构造方法(根据参数列表获取)
Constructor<Cate> dc = clazz.getDeclaredConstructor(String.class);
// 开启强制访问
dc.setAccessible(true);
// 实例化对象,并传入参数
Cate cate = dc.newInstance("新疆馕饼");
System.out.println(cate);
System.out.println("-------------");
}
@Test
public void test3() throws Exception { // 反射私有方法
Constructor<Cate> dc = clazz.getDeclaredConstructor(String.class, Boolean.class);
// 开启强制访问
dc.setAccessible(true);
// 实例化对象,并传入参数
Cate cate = dc.newInstance("猪肉炖粉条",true);
System.out.println(cate);
System.out.println("-------------");
}
@Test
public void test6() throws Exception { // 反射public修饰的构造方法
//获取构造器对象
Constructor<Cate> dc = clazz.getConstructor(String.class, String.class,Boolean.class);
// 实例化对象,并传入参数
Cate cate = dc.newInstance("粉蒸肉","鲜香",true);
System.out.println(cate);
System.out.println("-------------");
}
}
2.3 Method类
Method
类是Java中用于描述的方法的一个类,当通过反射获剖析到一个类的方法时返回该对象,通过该对象可以执行该对象封装的方法;
2.3.1 Class中获取Method相关方法
-
public Method getMethod(String name, Class<?>... parameterTypes)
:根据方法名和参数类型获得一个方法对象,只能是获取public修饰的 -
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
:根据方法名和参数类型获得一个方法对象,包含任意修饰符的 -
public Method[] getMethods()
:获取所有的public修饰的成员方法,包括父类中的方法。 -
public Method[] getDeclaredMethods()
:获取当前类中所有的方法,包含任意修饰符的,但不包括父类中。
提供一个测试类:
package com.dfbz.demo03;
/**
* @author lscl
* @version 1.0
* @intro: 测试实体类
*/
public class TestEntity {
// 共有方法
public void a() {
System.out.println("我是共有方法");
}
// 共有有参方法
public void b(String param) {
System.out.println("我是共有方法,传递的参数为: " + param);
}
// 私有方法
private void c() {
System.out.println("我是私有方法");
}
// 共有方法
public static void d() {
System.out.println("我静态共有方法");
}
// 共有方法
private static void e() {
System.out.println("我是静态私有方法");
}
}
示例代码:
package com.dfbz.demo03;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.Method;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
static Class<TestEntity> clazz;
@BeforeClass // 首先获取到TestEntity的字节码对象(反射的前提)
public static void before() {
clazz = TestEntity.class;
}
@Test
public void test1(){ // 反射所有共有方法,包括父类中的
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println(method);
}
}
@Test
public void test2(){ // 反射当前类的所有方法,包含任意修饰符,但不能获取父类的方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
}
}
}
2.3.2 Method常用方法
-
public Object invoke(Object obj, Object... args)
:根据参数args调用对象obj的该成员方法,如果obj=null,则表示该方法是静态方法 -
public void setAccessible(boolean flag)
:开启强制访问,设置为可以直接调用非public修饰的方法 -
public String getName()
:获取此对象封装的方法名
示例代码:
package com.dfbz.demo03;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.Method;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02 {
static Class<TestEntity> clazz;
@BeforeClass // 首先获取到TestEntity的字节码对象(反射的前提)
public static void before() {
clazz = TestEntity.class;
}
@Test
public void test1() throws Exception { // 反射共有方法
// 原有对象
TestEntity testEntity1 = new TestEntity();
// 原有对象
TestEntity testEntity2 = clazz.newInstance();
// 原有对象
TestEntity testEntity3 = clazz.getConstructor().newInstance();
// 根据方法名和参数列表 获取对应的方法对象
Method method = clazz.getMethod("a");
// 执行目标方法(需要传递原有对象来执行)
Object invoke = method.invoke(testEntity1);
System.out.println(method.getName() + "方法的返回值是: " + invoke);
}
@Test
public void test2() throws Exception { // 反射共有有参方法
// 原有对象
TestEntity testEntity1 = new TestEntity();
// 根据方法名和参数列表获取对应的方法对象
Method method = clazz.getMethod("b",String.class);
// 执行目标方法(需要传递原有对象来执行),并传递方法参数
Object invoke = method.invoke(testEntity1,"abc");
System.out.println(method.getName() + "方法的返回值是: " + invoke);
}
@Test
public void test3() throws Exception { // 反射私有方法
// 原有对象
TestEntity testEntity1 = new TestEntity();
// 根据方法名和参数列表 获取对应的方法对象
Method method = clazz.getDeclaredMethod("c");
// 开启强制访问
method.setAccessible(true);
// 执行目标方法(需要传递原有对象来执行),并传递方法参数
Object invoke = method.invoke(testEntity1);
System.out.println(method.getName() + "方法的返回值是: " + invoke);
}
@Test
public void test4() throws Exception { // 反射共有静态方法
// 原有对象
TestEntity testEntity1 = new TestEntity();
// 根据方法名和参数列表 获取对应的方法对象
Method method = clazz.getMethod("d");
// 执行目标方法,并传递方法参数
Object invoke = method.invoke(testEntity1);
Object invoke2 = method.invoke(null); // 静态方法可以不用对象来调用
System.out.println(method.getName() + "方法的返回值是: " + invoke);
}
@Test
public void test5() throws Exception { // 反射私有静态方法
// 原有对象
TestEntity testEntity1 = new TestEntity();
// 注意:非public修饰的方法需要用getDeclaredMethod()来获取
Method method = clazz.getDeclaredMethod("e");
// 非public修饰的方法需要开启强制访问
method.setAccessible(true);
// 执行目标方法,并传递方法参数
Object invoke = method.invoke(testEntity1);
Object invoke2 = method.invoke(null); // 静态方法可以不用对象来调用
System.out.println(method.getName() + "方法的返回值是: " + invoke);
}
}
2.4 Field类
Field是Java中用于描述成员属性的类,通过反射获取到类的某个属性时,用Field将其封装;当我们获取到Field时,可以通过Field对象来获取属性的值;
2.4.1 Class中获取Field相关方法
-
Field getDeclaredField(String name)
:根据属性名获得属性对象,包括private修饰的 -
Field getField(String name)
:根据属性名获得属性对象,只能获取public修饰的 -
Field[] getFields()
:获取所有的public修饰的属性对象,返回数组。 -
Field[] getDeclaredFields()
:获取所有的属性对象,包括private修饰的,返回数组。
2.4.2 Field常用方法
void set(Object obj, Object value)
:给指定的属性设置值Object get(Object obj)
:获取属性字段的值void setAccessible(boolean flag)
:开启强制访问Class getType()
:获取属性的类型,返回Class对象。
在TestEntity类中添加如下属性:
public class TestEntity {
public String username = "root";
protected String password = "admin";
String phone = "110";
private String email = "110@abc.com";
}
示例代码:
package com.dfbz.demo04;
import com.dfbz.demo01.Cate;
import com.dfbz.demo03.TestEntity;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.Field;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
static Class<TestEntity> clazz;
@BeforeClass // 首先获取到TestEntity的字节码对象(反射的前提)
public static void before() {
clazz = TestEntity.class;
}
@Test
public void test1(){ // 反射所有公有的字段
Field[] fields = clazz.getFields();
for(Field field:fields){
System.out.println(field);
}
System.out.println("---------");
}
@Test
public void test2() throws Exception { // 反射公有字段
// 获取实体对象
TestEntity testEntity = clazz.newInstance();
// 根据字节码对象获取属性对象
Field field = clazz.getField("username");
// 通过属性对象给指定的书设置值
field.set(testEntity,"zhangsan");
// 获取属性对象的值
Object obj = field.get(testEntity);
System.out.println(obj);
}
@Test
public void test3() throws Exception { // 反射非public修饰的字段
TestEntity testEntity = clazz.newInstance();
// 注意: 获取非public修饰的字段时,需要使用getDeclaredField方法
Field field = clazz.getDeclaredField("password");
// 开启强制访问
field.setAccessible(true);
// 设置该属性字段的额值
field.set(testEntity,"123456");
// 获取属性字段的值
Object object = field.get(testEntity);
System.out.println(object);
}
}
2.5 反射小案例
准备一个properties文件,内容如下:
class=com.dfbz.demo01.Cate
name=藜蒿炒腊肉
taste=鲜香辣
recommend=true
根据配置文件创建一个Cate对象,利用反射创建对象并设置属性值;
示例代码:
package com.dfbz.demo05;
import com.dfbz.demo01.Cate;
import com.dfbz.demo01.Demo02;
import com.sun.org.apache.xpath.internal.operations.Bool;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.Properties;
import java.util.Set;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) throws Exception {
Properties prop = new Properties();
prop.load(
new InputStreamReader(
Demo01.class.getClassLoader().getResourceAsStream("cate.properties"),
"UTF-8"
)
);
System.out.println(prop);
// 从集合中获得类名
String className = prop.getProperty("class");
// 通过反射获得Class对象
Class<Cate> clazz = (Class<Cate>) Class.forName(className);
// 快速创建对象
Cate cate = clazz.newInstance();
// 获取字段的真实值
String name = prop.getProperty("name");
String taste = prop.getProperty("taste");
String recommend = prop.getProperty("recommend");
// 通过反射获取到Filed对象
Field nameField = clazz.getDeclaredField("name");
// 开启强制访问
nameField.setAccessible(true);
// 给指定对象设置值
nameField.set(cate,name);
Field tasteField = clazz.getDeclaredField("taste");
tasteField.setAccessible(true);
tasteField.set(cate,taste);
Field recommendFiled = clazz.getDeclaredField("recommend");
recommendFiled.setAccessible(true);
recommendFiled.set(cate, Boolean.parseBoolean(recommend));
System.out.println(cate);
}
}
三、注解
注解是一种标记,可以作用于类、方法、方法形参、成员变量上,给类携带一些额外的参数信息,通过反射来提取这些注解上的信息,然后进行一些操作;
3.1 常见的注解
生成帮助文档:@author和@version
-
@author:用来标识作者姓名。
-
@version:用于标识对象的版本号,适用范围:文件、类、方法。
我们之前写的每个类上面都标注了@author和@version注解:
使用**@author和@version注解就是告诉Javadoc工具**在生成帮助文档时把作者姓名和版本号也标记在文档中。
使用javadoc工具生成帮助文档:
javadoc -encoding utf-8 -author -version TestAnnotation.java
查看生成的帮助文档:
编译检查注解:@Override
3.2 自定义注解
3.2.1 定义注解格式
public @interface 注解名{
}
如:定义一个名为Book的注解
public @interface Book {
字段类型 字段名1();
字段类型 字段名2();
}
3.2.2 字段类型的格式
属性的格式
- 格式1:数据类型 属性名();
- 格式2:数据类型 属性名() default 默认值;
示例:
public @interface Book {
String name(); // 书名
String author() ; // 作者
Double price() default 20.8; // 价格
}
tips:注解字段类型可以有如下:
- 八种基本数据类型(int,float,boolean,byte,double,char,long,short)
- String类型,Class类型,枚举类型,注解类型
- 以上所有类型的一维数组
3.3 使用自定义注解
3.3.1 定义注解
定义一个注解:Book
- 包含属性:String value() 书名
- 包含属性:double price() 价格,默认值为 100
- 包含属性:String[] authors() 多位作者
示例代码:
public @interface Book {
// 书名
String value();
// 价格
double price() default 100;
// 多位作者
String[] authors();
}
3.3.2 使用注解
public class BookShelf {
@Book(value = "《汉书》",price = 108,authors = {"班固","班昭"})
public void showBook(){
}
}
注意事项:
- 如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。
- 如果属性没有默认值,那么在使用注解时一定要给属性赋值。
3.3.3 特殊属性value
当注解中只有一个属性且名称是value,在使用注解时给value属性赋值可以直接给属性值,无论value是单值元素还是数组类型。
- 定义一个注解:
// 定义注解Book
public @interface Book {
// 书名
String value();
}
- 使用注解:
// 使用注解Book
public class BookShelf {
@Book("《史记》")
public void showBook(){
}
}
或者:
public class BookShelf {
@Book(value="《史记》")
public void showBook(){
}
}
如果注解中除了value属性还有其他属性,且至少有一个属性没有默认值,则在使用注解给属性赋值时,value属性名不能省略。
- 定义注解
// 定义注解Book
public @interface Book {
// 书名
String value();
// 价格
double price() default 100;
// 多位作者
String[] authors();
}
- 使用注解
// 使用Book注解:正确方式
@Book(value="《三国志》",authors = "陈寿")
public class BookShelf {
// 使用Book注解:正确方式
@Book(value="《后汉书》",authors = {"范晔"})
public void showBook(){
}
}
当注解中除了value还有其他属性时,在赋值时必须指定全部的属性名,value属性名不能省略。
错误示例:
// 使用Book注解:错误方式
public class BookShelf {
@Book("《后汉书》",authors = {"范晔"})
public void showBook(){
}
}
不能省略value
3.4 元注解
元注解是Java API提供的注解,是用来定义注解的注解。元注解可以限制注解标注的位置,生命周期等;
3.4.1 @Target注解
作用:指明此注解用在哪个位置,如果不写默认是任何地方都可以使用。
可选的参数值在枚举类ElemenetType中包括:
TYPE: 用在类,接口上
FIELD:用在成员变量上
METHOD: 用在方法上
PARAMETER:用在参数上
CONSTRUCTOR:用在构造方法上
LOCAL_VARIABLE:用在局部变量上
ANNOTATION_TYPE:用在注解上
3.4.2 @Retention注解
作用:定义该注解的生命周期(有效范围)。
可选的参数值在枚举类型RetentionPolicy中包括:
SOURCE
:注解只存在于Java源代码中,编译生成的字节码文件中就不存在了。CLASS
:注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没有,默认值。RUNTIME
:注解存在于Java源代码中、编译以后的字节码文件中、运行时内存中,程序可以通过反射获取该注解。
3.4.3 元注解的使用
- 定义注解:
package com.dfbz.demo01;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
* @author lscl
* @version 1.0
* @intro:
*/
// 允许在方法、类上使用
@Target({ElementType.METHOD,ElementType.TYPE})
@interface Student{
String name();
}
- 测试类:
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro:
*/
// 类
@Student(name="jack")
public class AnnotationDemo02 {
// 成员变量
@Student(name = "lily") // 编译失败
private String gender;
// 成员方法
@Student(name="rose")
public void test(){
}
// 构造方法
@Student(name="lucy") // 编译失败
public AnnotationDemo02(){}
}
3.5 注解解析
当我们标注注解后,注解中携带的信息需要我们手动来解析,通过反射技术来剖析一个类的注解,然后提取里面的信息;
3.5.1 注解解析相关接口
- Anontation:所有注解类型的公共接口,类似所有类的父类是Object。
- AnnotatedElement:定义了与注解解析相关的方法,常用方法以下四个:
boolean isAnnotationPresent(Class annotationClass)
:判断当前对象是否有指定的注解,有则返回true,否则返回false。T getAnnotation(Class<T> annotationClass)
:获得当前对象上指定的注解对象。Annotation[] getAnnotations()
:获得当前对象及其从父类上继承的所有的注解对象。Annotation[] getDeclaredAnnotations()
:获得当前对象上所有的注解对象,不包括父类的。
3.5.2 获取注解
注解作用在那个成员上,就通过反射获得该成员的对象来得到它的注解。
- 如注解作用在方法上,就通过方法(Method)对象得到它的注解
// 得到方法对象
Method method = clazz.getDeclaredMethod("方法名");
// 根据注解字节码对象得到方法上的注解对象
Student student = method.getAnnotation(Student.class);
- 如注解作用在类上,就通过Class对象得到它的注解
// 获得Class对象
Class c = 类名.class;
// 根据注解字节码对象得到类上的注解对象
Student student = c.getAnnotation(Student.class);
3.5.3 注解信息解析案例
1)需求如下:
- 定义注解Computer,要求如下:
- 包含属性:String value() 计算机名
- 包含属性:double price() 价格,默认值为 2000
- 包含属性:String[] factories() 多个厂商
- 限制注解使用的位置:类和成员方法上
- 指定注解的有效范围:RUNTIME
- 定义TestComputer类,在类和成员方法上使用Computer注解
- 定义TestAnnotation测试类获取Computer注解上的数据
2)代码实现
- 定义Computer注解:
package com.dfbz.demo01;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 只允许在类、方法上标注此注解
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Computer {
// 计算机名
String name();
// 计算机价格
double price() default 2000;
// 厂商
String[] factories();
}
- 定义TestComputerr类:
package com.dfbz.demo01;
@Computer(name ="神舟战神",price = 3800,factories = {"华南厂商","华北厂商"})
public class TestComputerr {
@Computer(name = "神舟优雅",price = 4200,factories = {"华中厂商","华东厂商"})
public void buyComputer(){
System.out.println("买神舟电脑啦~~好开森~~");
}
}
- 定义测试类:
package com.dfbz.demo01;
import org.junit.Test;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
//获取类上的注解信息
@Test
public void test1() throws IllegalAccessException, InstantiationException {
System.out.println("类上注解信息:");
//获取字节码对象
Class<TestComputer> clazz= TestComputer.class;
//获取注解对象
Computer c = clazz.getAnnotation(Computer.class);
//判断TestCompute类上是否使用了Compute注解,或者该注解的生命周期是否是RUNTIME
if(clazz.isAnnotationPresent(Computer.class)){
//通过注解对象获取注解上的值
String name = c.name();
double price = c.price();
String[] factories = c.factories();
System.out.println(name);
System.out.println(price);
System.out.println(Arrays.toString(factories));
System.out.println("-------------");
}
}
//获取方法上注解信息
@Test
public void test2() throws IllegalAccessException, InstantiationException, NoSuchMethodException {
System.out.println("方法上注解信息:");
//获取字节码对象
Class<TestComputer> clazz= TestComputer.class;
//通过类字节码对象获取方法对象
Method method = clazz.getMethod("buyCompute");
//判断该方法上是否使用了Compute注解,或者该注解的生命周期是否是RUNTIME
if(method.isAnnotationPresent(Computer.class)){
//通过方法对象获取该方法上的注解
Computer c = method.getAnnotation(Computer.class);
//通过注解对象获取注解上的值
String name = c.name();
double price = c.price();
String[] factories = c.factories();
System.out.println(name);
System.out.println(price);
System.out.println(Arrays.toString(factories));
}
}
}
3.6 注解案例
我们尝试自己定义一个@MyTest注解来模拟Junint的@Test注解的功能
1)案例分析如下:
- 模拟Junit测试的注释@Test,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得。
- 然后编写目标类(测试类),然后给目标方法(测试方法)使用 @MyTest注解,编写三个方法,其中两个加上@MyTest注解。
- 最后编写调用类,使用main方法调用目标类,模拟Junit的运行,只要有@MyTest注释的方法都会运行。
2)代码实现
- 定义@MyTest注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
- 定义目标类:
package com.dfbz.demo02;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class MyTestDemo {
@MyTest
public void test1(){
System.out.println("自定义的Test1方法执行啦!!~~~");
}
@MyTest
public void test2(){
System.out.println("自定义的Test2方法执行啦!!~~~");
}
@MyTest
public void test3(){
System.out.println("自定义的Test3方法执行啦!!~~~");
}
}
- 定义测试类:
package com.dfbz.demo02;
import java.lang.reflect.Method;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) throws Exception {
//获取MyTestDemo字节码对象
Class<MyTestDemo> clazz= MyTestDemo.class;
//通过字节码对象创建对象
MyTestDemo obj= clazz.newInstance();
//获取所有public修饰的方法对象
Method[] methods = clazz.getMethods();
for(Method m:methods){
//如果该方法上存在MyTest注解,就把该方法执行
if(m.isAnnotationPresent(MyTest.class)){
m.invoke(obj);
}
}
}
}
3.7 Lombok插件
Lombok 是一种 Java 实用工具,可用来帮助开发人员消除 JavaBean 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注解实现这一目的。通过在开发环境中实现 Lombok,开发人员可以节省构建诸如 hashCode() 和 equals() 这样的方法
3.7.1 下载Lombok
官方地址:https://projectlombok.org/download
3.7.2 IDEA配置Lombok插件
1)官网下载
IDEA插件官网:https://plugins.jetbrains.com/
选择指定的项目:
找到合适的版本:
2)配置插件
下载完成之后,拷贝到idea安装目录下的plugins目录下(我的是:D:\IntelliJ IDEA 2020.1.3\plugins
)
然后打开IDEA,打开Settings–>Plugins,然后选择从磁盘安装插件:
选择刚刚的插件:
点击重启IDEA:
完成:
3)开启IDEA对注解的支持
3.7.3 Lombok插件的使用
在模块的一级目录下,创建一个lib文件夹(名称随意),将lombok的jar包拷贝到目录中,然后右击选择:Add as Library...
Lombok中提供了很多的注解来代替JavaBean中的一些方法,如:
@ToString
:重写toString方法。@Setter
:提供set方法。@Getter
:提供get方法。@NoArgsConstructor
:为类提供一个无参的构造方法。@AllArgsConstructor
:为类提供一个全参的构造方法。@Data
: @Data 是 @Getter、 @Setter、 @ToString、 @EqualsAndHashCode 和 @RequiredArgsConstructor 的快捷方式。@NonNull
:注解在属性上,用来校验参数非空,可以帮助我们避免空指针。@Slf4j
:为类提供一个 属性名为log 的 log4j 日志对象。@Log4j
:为类提供一个 属性名为log 的 log4j 日志对象。
编写一个Student类:
package com.dfbz.demo03;
import lombok.*;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@ToString
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@Getter
@Setter
public class Student {
private String name;
private Integer age;
public void eat(@NonNull String food) {
System.out.println(name + "刚刚吃了: " + food);
}
}
测试类:
package com.dfbz.demo03;
import java.util.HashMap;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
// 提供了有参和无参构造
Student s1 = new Student("小灰", 20);
Student s2 = new Student();
// 提供了get/set
s2.setName("小灰");
s2.setAge(20);
HashMap map = new HashMap<>();
map.put(s1,1);
map.put(s2,1);
System.out.println(map.size()); // 1(提供了hashCode和equals)
System.out.println(s2); // 提供了toString
s1.eat(null); // 出现空指针异常
}
}
输出结果:
上一篇:16【TCP、UDP、网络通信】
下一篇:18【枚举、类加载器、动态代理】
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131768.html