17【测试单元、反射、注解、Lombok插件】
一、测试单元
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_junit的使用;
import org.junit.Test;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_Junit基本用法 {
@Test
public void test(){
System.out.println("hello~");
}
}
标注完@Test注解后,按住Alt+回车,引入Junit依赖:

依赖引入完毕之后,左边出现一个可运行的按钮:

1.3 Junit常用注解
@Before:在执行每个测试方法之前都会执行一次@After:在执行每个测试方法之后都会执行一次@BeforeClass:在执行每个测试方法之前执行一次,比@Before更先执行,方法必须被static修饰@AfterClass:在执行每个测试方法之后执行一次,比@After更后执行,方法必须被static修饰
示例代码:
package com.dfbz.demo01_junit的使用;
import org.junit.*;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_Junit其他注解 {
@BeforeClass
public static void beforeClass() { // 一般用于资源的初始化
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 destroy() { // 一般用于释放系统资源
System.out.println("AfterClass...");
}
}
执行test1方法:

二、反射
2.1 类加载器
我们都知道,Java程序都是由若干个.class文件组织而成的,程序的功能都被封装在不同的class文件当中,所以经常要从这个class文件中要调用另外一个class文件中的方法
而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(借助ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。所以ClassLoader就是用来动态加载class文件到内存当中用的。
我们之前学习过JVM的内存划分,当Java程序运行时,会将指定的类首先加载到方法区,方法区保存该类一些基本信息,例如有哪些成员、变量、构造方法,以及这些变量的修饰符等,最终将类的实体存储在堆内存;
2.1.1 类加载器的种类
而将类加载到内存这一过程则是由类加载器(ClassLoader)来完成的;Java中类加载器共分为3种:
- 引导类加载器(BootstrapClassLoader): 是嵌在JVM内核中的加载器,该加载器是用C++语言写的,主要负则加载
JRE_HOME/lib下的类库,引导加载器无法被应用程序直接使用。 - 扩展类加载器(Extension ClassLoader):该加载器器是用JAVA编写,且它的父加载器是Bootstrap,是由sun.misc.Launcher$ExtClassLoader实现的,主要加载
JRE_HOME/lib/ext目录中的类库。开发者可以这几使用扩展类加载器。 - 应用类加载器(App ClassLoader):系统类加载器,也称为应用程序类加载器,负责加载应用程序classpath目录下的所有jar和class文件(第三方jar)。它的父加载器为
Ext ClassLoader;
【代码示例】
package com.dfbz.demo01_类加载器;
import com.sun.java.accessibility.AccessBridge;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_获取类加载器 {
public static void main(String[] args) {
// 获取这个类的类加载器(BootstrapClassLoader在Java中不可以获取到)
ClassLoader bootStrapClassLoader = Object.class.getClassLoader();
ClassLoader extClassLoader = AccessBridge.class.getClassLoader();
ClassLoader appClassLoader = Demo01_获取类加载器.class.getClassLoader();
System.out.println(bootStrapClassLoader); // null
System.out.println(extClassLoader); // sun.misc.Launcher$ExtClassLoader@45ee12a7
System.out.println(appClassLoader); // sun.misc.Launcher$AppClassLoader@18b4aac2
}
}
2.1.2 通过类加载器获取资源
通过类加载器可以获取src下的任意一个资源,此类加载器必须是App ClassLoader类加载器
在src目录下新建一个cate.txt:
桃酥饼
白糖糕
大麻枣
冻米糖
黄金糕
麻糍
发糕
使用类加载器
package com.dfbz.demo01_类加载器;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_通过类加选择器获取资源 {
public static void main(String[] args) throws Exception {
// 获取一个AppClassLoader
ClassLoader classLoader = Demo02_通过类加选择器获取资源.class.getClassLoader();
// 获取类路径下的一个资源(是一个缓存流)
BufferedInputStream bis = (BufferedInputStream) classLoader.getResourceAsStream("cate.txt");
System.out.println("通过类加载器获取的资源: ");
// 包装为BufferedReader
BufferedReader br = new BufferedReader(new InputStreamReader(bis));
String str;
while ((str = br.readLine())!=null){
System.out.println(str);
}
br.close();
}
}

2.2 Class类
反射是Java中的一种机制,可以通过Java代码对一个类进行解析;例如获取类的属性、方法、构造方法等
Java中一个类被加载到内存中后被java.lang.Class类所描述,该类又称字节码类,我们可以通过该类获取所描述的属性、方法、构造方法等;也就是说使用反射就必须先获取到Class对象(字节码对象);
准备一个Cate类:
package com.dfbz.demo02_Class;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Cate {
private String name; // 美食名称
private String taste; // 口味偏重
private Boolean recommend; // 是否推荐
}
- 获取Class对象:
package com.dfbz.demo02_Class;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_字节码对象的获取方式 {
public static void main(String[] args) throws ClassNotFoundException {
// 获取这个类的字节码对象 方式一:
Class<Cate> cateClass = Cate.class;
// 方式二:
Cate cate = new Cate();
Class<? extends Cate> cateClass2 = cate.getClass();
// 方式三:
Class<?> cateClass3 = Class.forName("com.dfbz.demo02_Class.Cate");
System.out.println(cateClass == cateClass2);
System.out.println(cateClass2 == cateClass3);
}
}
Tips:不管哪种方式获取到的字节码对象始终是同一个,因此类只会被加载一次;
2.3.1 Class类相关方法
public String getSimpleName(): 获得简单类名,只是类名,没有包public String getName(): 获取完整类名,包含包名+类名public T newInstance():创建此 Class 对象所表示的类的一个新实例。要求:类必须有public的无参数构造方法
示例代码:
package com.dfbz.demo02_Class;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_Class类的基本用法 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
// 获取这个类的字节码对象
Class<Cate> cateClass = Cate.class;
// 获取简单类名: Cate
String simpleName = cateClass.getSimpleName();
// 获取这个类的全类名: com.dfbz.demo02_Class.Cate
String name = cateClass.getName();
// 使用字节码对象来创建这个类的对象, 是调用Cate类的空参构造来创建对象
Cate cate = cateClass.newInstance();
System.out.println(simpleName);
System.out.println(name);
System.out.println(cate);
}
}
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.demo02_Class;
/**
* @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("公共方法");
}
// 省略get/set/toString...
}
示例代码:
package com.dfbz.demo03_Constructor;
import com.dfbz.demo02_Class.Cate;
import org.junit.Test;
import java.lang.reflect.Constructor;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_反射获取类的构造方法 {
// 反射空参构造方法
@Test
public void test1() throws NoSuchMethodException {
// 获取字节码对象
Class<Cate> cateClass = Cate.class;
// 获取构造方法:获取空参构造
Constructor<Cate> constructor = cateClass.getConstructor();
System.out.println(constructor);
}
// 反射参数列表获取构造方法
@Test
public void test2() throws NoSuchMethodException {
// 获取字节码对象
Class<Cate> cateClass = Cate.class;
// 根据参数列表获取构造方法
Constructor<Cate> constructor = cateClass.getConstructor(String.class, String.class, Boolean.class);
System.out.println(constructor);
}
// 反射任意权限修饰符修饰的方法
@Test
public void test3() throws NoSuchMethodException {
// 获取字节码对象
Class<Cate> cateClass = Cate.class;
// 默认情况下getConstructor只能获取public修饰的方法,如果要获取其他的权限修饰符修饰的构造方法
// getDeclaredConstructor可以获取任意权限修饰符修饰的构造方法,包括public修饰的
// Constructor<Cate> constructor = cateClass.getDeclaredConstructor(String.class,String.class);
Constructor<Cate> constructor = cateClass.getDeclaredConstructor(String.class, String.class, Boolean.class);
System.out.println(constructor);
}
// 反射所有构造方法
@Test
public void test4() throws NoSuchMethodException {
// 获取字节码对象
Class<Cate> cateClass = Cate.class;
// 获取这个类的所有的构造方法(public修饰)
// Constructor<?>[] constructors = cateClass.getConstructors();
// 获取这个类的所有的构造方法(包括所有的权限修饰符)
Constructor<?>[] constructors = cateClass.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
}
}
2.3.2 Constructor 常用方法
T newInstance(Object... initargs): 根据指定参数创建对象。void setAccessible(true):开启强制访问,除public修饰的构造方法外,其他构造方法反射都需要暴力反射
示例代码:
package com.dfbz.demo03_Constructor;
import com.dfbz.demo02_Class.Cate;
import org.junit.Test;
import java.lang.reflect.Constructor;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_Constructor的常用方法 {
// 调用空参构造
@Test
public void test1() throws Exception {
// 获取字节码对象
Class<Cate> cateClass = Cate.class;
// 反射构造方法:空参构造
Constructor<Cate> constructor = cateClass.getConstructor();
// 调用这个constructor构造器锁描述的方法,不传递参数
Cate cate = constructor.newInstance();
System.out.println(cate);
}
// 调用有参构造方法
@Test
public void test2() throws Exception {
// 1. 先获取字节码对象
Class<Cate> cateClass = Cate.class;
// 根据参数列表去反射一个public修饰的方法
Constructor<Cate> constructor = cateClass.getConstructor(String.class, String.class, Boolean.class);
Cate cate = constructor.newInstance("瓦罐汤", "鲜香", true);
System.out.println(cate);
}
// 调用任意权限修饰符修饰的方法
@Test
public void test3() throws Exception {
// 1. 获取字节码对象
Class<Cate> cateClass = Cate.class;
// 2. 反射指定的构造器(如果不是public修饰的记得要使用getDeclaredConstructor)
Constructor<Cate> constructor = cateClass.getDeclaredConstructor(String.class, String.class);
// 开启强制访问,只要不是public修饰的构造方法都需要开启强制访问
constructor.setAccessible(true);
// 3. 传递参数调用具体的构造方法
Cate cate = constructor.newInstance("桃酥饼", "甜");
System.out.println(cate);
}
}
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修饰)。public Method[] getDeclaredMethods():获取当前类中所有的方法,包含任意修饰符的,但不包括父类中。
提供一个测试类:
package com.dfbz.demo04_Method;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class TestMethod {
// 共有方法
public String a() {
System.out.println("我是共有方法");
return "ok-a";
}
// 共有有参方法
public String b(String param) {
System.out.println("我是共有方法,传递的参数为: " + param);
return "ok-b-" + param;
}
// 私有方法
private void c() {
System.out.println("我是私有方法");
}
// 共有静态方法
public static void d() {
System.out.println("我静态共有方法");
}
// 私有的静态方法
private static void e() {
System.out.println("我是静态私有方法");
}
}
【示例代码】
package com.dfbz.demo04_Method;
import com.dfbz.demo05_Field.TestEntity;
import org.junit.Test;
import java.lang.reflect.Method;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_反射获取类的方法 {
// 反射空参方法
@Test
public void test1() throws Exception{
// 1. 字节码对象
Class<TestEntity> testEntityClass = TestEntity.class;
// 2. 根据方法名和参数列表反射指定方法
Method aMethod = testEntityClass.getMethod("a",null);
System.out.println(aMethod);
}
// 反射有参方法
@Test
public void test2() throws Exception{
// 1. 字节码对象
Class<TestEntity> testEntityClass = TestEntity.class;
// 2. 根据方法名和参数列表反射指定方法
Method bMethod = testEntityClass.getMethod("b", String.class);
System.out.println(bMethod);
}
// 反射任意权限修饰符修饰的方法
@Test
public void test3() throws Exception{
// 1. 字节码对象
Class<TestEntity> testEntityClass = TestEntity.class;
// 2. 根据方法名和参数列表反射指定方法(如果不是public修饰的要使用getDeclaredMethod)
Method cMethod = testEntityClass.getDeclaredMethod("c", null);
System.out.println(cMethod);
}
// 反射静态方法
@Test
public void test4() throws Exception{
// 1. 字节码对象
Class<TestEntity> testEntityClass = TestEntity.class;
// 2. 获取静态方法(静态方法和普通方法在获取上没有任何的区别)
// Method dMethod = testEntityClass.getMethod("d", null);
Method eMethod = testEntityClass.getDeclaredMethod("e", null);
System.out.println(eMethod);
}
}
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.demo04_Method;
import org.junit.Test;
import java.lang.reflect.Method;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_Method的功能 {
@Test
public void test1() throws Exception {
// 1. 获取字节码对象
Class<TestEntity> testEntityClass = TestEntity.class;
// 2. 获取指定的方法
Method aMethod = testEntityClass.getMethod("a", null);
// 3. 调用这个Method对象所描述的方法, 返回值就是目标方法的返回值
Object result = aMethod.invoke(new TestEntity(), null);
// 这个Method所描述的那个方法的名称
String methodName = aMethod.getName();
System.out.println("【" + methodName + "】方法的返回值: " + result); // ok-a
}
@Test
public void test2() throws Exception {
// 1. 获取字节码对象
Class<TestEntity> testEntityClass = TestEntity.class;
// 2. 获取方法
Method bMethod = testEntityClass.getMethod("b", String.class);
// 3. 调用方法,获取方法的结果集
Object result = bMethod.invoke(new TestEntity(), "hello");
System.out.println("【" + bMethod.getName() + "】方法的返回值: " + result); // ok-b-hello
}
@Test
public void test3() throws Exception {
// 1. 获取字节码对象
Class<TestEntity> testEntityClass = TestEntity.class;
// 2. 获取方法
Method cMethod = testEntityClass.getDeclaredMethod("c", null);
// 只要不是public修饰的方法在访问的时候都加上这一句代码
cMethod.setAccessible(true);
cMethod.invoke(new TestEntity(), null);
}
@Test
public void test4() throws Exception {
// 1. 获取字节码对象
Class<TestEntity> testEntityClass = TestEntity.class;
// 2. 获取方法
Method dMethod = testEntityClass.getMethod("d", null);
dMethod.invoke(null, null);
}
@Test
public void test5() throws Exception {
// 1. 获取字节码对象
Class<TestEntity> testEntityClass = TestEntity.class;
// 2. 获取方法
Method dMethod = testEntityClass.getDeclaredMethod("e", null);
dMethod.setAccessible(true);
dMethod.invoke(null, null);
}
}
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修饰的属性对象,返回数组。包括父类中的属性(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类中添加如下属性:
package com.dfbz.demo05_Field;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class TestEntity {
public String username = "root";
protected String password = "admin";
String phone = "110";
private String email = "110@abc.com";
}
示例代码:
package com.dfbz.demo05_Field;
import org.junit.Test;
import java.lang.reflect.Field;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_反射类的成员变量 {
// 反射所有公有的字段
@Test
public void test1(){
// 获取字节码对象
Class<TestEntity> testEntityClass = TestEntity.class;
Field[] fields = testEntityClass.getFields();
for(Field field:fields){
System.out.println(field);
}
System.out.println("---------");
}
// 反射公有字段
@Test
public void test2() throws Exception {
// 1. 获取字节码对象
Class<TestEntity> testEntityClass = TestEntity.class;
// 获取实体对象
TestEntity testEntity = testEntityClass.newInstance();
// 根据字节码对象获取属性对象
Field field = testEntityClass.getField("username");
// 通过属性对象给指定的书设置值
field.set(testEntity,"zhangsan");
// 获取属性对象的值
Object obj = field.get(testEntity);
System.out.println(obj);
}
// 反射非public修饰的字段
@Test
public void test3() throws Exception {
// 获取字节码对象
Class<TestEntity> testEntityClass = TestEntity.class;
TestEntity testEntity = testEntityClass.newInstance();
// 注意: 获取非public修饰的字段时,需要使用getDeclaredField方法
Field field = testEntityClass.getDeclaredField("password");
// 开启强制访问
field.setAccessible(true);
// 设置该属性字段的额值
field.set(testEntity,"123456");
// 获取属性字段的值
Object object = field.get(testEntity);
System.out.println(object);
}
}
2.5 反射小案例
准备一个cate.properties文件,内容如下:
class=com.dfbz.demo02_Class
name=藜蒿炒腊肉
taste=鲜香辣
recommend=true
根据配置文件创建一个Cate对象,利用反射创建对象并设置属性值;
示例代码:
package com.dfbz.demo06_反射小案例;
import com.dfbz.demo02_Class.Cate;
import org.junit.Test;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.Properties;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_反射小案例 {
@Test
public void test1() throws Exception {
Properties prop = new Properties();
prop.load(new InputStreamReader(Demo01_反射小案例.class.getClassLoader().getResourceAsStream("cate.properties"), "UTF-8"));
System.out.println("properties的内容: " + 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");
Field nameField = clazz.getDeclaredField("name"); // 通过反射获取到Filed对象
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对象: " + cate);
}
}
执行效果:

2.6 反射与泛型
2.6.2 查看泛型擦除
我们之前学习泛型的时候了解过泛型的擦除,即泛型只存在与编译期间,到运行期间将会被擦除;
1)有限制擦除
- 有限制擦除:运行期间将泛型提升为泛型的上边界类型

- 示例代码:
package com.dfbz.demo07_反射与泛型;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_泛型的限制擦除 {
public static void main(String[] args) {
Class<Example> exampleClass = Example.class;
System.out.println("成员变量: ");
Field[] fields = exampleClass.getDeclaredFields();
for (Field field : fields) {
System.out.println(
"字段名:【" + field.getName() + "】," +
"字段类型:【" + field.getType().getSimpleName() + "】"
);
}
System.out.println("---------------------");
System.out.println("成员方法: ");
Method[] methods = exampleClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(
"方法名:【" + method.getName() + "】," +
"方法返回值:【" + method.getReturnType().getSimpleName() + "】"
);
}
System.out.println("---------------------");
}
}
class Example<T extends Number> {
private T param;
public T getParam() {
return param;
}
public void setParam(T param) {
this.param = param;
}
}
运行结果:

2)无限制擦除
- 无限制擦除:运行期间将泛型提升为Object类型

将Example的泛型改为<T>:
package com.dfbz.demo07_反射与泛型;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_无限制擦除 {
public static void main(String[] args) {
Class<Shower> showerClass = Shower.class;
System.out.println("成员变量: ");
Field[] fields = showerClass.getDeclaredFields();
for (Field field : fields) {
System.out.println(
"字段名:【" + field.getName() + "】," +
"字段类型:【" + field.getType().getSimpleName() + "】"
);
}
System.out.println("---------------------");
System.out.println("成员方法: ");
Method[] methods = showerClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(
"方法名:【" + method.getName() + "】," +
"方法返回值:【" + method.getReturnType().getSimpleName() + "】"
);
}
System.out.println("---------------------");
}
}
class Shower<T> {
private T param;
public T getParam() {
return param;
}
public void setParam(T param) {
this.param = param;
}
}
再次运行示例代码:

2.6.3 查看泛型桥接方法
- 泛型的桥接方法:如果我们编写的类实现了一个带有泛型的接口时,在运行期期间JVM会在实现类中帮我们自动的生产一个方法来帮助我们实现泛型接口中被擦除过后的那个方法,这个方法被称为桥接方法;

- 示例代码:
package com.dfbz.demo07_反射与泛型;
import org.junit.Test;
import java.lang.reflect.Method;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03_泛型桥接方法 {
public static void main(String[] args) {
Class<ExampleImpl> exampleClass = ExampleImpl.class;
Method[] methods = exampleClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(
"方法名:【" + method.getName() + "】," +
"方法返回值:【" + method.getReturnType().getSimpleName() + "】"
);
}
}
}
interface IExample<T> {
T method(T t);
}
class ExampleImpl implements IExample<Number> {
@Override
public Number method(Number number) {
return number;
}
}
- 运行效果:

三、注解
注解是一种标记,可以作用于类、方法、方法形参、成员变量上,给类携带一些额外的参数信息,通过反射来提取这些注解上的信息,然后进行一些操作;
3.1 常见的注解
生成帮助文档:@author@version
- @author:用来标识作者姓名。
- @version:用于标识对象的版本号,适用范围:文件、类、方法。
- @Override:检查当前方法是否是重写与父类的;
3.2 自定义注解
3.2.1 定义注解格式
public @interface 注解名{
}
如:定义一个名为Book的注解
public @interface Book {
字段类型 字段名1();
字段类型 字段名2();
}
3.2.2 字段类型的格式
属性的格式
- 格式1:数据类型 属性名();
- 格式2:数据类型 属性名() default 默认值;
示例:
package com.dfbz.anno;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public @interface User {
String username(); // 用户名
String password(); // 密码
char sex(); // 性别
// Date birthday(); // 不能使用Date类型
String birthday(); // 出生日期
String[] friends(); // 朋友
}
Tips:注解字段类型可以有如下:
- 八种基本数据类型(int,float,boolean,byte,double,char,long,short)
- String类型,Class类型,枚举类型,注解类型
- 以上所有类型的一维数组
3.3 使用自定义注解
3.3.1 定义注解
定义一个注解:Book
- 包含属性:String value() 书名
- 包含属性:double price() 价格,默认值为 100
- 包含属性:String[] authors() 多位作者
示例代码:
package com.dfbz.anno;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public @interface Book {
// 书名
String value();
// 价格
double price() default 100;
// 多位作者
String[] authors();
}
3.3.2 使用注解
package com.dfbz.demo01_注解的定义;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Book(value = "《Java入门到精通》", price = 45.8D, authors = {"小灰", "小红"})
public class AnnotationDemo01 {
}
注意事项:
- 1)如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。
- 2)如果属性没有默认值,那么在使用注解时一定要给属性赋值。
3.3.3 特殊属性value
当注解中只有一个属性且名称是value,在使用注解时给value属性赋值可以直接给属性值,无论value是单值元素还是数组类型。
- 定义一个注解:
package com.dfbz.anno;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public @interface Coder {
String value();
}
- 使用注解:
package com.dfbz.demo01_注解的定义;
import com.dfbz.anno.Coder;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Coder("小灰") // value 可写可不写
public class AnnotationDemo02 {
@Coder(value = "小蓝")
public String name;
}
如果注解中除了value属性还有其他属性,且至少有一个属性没有默认值,则在使用注解给属性赋值时,value属性名不能省略。
- 定义注解
package com.dfbz.anno;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public @interface Person {
String value();
int age() default 18;
}
- 使用注解
package com.dfbz.demo01_注解的定义;
import com.dfbz.anno.Person;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Person("小灰")
public class AnnotationDemo03 {
@Person(value = "小蓝", age = 20) // 如果有其他属性要赋值,则value不能省略
public String name;
}
Tips:当注解中除了value还有其他属性要赋值时,在赋值时必须指定全部的属性名,value属性名不能省略。
3.4 元注解
元注解是Java API提供的注解,是用来定义注解的注解。元注解可以限制注解标注的位置,生命周期,是否可以被继承等;
3.4.1 @Target注解
作用:指明此注解用在哪个位置,如果不写默认是任何地方都可以使用。
可选的参数值在枚举类ElemenetType中包括:
TYPE: 用在类,接口上
FIELD:用在成员变量上
METHOD: 用在方法上
PARAMETER:用在参数上
CONSTRUCTOR:用在构造方法上
LOCAL_VARIABLE:用在局部变量上
ANNOTATION_TYPE:用在注解上
- 定义一个注解:
package com.dfbz.anno;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public @interface Student {
}
@Student // 也可以标注在其他注解上
@interface A{}
- 测试可以标注的位置:
如果没有对注解进行位置限制,则默认情况下运行标注在任意位置:
package com.dfbz.demo01_注解的定义;
import com.dfbz.anno.Student;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Student // 标注在类上
public class AnnotationDemo04 {
@Student // 标注在成员变量上
public String name;
@Student // 标注在构造方法上
public AnnotationDemo04(){}
@Student // 标注在构造方法上
public void method(@Student int age){ // 标注在方法形参上
@Student // 标注在局部方法上
String str;
}
}
- 修改Student注解:
package com.dfbz.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Target({
ElementType.TYPE, // 类、接口
ElementType.FIELD, // 成员变量
ElementType.METHOD, // 方法
ElementType.PARAMETER, // 方法(构造方法)形参
ElementType.CONSTRUCTOR, // 构造方法
ElementType.LOCAL_VARIABLE, // 局部变量
ElementType.ANNOTATION_TYPE // 注解
})
public @interface Student {
}
@Student // 也可以标注在其他注解上
@interface A {
}
3.4.2 @Retention注解
作用:定义该注解的生命周期(有效范围)。
可选的参数值在枚举类型RetentionPolicy中包括:
SOURCE:注解只存在于Java源代码中,编译生成的字节码文件中就不存在了。CLASS:注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没有,默认值。RUNTIME:注解存在于Java源代码中、编译以后的字节码文件中、运行时内存中,程序可以通过反射获取该注解。
3.4.3 @Inherited注解
作用:如果该注解被@Inherited所定义,那么标注该注解的类的子类上也会标注有该注解
【示例】
- 定义标注有@Inherited注解的注解:
package com.dfbz.anno;
import java.lang.annotation.Inherited;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Inherited // 该注解允许被继承
public @interface Worker {
String value();
}
- 定义一个类:
package com.dfbz.demo01_注解的定义;
import com.dfbz.anno.Worker;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Worker("小刚")
public class AnnotationDemo05 {
}
// 该子类上也会存在@Worker注解
class SubTest extends AnnotationDemo05{}
3.5 注解解析
当我们标注注解后,注解中携带的信息需要我们手动来解析,通过反射技术来剖析一个类的注解,然后提取里面的信息;
3.5.1 注解解析相关接口
- Annotation:所有注解类型的公共接口,类似所有类的父类是Object。
- AnnotatedElement:定义了与注解解析相关的方法,Class、Method、Field、Constructor等类都继承与该类,常用方法以下四个:
boolean isAnnotationPresent(Class annotationClass):判断当前对象是否有指定的注解,有则返回true,否则返回false。T getAnnotation(Class<T> annotationClass):获得当前对象上指定的注解对象。Annotation[] getAnnotations():获得当前对象及其从父类上继承的所有的注解对象。Annotation[] getDeclaredAnnotations():获得当前对象上所有的注解对象,不包括父类的。
3.5.2 注解的获取
关于注解的获取,我们只需要关心获取所标注的成员是哪一个,然后通过所标注的成员对象来获取对应的注解;
- 1)例如注解标准在类上,那么我们就先获取Class对象,通过Class对象来获取标注在类上的注解;
// 得到方法对象
Method method = clazz.getDeclaredMethod("方法名");
// 根据注解字节码对象得到方法上的注解对象
Student student = method.getAnnotation(Student.class);
- 2)如果注解标注在构造方法上,那么我们就先获取Constructor对象,然后通过Constructor对象来获取标注在构造方法上的注解;
// 获得Class对象
Class c = 类名.class;
// 根据注解字节码对象得到类上的注解对象
Student student = c.getAnnotation(Student.class);
3.5.3 注解信息解析案例
1) 需求如下:
- 定义注解Computer,要求如下:
- 1)属性如下:
- 包含属性:String value() 计算机名
- 包含属性:double price() 价格,默认值为 2000
- 包含属性:String[] factories() 多个厂商
- 2)限制如下:
- 1)限制注解使用的位置:类、方法、构造方法、成员变量
- 2)指定注解的有效范围:RUNTIME(要想注解能在运行期间被解析,生命周期一定要为RUNTIME)
- 3)允许该注解被继承:使用@Inherited
- 1)属性如下:
- 定义Phone注解,要求如下:
- 1)属性如下:
- 包含属性:String value() 手机名称
- 2)限制如下:
- 1)限制注解使用的位置:类
- 2)指定注解的有效范围:RUNTIME(要想注解能在运行期间被解析,生命周期一定要为RUNTIME)
- 3)不允许该注解被继承(不使用@Inherited标注)
- 1)属性如下:
2) 代码实现
- 定义Computer注解:
package com.dfbz.anno;
import java.lang.annotation.*;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Target({
ElementType.TYPE,
ElementType.METHOD,
ElementType.CONSTRUCTOR,
ElementType.FIELD
})
@Retention(RetentionPolicy.RUNTIME)
@Inherited // 允许当前注解被继承
public @interface Computer {
String value();
double price() default 2000.0D;
String[] factories();
}
- 定义Phone注解
package com.dfbz.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Phone {
String value();
}
- 定义注解测试类:
package com.dfbz.demo02_注解的解析;
import com.dfbz.anno.Computer;
import com.dfbz.anno.Phone;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Computer(value = "神舟战神笔记本X8", price = 2888.8D, factories = {"华东厂商,华南厂商"})
@Phone("vivo手机")
public class TestComputer {
@Computer(value = "小米15Pro", price = 1888.8D, factories = {"华北厂商,华南厂商"})
public String name;
@Computer(value = "华为mate book",price = 2899.0D,factories = "华中厂商")
public TestComputer(){}
@Computer(value = "华硕飞行堡垒", price = 888.8D, factories = {"西北厂商,东北厂商"})
public void print() {}
}
- 定义类继承TestComputer:
package com.dfbz.demo02_注解的解析;
import com.dfbz.anno.Phone;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class SubTestComputer extends TestComputer {
}
- 1)解析类上面的注解:
package com.dfbz.demo02_注解的解析;
import com.dfbz.anno.Coder;
import com.dfbz.anno.Computer;
import com.dfbz.anno.Phone;
import com.dfbz.anno.Student;
import org.junit.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_注解的解析 {
/**
* 解析类上面的注解
*
* @throws Exception
*/
@Test
public void test1() throws Exception {
// 1. 先获取字节码对象
Class<TestComputer> computerClass = TestComputer.class;
// 2. 通过字节码对象获取标注在类上面的注解
Computer computer = computerClass.getAnnotation(Computer.class);
Phone phone = computerClass.getAnnotation(Phone.class);
// 3. 打印注解信息
System.out.println(computer.value());
System.out.println(computer.price());
System.out.println(Arrays.toString(computer.factories()));
System.out.println("----------");
System.out.println(phone.value());
}
}
- 2)解析构造方法上面的注解:
/**
* 解析构造方法上的注解
*
* @throws Exception
*/
@Test
public void test2() throws Exception {
// 1. 先获取字节码对象
Class<TestComputer> computerClass = TestComputer.class;
// 2. 通过字节码对象获取指定的构造器
Constructor<TestComputer> constructor = computerClass.getConstructor();
// 3. 通过constructor获取构造器上标注的注解
Computer computer = constructor.getAnnotation(Computer.class);
// 3. 打印注解信息
System.out.println(computer.value());
System.out.println(computer.price());
System.out.println(Arrays.toString(computer.factories()));
}
- 3)解析方法上的注解:
/**
* 解析方法上的注解
*
* @throws Exception
*/
@Test
public void test3() throws Exception {
// 1. 先获取字节码对象
Class<TestComputer> computerClass = TestComputer.class;
// 2. 通过字节码对象获取指定的方法
Method method = computerClass.getMethod("print");
// 3. 通过method获取方法上标注的注解
Computer computer = method.getAnnotation(Computer.class);
// 3. 打印注解信息
System.out.println(computer.value());
System.out.println(computer.price());
System.out.println(Arrays.toString(computer.factories()));
}
- 4)获取成员变量上标注的注解:
/**
* 解析成员变量上的注解
*
* @throws Exception
*/
@Test
public void test4() throws Exception {
// 1. 先获取字节码对象
Class<TestComputer> computerClass = TestComputer.class;
// 2. 通过字节码对象获取指定的方法
Field field = computerClass.getField("name");
// 3. 通过field获取成员变量上标注的注解
Computer computer = field.getAnnotation(Computer.class);
// 3. 打印注解信息
System.out.println(computer.value());
System.out.println(computer.price());
System.out.println(Arrays.toString(computer.factories()));
}
- 5)解析注解的其他方法:
/**
* 解析注解的其他方法
*
* @throws Exception
*/
@Test
public void test5() throws Exception {
// 1. 先获取字节码对象
Class<TestComputer> computerClass = TestComputer.class;
// true
System.out.println(computerClass.isAnnotationPresent(Computer.class));
// true
System.out.println(computerClass.isAnnotationPresent(Phone.class));
// false(即使在TestComputer对象上标注了Student注解,也要让Student注解的生命周期为RUNTIME)
System.out.println(computerClass.isAnnotationPresent(Student.class));
// false(即使在TestComputer对象上标注了Coder注解,也要让Coder注解的生命周期为RUNTIME)
System.out.println(computerClass.isAnnotationPresent(Coder.class));
// 获取TestComputer上标注的所有注解(包括TestComputer父类的注解)
Annotation[] annotations = computerClass.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
System.out.println("-------------------------");
}
3.5.4 注解的继承
默认情况下,子类继承父类时,标注在父类上的注解不会被继承到子类,但如果该注解被@inherited所标注,那么将会被继承到子类;
- 测试代码:
package com.dfbz.demo02_注解的解析;
import org.junit.Test;
import java.lang.annotation.Annotation;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_注解的继承 {
/**
* 注解的继承
*
* @throws Exception
*/
@Test
public void test1() throws Exception {
// 获取TestComputer类的字节码对象
Class<TestComputer> computerClass = TestComputer.class;
// 获取TestComputer上标注的所有注解(包括TestComputer父类的注解,前提是指定的注解要被@Inherited所标注)
Annotation[] annotations = computerClass.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
System.out.println("-------------------------");
// 获取SubTestComputer类的字节码对象
Class<SubTestComputer> subTestComputerClass = SubTestComputer.class;
// 获取SubTestComputer上标注的所有注解(包括SubTestComputer父类的注解,前提是指定的注解要被@Inherited所标注)
Annotation[] subAnnotations = subTestComputerClass.getAnnotations();
for (Annotation subAnnotation : subAnnotations) {
System.out.println(subAnnotation);
}
}
}
3.6 注解案例
我们尝试自己定义一个@MyTest注解来模拟Junit的@Test注解的功能
1) 案例分析如下:
- 模拟Junit测试的注释@Test,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得。
- 然后编写目标类(测试类),然后给目标方法(测试方法)使用 @MyTest注解,编写三个方法,其中两个加上@MyTest注解。
- 最后编写调用类,使用main方法调用目标类,模拟Junit的运行,只要有@MyTest注释的方法都会运行。
2) 代码实现
- 定义@MyTest注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
- 定义目标类:
package com.dfbz.demo03_综合案例;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class MyTestDemo {
@MyTest
public void test1(){
System.out.println("自定义的Test1方法执行啦!!~~~");
}
public void test2(){
System.out.println("自定义的Test2方法执行啦!!~~~");
}
@MyTest
public void test3(){
System.out.println("自定义的Test3方法执行啦!!~~~");
}
}
- 定义测试类:
package com.dfbz.demo03_综合案例;
import java.lang.reflect.Method;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_测试代码 {
public static void main(String[] args) throws Exception{
Class<MyTestDemo> demoClass = MyTestDemo.class;
// 获取这个类的有public修饰的方法
Method[] methods = demoClass.getMethods();
for (Method method : methods) {
if( method.isAnnotationPresent(MyTest.class)){
// 代表这个方法上面有标注@MyTest注解
method.invoke(new MyTestDemo(),null);
}
}
}
}

