17【测试单元、反射、注解、Lombok插件】

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 17【测试单元、反射、注解、Lombok插件】,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文


上一篇16【TCP、UDP、网络通信】

下一篇18【枚举、类加载器、动态代理】

目录【JavaSE零基础系列教程目录】



17【测试单元、反射、注解】

一、测试单元

1.1 Junit的概述

我们知道程序的入口是main方法,想要执行任何的代码都必须编写一个main方法;而一个类中只能有一个main方法,有时我们仅仅想要测试某段代码的运行而已,这样会导致类的数量变得很多,且都是测试类;

Junit是一个单元测试框架,只需要在我们自己定义的任何方法上面标注一个@Test注解,那么这个方法就可以单独的独立运行了,再也不需要main方法了,Junit属于第三方工具,一般情况下需要导入jar包。不过,多数Java开发环境已经集成了JUnit作为单元测试工具,我们的idea开发工具就已经集成Junit单元测试框架了;

在这里插入图片描述

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是单值元素还是数组类型。

  1. 定义一个注解:
// 定义注解Book
public @interface Book {
	// 书名
	String value();
}
  1. 使用注解:
// 使用注解Book
public class BookShelf {
    @Book("《史记》")
    public void showBook(){

    }
}

或者:

public class BookShelf {
    @Book(value="《史记》")
    public void showBook(){

    }
}

如果注解中除了value属性还有其他属性,且至少有一个属性没有默认值,则在使用注解给属性赋值时,value属性名不能省略。

  1. 定义注解
// 定义注解Book
public @interface Book {
        // 书名
        String value();
  		// 价格
        double price() default 100;
        // 多位作者
        String[] authors();
}
  1. 使用注解
// 使用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 元注解的使用

  1. 定义注解:
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();
}
  1. 测试类:
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)需求如下:

  1. 定义注解Computer,要求如下:
    • 包含属性:String value() 计算机名
    • 包含属性:double price() 价格,默认值为 2000
    • 包含属性:String[] factories() 多个厂商
    • 限制注解使用的位置:类和成员方法上
    • 指定注解的有效范围:RUNTIME
  2. 定义TestComputer类,在类和成员方法上使用Computer注解
  3. 定义TestAnnotation测试类获取Computer注解上的数据

2)代码实现

  1. 定义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();
}
  1. 定义TestComputerr类:
package com.dfbz.demo01;

@Computer(name ="神舟战神",price = 3800,factories = {"华南厂商","华北厂商"})
public class TestComputerr {

    @Computer(name = "神舟优雅",price = 4200,factories = {"华中厂商","华东厂商"})
    public void buyComputer(){
        System.out.println("买神舟电脑啦~~好开森~~");
    }
}
  1. 定义测试类:
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)案例分析如下:

  1. 模拟Junit测试的注释@Test,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得。
  2. 然后编写目标类(测试类),然后给目标方法(测试方法)使用 @MyTest注解,编写三个方法,其中两个加上@MyTest注解。
  3. 最后编写调用类,使用main方法调用目标类,模拟Junit的运行,只要有@MyTest注释的方法都会运行。

2)代码实现

  1. 定义@MyTest注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {

}

  1. 定义目标类:
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方法执行啦!!~~~");
    }
}
  1. 定义测试类:
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【枚举、类加载器、动态代理】

目录【JavaSE零基础系列教程目录】


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131768.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!