一、JUnit单元测试
1. JUnit的介绍
JUnit是一个Java语言的单元测试工具。有了它我们在开发阶段就可以对自己编写的功能模块进行单元测试(就是一块一块去测试),看看是否达到具体预期(这样小Bug我们自己就能解决)。
黑盒测试:不需要写代码,给定输入值,看程序是否能够输出期望的值。
白盒测试:需要写代码的。关注程序具体的执行流程。
JUnit通过注解识别测试方法:@Test
、@Before
、@After
。
- @Test:用于修饰需要执行的测试方法。
- @Before:修饰的方法会在测试方法之前被自动执行。
- @After:修饰的方法会在测试方法执行之后自动被执行。
JUnit以上三种单元测试方法的注意事项:
- 测试方法不能是静态方法。
- 测试方法不能有返回值。
- 测试方法不能有参数。
2. JUnit的使用
1、下载JUnit相关的jar包
junit-4.12下载地址: junit-4.12 https://mvnrepository.com/artifact/junit/junit/4.12
hamcrest-core下载地址: https://mvnrepository.com/artifact/org.hamcrest/hamcrest-core/1.3
2、在模块下面创建一个lib目录,用于存放jar包
3、选中lib目录并鼠标右键,然后找到【Add as Library】并点击,将jar包添加到模块中。
4、创建一个测试类,编写测试方法,分别@Test、@Before、@After注解修饰。
/**
* Junit快速入门
* @author 白豆五
* @version 2022/11/19 22:05
* @since JDK8
*/
public class JUnitTest {
// 定义静态成员变量 便于让测试方法访问
private static List<Integer> list = new ArrayList<>();
// 在测试方法之前执行
@Before
public void init() {
list.add(111);
list.add(222);
list.add(13);
list.add(25);
list.add(16);
}
//测试方法
@Test
public void test() {
list.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
}
// 在测试方法之后执行
@After
public void print() {
System.out.println(list);
}
}
二、反射(Reflection)
1. 类加载器
1.1 类加载器的时机
当程序运行之后,第一次使用某个类的对象,类加载器(ClassLoader)会将该类的.class
文件从磁盘加载到内存中,然后将该类的信息(如 成员变量、成员方法、构造方法)存储到 java.lang.Class
对象中。
什么时候类加载器会将类的字节码文件加载到内存,并使用Class对象存储类的相关信息?
1、创建类的实例。
2、使用类的静态变量。
3、使用这个类的静态方法。
4、使用反射的方式创建某个类或者接口的对应Class对象。
5、初始化某个类的子类。
6、使用java命令运行某个主类(带main方法的类)。
public class TestClassLoder {
/**
* 类加载器的时机
*/
@Test
public void testClassLoder() {
// Animal animal = new Animal(); // 1、创建类的实例
// Animal.color="red"; // 2、使用类调用静态成员变量
// Animal.print(); // 3、使用类调用静态方法
/*
try {
// 4、通过反射加载这个类获取字节码对象
Class clazz = Class.forName("com.baidou.p3_loder_time.Animal");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}*/
// 5、初始化子类
Animal dog = new Dog();
}
}
class Animal {
// 静态成员变量
static String color = "blue";
// 静态代码块在类加载的时候会被执行,而且只会执行一次
static {
System.out.println("执行了Animal的静态代码块");
}
// 静态方法
static void print() {
System.out.println(color);
}
}
class Dog extends Animal {
}
1.2 类加载器的分类
Java中有三种不同的类加载器(ClassLoader)用于加载不同种类的class文件:(JDK8)
1、BootstrapClassLoader:根类加载器,也被称为引导类加载器。
-
负责Java核心类库的加载。(如 jdk1.8.0_201\jre\lib\rt.jar)
-
它是用C++编写的,是JVM自带的类加载器。
-
该加载器无法直接获取。(如 System、String获取加载器返回的值为null)
2、ExtClassLoader:扩展类加载器。
- ***负责加载JRE的扩展目录中的jar包。***(如 jdk1.8.0_201\jre\lib\ext)
3、AppClassLoader:系统类加载器/应用类加载器。
- Java语言编写的类加载器,用于加载我们定义的类和第三方jar包中的类 。
类加载器加载机制
双亲委派机制: 谁用谁加载。
当加载类的时候,首先会问委托父加载器(如AppClassLoader)它负不负责加载,如果它不会则继续向上找。但是不管是谁加载 .class
文件只能被加载一次。
这三个类加载器之间不存在继承关系,ClassLoader是的他们的最终父类。
1.3 如何获取类加载器
用Class对象.getClassLoader()方法获取类加载器。
import org.junit.Test;
import sun.net.spi.nameservice.dns.DNSNameService;
/**
* 获取类加载器
* @author 白豆五
* @version 2022/11/21 19:49
* @since JDK8
*/
public class Demo04ClassLoder {
/**
* 获取根类加载器
*/
@Test
public void boot() {
// String类 是核心类库rt.jar里的 由BootstrapClassLoader负则加载
// 获取String类的Class对象
Class<String> clazz = String.class;
// 获取类加载器对象
ClassLoader classLoader = clazz.getClassLoader();
System.out.println(classLoader);// BootstrapClassLoader是根类加载器是C++编写,不让我们直接获取,所以返回值为null
}
/**
* 获取扩展加载器
*/
@Test
public void ext() {
// DNSNameService是扩展类 由ExtClassLoader负则加载
// 获取DNSNameService类的Class对象
Class<DNSNameService> clazz = DNSNameService.class;
// 获取类加载器对象
ClassLoader classLoader = clazz.getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$ExtClassLoader@28a418fc
}
/**
* 获取应用类加载器
*/
@Test
public void app() {
// Demo04ClassLoder类是自己编写的类 由AppClassLoader负则加载
// 获取Demo04ClassLoder类的Class对象
Class<Demo04ClassLoder> clazz = Demo04ClassLoder.class;
// 获取类加载器对象
ClassLoader c1 = clazz.getClassLoader();
System.out.println(c1);//sun.misc.Launcher$AppClassLoader@18b4aac2
// 获取应用类加载器的委托父加载器对象(扩展类加载器)
ClassLoader c2 = c1.getParent();
System.out.println(c2);//sun.misc.Launcher$ExtClassLoader@28a418fc
// 获取扩展类加载器的委托父加载器对象(根类加载器)
ClassLoader c3 = c2.getParent();
System.out.println(c3);//因他是c++编写不让直接获取,所以拿到的是null
}
}
2. 反射的介绍
原文:https://blog.csdn.net/weixin_42298270/article/details/113371164
反射机制指的是程序在运行时能够获取自身的信息。在Java中,只要给定类的名字, 那么就可以通过反射机制来获得类的所有信息。
要使用一个类,就要先把它加载到虚拟机中,生成一个Class对象。这个Class对象就保存了这个类的一切信息。反射机制的实现,就是获取这个Class对象,通过Class对象去访问类、对象的元数据以及运行时的数据。
反射是一种机制,利用该机制在程序运行的过程中,对类进行解剖并且去操作类的成员(成员变量,成员方法,构造方法)。要想使用反射,就必须要获取该类的字节码对象(也叫Class对象)。
反射的应用场景:如主流的开发框架(如spring-frammework、mybaits等等)、IDEA(智能化的语法提示等等)。
3. Class类的对象
Class类也是有对象的,但是程序员无法自己创建,由JVM帮助创建,程序员可以获取到该Class类型的对象,从而完成相关的操作。
3.1. 获取Class对象的三种方法
- 方式一:
Class c1 = Class.forName("类的全名称,即包名.类名");
, forName是Class类的静态方法。 - 方式二:
Class c2 = 类名.class属性
,每个类都默认有一个class属性。 - 方式三:
Class c3 = 对象.getClass();
,getClass是Object类的方法。
pojo类:
public class User {
private String name;
private int age;
//空参构造
public User() {
}
//满参
public User(String name, int age) {
this.name = name;
this.age = age;
}
//只给name赋值
public User(String name) {
this.name = name;
}
//只给age赋值
private User(int age) {
this.age = age;
}
private char[] my2CharArray(String str) {
return str.toCharArray();
}
public int getSum(int a, int b) {
return a + b;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
测试类:
/**
* 获取Class对象
*
* @author 白豆五
* @version 2022/11/21 21:49
* @since JDK8
*/
public class Demo04GetClass {
/**
* 方式一:对象.getClass()获取对应的字节码对象
*/
@Test
public void test1(){
User user = new User();
Class c1 = user.getClass();
//调用Class类的toString()方法
System.out.println(c1); //class com.baidou.pojo.User
}
/**
* 方式二:类名.class属性获取对应的字节码对象
*/
@Test
public void test2(){
Class<User> c2 = User.class;
//调用Class类的toString()方法
System.out.println(c2); //class com.baidou.pojo.User
}
/**
* 方式三:Class.forName()获取对应的字节码对象
*/
@Test
public void test3() throws ClassNotFoundException {
Class<?> c3 = Class.forName("com.baidou.pojo.User");
System.out.println(c3); //class com.baidou.pojo.User
}
/**
* 基本数据类型,也有对应的Class对象
*/
@Test
public void test4(){
Class<Integer> c1 = int.class;
System.out.println(c1); //int
}
}
3.2 Class类的常用方法
下面的成员方法都是通过与类关联的Class对象调用。
public String getName()
:获取类的全路径名,即包名+类名。public String getSimpleName()
:获取类的类名(类名、接口名、注解名等等)。public T newInstance()throws InstantiationException, IllegalAccessException
:使用反射的方式创建对象,会调用该类的无参构造方法,如果该类没有无参构造方法会报 InstantiationException(实例化异常)。
/**
* Class类的常用方法
*/
@Test
public void testClassNewInstance() {
// 获取Animal类的字节码对象
Class<Animal> animalClass = Animal.class;
String name = animalClass.getName(); //获取Animal类的全路径名,包名+类名
System.out.println(name); //com.baidou.p3_loder_time.Animal
System.out.println(animalClass.getSimpleName()); //获取类名
try {
// 反射的方式创建animal对象
Animal animal = animalClass.newInstance();
System.out.println(animal);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
Java源程序通过编译会生成字节码文件,字节码文件会被类加载器加载到内存方法区中,同时针对.class
文件创建一个Class类型的对象(存储类的信息),然后该对象被保存到堆内存中。这个Class对象里面封装了操作构造器、成员方法、成员变量的api,这样我们通过反射可以操作.class
文件中成员变量、成员方法、构造方法啦。
4. 反射获取构造方法(Constructor)
反射获取构造方法的步骤:
1、获取类的Class对象
2、Class对象.get构造器的方法。
4.1 获取构造方法
1、返回一个public修饰的指定参数构造方法,不传参默认调用无参构造方法。
public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException,SecurityException
2、返回多个public修饰的构造方法。
public Constructor<?>[] getConstructors() throws SecurityException
3、返回一个任意修饰符的指定参数构造方法。
//返回一个任意修饰符的指定参数构造方法
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
throws NoSuchMethodException,SecurityException
// 暴力反射
//java.lang.reflect.AccessibleObject类,成员方法:
public void setAccessible(boolean flag)
// 参数:true,取消 Java 语言访问检查 private 失效
// 参数:false,实施 Java 语言访问检查 private 有效
// 私有构造方法对象调用setAccessible()方法,取消Java语言访问检查
constructor.setAccessible(true);
//Constructor,Field,Method类,都可以调用setAccessible方法,进行暴力反射
4、返回多个任意修饰符的构造方法。
public Constructor<?>[] getDeclaredConstructors() throws SecurityException
示例:
public class User {
private String name;
private int age;
//空参构造
public User() {
}
//满参
public User(String name, int age) {
this.name = name;
this.age = age;
}
//只给name赋值
public User(String name) {
this.name = name;
}
//只给age赋值
private User(int age) {
this.age = age;
}
private char[] my2CharArray(String str) {
return str.toCharArray();
}
public int getSum(int a, int b) {
return a + b;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
(1) 获取public修饰的所有构造方法对象
/**
* 获取public修饰的所有构造方法对象
*/
@Test
public void test1() throws ClassNotFoundException {
//获取类的Class对象
Class<?> c1 = Class.forName("com.baidou.pojo.User");
//取public修饰的所有构造方法对象
for (Constructor<?> cons : cons1) {
System.out.println(cons);
}
}
(2) 获取所有构造方法对象(包含private修饰的)
/**
* 获取所有构造方法对象(包含private修饰的)
*/
@Test
public void test2() throws ClassNotFoundException {
//获取类的Class对象
Class<?> c2 = Class.forName("com.baidou.pojo.User");
// 获取所有构造方法对象(包含private修饰的)
Constructor<?>[] cons2 = c2.getDeclaredConstructors();
for (Constructor<?> constructor : cons2) {
System.out.println(constructor);
}
}
(3) 获取public修饰的空参构造方法对象
/**
* 获取public修饰的空参构造方法对象
*/
@Test
public void test3() throws Exception {
//获取类的Class对象
Class<?> c3 = Class.forName("com.baidou.pojo.User");
//获取public修饰的空参构造方法对象
Constructor<?> cons3 = c3.getConstructor();
System.out.println(cons3);
}
(4) 获取public修饰的第一个参数是String类型,第二个参数是int类型的构造方法对象
/**
* 获取public修饰的第一个参数是String类型,第二个参数是int类型的构造方法对象
*/
@Test
public void test4() throws Exception {
//获取类的Class对象
Class<?> c4 = Class.forName("com.baidou.pojo.User");
//获取public修饰的第一个参数是String类型,第二个参数是int类型的构造方法对象
Constructor<?> cons4 = c4.getConstructor(String.class,int.class);
System.out.println(cons4);
}
(5) 获取private修饰的参数是int类型的构造方法对象
/**
* 获取private修饰的参数是int类型的构造方法对象
*/
@Test
public void test5() throws Exception {
//获取类的Class对象
Class<?> c5 = Class.forName("com.baidou.pojo.User");
//获取private修饰的参数是int类型的构造方法对象
Constructor<?> cons5 = c5.getDeclaredConstructor(int.class);
System.out.println(cons5);
}
4.2 执行构造方法创建具体对象
1、调用java.lang.reflect.Constructor类中的newInstance()方法:
// 根据方法参数传递的具体数据,调用指定的构造方法,从而创建一个具体的对象
// 可变参数 可以是0个或多个 ,Object[]
public T newInstance(Object ... initargs)
2、快捷方式:Class对象.newInstance()方法
,调用该类的无参构造方法创建对象,如果该类没有无参构造方法会报 InstantiationException(实例化异常)。
public T newInstance()throws InstantiationException, IllegalAccessException
示例:反射获取空参构造方法并运行
/**
* 反射获取空参构造方法并运行
*/
@Test
public void newInstance1() throws Exception {
// 获取字节码对象
Class<?> clazz = Class.forName("com.baidou.pojo.User");
// 反射获取空参构造方法对象
Constructor<?> con = clazz.getConstructor();
// 执行空参构造方法对象,创建具体的实例
User user = (User) con.newInstance();
System.out.println(user);
}
示例:反射带参构造方法并运行
/**
* 反射带参构造方法并运行
*/
@Test
public void newInstance2() throws Exception {
// 获取字节码对象
Class<?> clazz = Class.forName("com.baidou.pojo.User");
// 反射获取满参构造方法对象 public User(String name,int age)
Constructor<?> con = clazz.getConstructor(String.class,int.class);
// 执行满参构造方法对象,创建具体的实例
User user = (User) con.newInstance("白豆五",18);
System.out.println(user);
}
示例:反射创建对象的快捷方式
/**
* 反射创建对象的快捷方式
*/
@Test
public void newInstance3() throws Exception {
// 获取字节码对象
Class<?> clazz = Class.forName("com.baidou.pojo.User");
// 反射创建对象的快捷方式 class对象.newInstance()方法
Object obj = clazz.newInstance();
System.out.println(obj);
}
示例:反射获取私有构造方法并运行
/**
* 反射获取私有构造方法并运行
*/
@Test
public void newInstance4() throws Exception {
// 获取字节码对象
Class<?> clazz = Class.forName("com.baidou.pojo.User");
// 反射获取私有构造方法对象 p private User(int age)
Constructor<?> con1 = clazz.getConstructor( int.class);
// 执行私有构造方法对象,创建具体的实例
User user = (User) con1.newInstance( 18);
System.out.println(user);
}
解决方案:使用getDeclaredConstructor()方法获取私有方法
/**
* 反射获取私有构造方法并运行
*/
@Test
public void newInstance4() throws Exception {
// 获取字节码对象
Class<?> clazz = Class.forName("com.baidou.pojo.User");
// 反射获取私有构造方法对象 p private User(int age)
// Constructor<?> con1 = clazz.getConstructor( int.class); //NoSuchMethodException
Constructor<?> con2 = clazz.getDeclaredConstructor(int.class);
// 执行私有构造方法对象,创建具体的实例
User user = (User) con2.newInstance( 18);
System.out.println(user);
}
解决方案:取消访问检查
/**
* 反射获取私有构造方法并运行
*/
@Test
public void newInstance4() throws Exception {
// 获取字节码对象
Class<?> clazz = Class.forName("com.baidou.pojo.User");
// 反射获取私有构造方法对象 p private User(int age)
// Constructor<?> con1 = clazz.getConstructor( int.class); //NoSuchMethodException
Constructor<?> con2 = clazz.getDeclaredConstructor(int.class);
con2.setAccessible(true); // 取消访问检查
// 执行私有构造方法对象,创建具体的实例
User user = (User) con2.newInstance( 18);
System.out.println(user);
}
5. 反射获取成员变量(Field)
反射第一步先得到类对象,然后从类对象中获取类的成分对象。
Class类中获取成员变量的方法如下:
-
Field[] getFields()
:返回所有成员变量对象的数组(只能拿public的)。 -
Field[] getDeclaredFields()
:返回所有成员变量对象的数组,存在就能拿到。 -
Field getField(String name)
:返回单个成员变量对象(只能拿public的)。 -
Field getDeclaredField(String name)
:返回单个成员变量对象,存在就能拿到。
Field类中取值、赋值的方法如下:
void set(Object obj,Object value)
:为Field对象赋值。Object get(Object obj)
:获Field对象的取值。
示例:反射获取成员变量
User.java
public class User {
private String name;
private int age;
//空参构造
public User() {
}
//满参
public User(String name, int age) {
this.name = name;
this.age = age;
}
//只给name赋值
public User(String name) {
this.name = name;
}
//只给age赋值
private User(int age) {
this.age = age;
}
private char[] my2CharArray(String str) {
return str.toCharArray();
}
public int getSum(int a, int b) {
return a + b;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
/**
* 反射获取成员变量
*/
@Test
public void testFiled() throws Exception {
// 获取Class对象
Class<User> clazz = User.class;
// 快捷方式创建对象
User user = clazz.newInstance();
// 获取私有Field对象
Field f1 = clazz.getDeclaredField("name");
Field f2 = clazz.getDeclaredField("age");
f1.setAccessible(true); //取消访问检查
f2.setAccessible(true); //取消访问检查
f1.set(user, "张三"); //为name属性赋值
f2.set(user, 18); //为age属性赋值
String name = (String) f1.get(user); //取值
int age = (int) f2.get(user); //取值
System.out.println(name + "::" + age);
}
注意:如果某成员变量是非public的,需要打开权限(暴力反射,setAccessible(true)),然后再取值、赋值。
6. 反射获取方法(Method)
也是通过Class对象拿Method对象。
Class类中获取成员方法的方法如下:
-
Method[] getMethods()
:返回所有成员方法对象的数组(只能拿public的)。 -
Method[] getDeclaredMethods()
:返回所有成员方法对象的数组,存在就能拿到。 -
Method getMethod(String name,Class<?>...parameterTypes)
:返回单个成员方法对象(只能拿public的)。 -
Method getDeclaredMethod(String name,Class<?>...parameterTypes)
:返回单个成员方法对象,存在就能拿到。
Method类中用于触发执行的方法如下:
Object invoke(Object obj, Object... args)
: 运行方法。- 参数一:用obj对象调用该方法。
- 参数二:调用方法的传递的参数(如果没有就不写)。
- 返回值:方法的返回值(如果没有就不写)。
示例1:获取setName()、getName()、toString()方法并执行。
/**
* 获取setName()、getName()、toString()方法并执行
* public String getName()
* public void setName(String name)
* public String toString()
*/
@Test
public void testgetMethoods() throws Exception {
// 获取Class对象
Class<User> clazz = User.class;
// 快捷方式创建对象
User user = clazz.newInstance();
// 获取Method对象
Method setName = null;
Method getName = null;
Method toString = null;
Method[] methods = clazz.getMethods();
for (Method method : methods) {
switch (method.getName()) {
case "setName":
setName = method;
break;
case "getName":
getName = method;
break;
case "toString":
toString = method;
break;
}
}
// System.out.println(setName);
// System.out.println(getName);
// System.out.println(toString);
// 执行Method对象对应的方法
setName.invoke(user,"张三");
Object name = getName.invoke(user);
System.out.println(name);
Object tostr = toString.invoke(user);
System.out.println(tostr);
}
三、注解(Annotation)
1. 什么是注解?
- 注解(Annotation): 也叫元数据。一种代码级别的说明。
- 它是JDK5.0引入的一个新特性,与类、接口、枚举是在同一个层次。
- 它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,标注/注释(comment)。
2. 注解的作用
- 编写文档:通过代码里标识的注解生成文档。(例如,生成文档javadoc文档)
- 代码分析:通过代码里标识的注解对代码进行分析。(例如,注解的反射)
- 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查。(例如,Override覆盖重写)
3. 常见注解
@author
:用来标识作者名。@version
:用于标识对象的版本号,适用范围:文件、类、方法。@Override
:用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。@FunctionalInterface
: 检测是否是函数式接口(JDK8新特性)。@Deprecated
:可以用来注解类、接口、成员方法和成员变量等,用于表示某个元素(类、方法等)已过时。当其他程序使用已过时的元素时,编译器将会给出警告(删除线)。@SuppressWarnings
:取消显示指定的编译器警告。- @SuppressWarnings(value=“all”):抑制所有警告。
- @SuppressWarnings(value=“deprecation”): 抑制过期方法警告。
…
4. 自定义注解
自定义注解就是自己做一个注解来玩玩。
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口。
自定义注解的格式:
public @interface 注解名 {
属性集
// public 属性类型 属性名() default 默认值;
}
// 空注解: 没有任何属性
/*
有属性集的注解:
属性的定义格式一:数据类型 属性名(); 没有默认值的属性
属性的定义格式二:数据类型 属性名() default 默认值; 有默认值的属性型对应的一维数组
*/
//注解属性可以选择的数据类型:8种基本类型,String类型,枚举类型,注解类型,Class类型以及以上任意类
// 空注解
public @interface MyTest {
//没有任何属性
}
//定义有属性集的注解
public @interface MyTest2 {
String name();//String 类型的属性 name,没有默认值
int age() default 18;//int 类型的属性 age,默认值 18
String[] hobbies();//String 类型的数组 hobbies
MyAnno01 myAnno01();//注解类型
}