四、Lombok插件
Lombok 是一种 Java 实用工具,可用来帮助开发人员消除 JavaBean 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注解实现这一目的。通过在开发环境中实现 Lombok,开发人员可以节省构建诸如 hashCode() 和 equals() 这样的方法
4.1 下载Lombok
官方地址:https://projectlombok.org/download

4.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对注解的支持

使用idea下载的插件默认的位置为:C:\Users\Administration\AppData\Local\JetBrains\IntelliJIdea2020.1\plugins
4.3 Lombok插件的使用
在模块的一级目录下,创建一个lib文件夹(名称随意),将lombok的jar包拷贝到目录中,然后右击选择:Add as Library...


Lombok中提供了很多的注解来代替JavaBean中的一些方法,如:
@ToString:重写toString方法。@Setter:提供set方法。@Getter:提供get方法。@NoArgsConstructor:为类提供一个无参的构造方法。@AllArgsConstructor:为类提供一个全参的构造方法。@Data: @Data 是 @Getter、 @Setter、 @ToString、 @EqualsAndHashCode 的快捷方式。@EqualsAndHashCode:为你提供一个equlas和hashCode方法@NonNull:注解在属性上,用来校验参数非空,可以帮助我们避免空指针。@Slf4j:为类提供一个 属性名为log 的 log4j 日志对象。@Log4j:为类提供一个 属性名为log 的 log4j 日志对象。
编写一个Student类:
package com.dfbz.demo;
import lombok.*;
/**
* @author lscl
* @version 1.0
* @intro:
*/
//@ToString
//@Getter
//@Setter
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Student {
private String name;
private Integer age;
public void eat(@NonNull String food) {
System.out.println(name + "刚刚吃了: " + food);
}
}
测试类:
package com.dfbz.demo;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_测试Lombok提供的方法 {
public static void main(String[] args) {
Student s1 = new Student("小灰", 20); // Lombok的有参构造
Student s2 = new Student(); // Lombok的无参构造
// lombok提供的set方法
s2.setName("小灰");
s2.setAge(20);
// lombok提供的get方法
System.out.println(s1.getName()); // 小灰
// lombok的equals方法
System.out.println(s1.equals(s2)); // true,说明lombok重写了equals方法
// lombok的hashCode方法
System.out.println(s1.hashCode() == s2.hashCode()); // true,说明lombok重写了hashCode方法
// lombok的toString方法
System.out.println(s1);
s1.eat("南昌拌粉~");
// 测试lombok的@NonNull注解
s1.eat(null); // 出现异常: Exception in thread "main" java.lang.NullPointerException: food is marked @NonNull but is null
}
}
输出结果:


浙公网安备 33010602011771号