JUnit是一个Java语言的单元测试工具。有了它我们在开发阶段就可以对自己编写的功能模块进行单元测试(就是一块一块去测试),看看是否达到具体预期(这样小Bug我们自己就能解决)。
黑盒测试:不需要写代码,给定输入值,看程序是否能够输出期望的值。
白盒测试:需要写代码的。关注程序具体的执行流程。
JUnit通过注解识别测试方法:@Test
、@Before
、@After
。
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 list = new ArrayList<>();// 在测试方法之前执行@Beforepublic void init() {list.add(111);list.add(222);list.add(13);list.add(25);list.add(16);}//测试方法@Testpublic void test() {list.sort(new Comparator() {@Overridepublic int compare(Integer o1, Integer o2) {return o1 - o2;}});}// 在测试方法之后执行@Afterpublic void print() {System.out.println(list);}
}
当程序运行之后,第一次使用某个类的对象,类加载器(ClassLoader)会将该类的.class
文件从磁盘加载到内存中,然后将该类的信息(如 成员变量、成员方法、构造方法)存储到 java.lang.Class
对象中。
什么时候类加载器会将类的字节码文件加载到内存,并使用Class对象存储类的相关信息?
1、创建类的实例。
2、使用类的静态变量。
3、使用这个类的静态方法。
4、使用反射的方式创建某个类或者接口的对应Class对象。
5、初始化某个类的子类。
6、使用java命令运行某个主类(带main方法的类)。
public class TestClassLoder {/*** 类加载器的时机*/@Testpublic 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 {}
Java中有三种不同的类加载器(ClassLoader)用于加载不同种类的class文件:(JDK8)
1、BootstrapClassLoader:根类加载器,也被称为引导类加载器。
负责Java核心类库的加载。(如 jdk1.8.0_201\jre\lib\rt.jar)
它是用C++编写的,是JVM自带的类加载器。
该加载器无法直接获取。(如 System、String获取加载器返回的值为null)
2、ExtClassLoader:扩展类加载器。
3、AppClassLoader:系统类加载器/应用类加载器。
类加载器加载机制
双亲委派机制: 谁用谁加载。
当加载类的时候,首先会问委托父加载器(如AppClassLoader)它负不负责加载,如果它不会则继续向上找。但是不管是谁加载 .class
文件只能被加载一次。
这三个类加载器之间不存在继承关系,ClassLoader是的他们的最终父类。
用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 {/*** 获取根类加载器*/@Testpublic void boot() {// String类 是核心类库rt.jar里的 由BootstrapClassLoader负则加载// 获取String类的Class对象Class clazz = String.class;// 获取类加载器对象ClassLoader classLoader = clazz.getClassLoader();System.out.println(classLoader);// BootstrapClassLoader是根类加载器是C++编写,不让我们直接获取,所以返回值为null}/*** 获取扩展加载器*/@Testpublic void ext() {// DNSNameService是扩展类 由ExtClassLoader负则加载// 获取DNSNameService类的Class对象Class clazz = DNSNameService.class;// 获取类加载器对象ClassLoader classLoader = clazz.getClassLoader();System.out.println(classLoader);//sun.misc.Launcher$ExtClassLoader@28a418fc}/*** 获取应用类加载器*/@Testpublic void app() {// Demo04ClassLoder类是自己编写的类 由AppClassLoader负则加载// 获取Demo04ClassLoder类的Class对象Class 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}
}
原文:https://blog.csdn.net/weixin_42298270/article/details/113371164
反射机制指的是程序在运行时能够获取自身的信息。在Java中,只要给定类的名字, 那么就可以通过反射机制来获得类的所有信息。
要使用一个类,就要先把它加载到虚拟机中,生成一个Class对象。这个Class对象就保存了这个类的一切信息。反射机制的实现,就是获取这个Class对象,通过Class对象去访问类、对象的元数据以及运行时的数据。
反射是一种机制,利用该机制在程序运行的过程中,对类进行解剖并且去操作类的成员(成员变量,成员方法,构造方法)。要想使用反射,就必须要获取该类的字节码对象(也叫Class对象)。
反射的应用场景:如主流的开发框架(如spring-frammework、mybaits等等)、IDEA(智能化的语法提示等等)。
Class类也是有对象的,但是程序员无法自己创建,由JVM帮助创建,程序员可以获取到该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;}@Overridepublic 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()获取对应的字节码对象*/@Testpublic void test1(){User user = new User();Class c1 = user.getClass();//调用Class类的toString()方法System.out.println(c1); //class com.baidou.pojo.User}/*** 方式二:类名.class属性获取对应的字节码对象*/@Testpublic void test2(){Class c2 = User.class;//调用Class类的toString()方法System.out.println(c2); //class com.baidou.pojo.User}/*** 方式三:Class.forName()获取对应的字节码对象*/@Testpublic void test3() throws ClassNotFoundException {Class> c3 = Class.forName("com.baidou.pojo.User");System.out.println(c3); //class com.baidou.pojo.User}/*** 基本数据类型,也有对应的Class对象*/@Testpublic void test4(){Class c1 = int.class;System.out.println(c1); //int}
}
下面的成员方法都是通过与类关联的Class对象调用。
public String getName()
:获取类的全路径名,即包名+类名。public String getSimpleName()
:获取类的类名(类名、接口名、注解名等等)。public T newInstance()throws InstantiationException, IllegalAccessException
:使用反射的方式创建对象,会调用该类的无参构造方法,如果该类没有无参构造方法会报 InstantiationException(实例化异常)。/*** Class类的常用方法*/
@Test
public void testClassNewInstance() {// 获取Animal类的字节码对象Class animalClass = Animal.class;String name = animalClass.getName(); //获取Animal类的全路径名,包名+类名System.out.println(name); //com.baidou.p3_loder_time.AnimalSystem.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
文件中成员变量、成员方法、构造方法啦。
反射获取构造方法的步骤:
1、获取类的Class对象
2、Class对象.get构造器的方法。
1、返回一个public修饰的指定参数构造方法,不传参默认调用无参构造方法。
public Constructor getConstructor(Class>... parameterTypes) throws NoSuchMethodException,SecurityException
2、返回多个public修饰的构造方法。
public Constructor>[] getConstructors() throws SecurityException
3、返回一个任意修饰符的指定参数构造方法。
//返回一个任意修饰符的指定参数构造方法
public Constructor 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;}@Overridepublic 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);
}
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); //NoSuchMethodExceptionConstructor> 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); //NoSuchMethodExceptionConstructor> con2 = clazz.getDeclaredConstructor(int.class);con2.setAccessible(true); // 取消访问检查// 执行私有构造方法对象,创建具体的实例User user = (User) con2.newInstance( 18);System.out.println(user);
}
反射第一步先得到类对象,然后从类对象中获取类的成分对象。
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;}@Overridepublic 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 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)),然后再取值、赋值。
也是通过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)
: 运行方法。 示例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 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);
}
@author
:用来标识作者名。@version
:用于标识对象的版本号,适用范围:文件、类、方法。@Override
:用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。@FunctionalInterface
: 检测是否是函数式接口(JDK8新特性)。@Deprecated
:可以用来注解类、接口、成员方法和成员变量等,用于表示某个元素(类、方法等)已过时。当其他程序使用已过时的元素时,编译器将会给出警告(@SuppressWarnings
:取消显示指定的编译器警告。 …
自定义注解就是自己做一个注解来玩玩。
使用@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,默认值 18String[] hobbies();//String 类型的数组 hobbiesMyAnno01 myAnno01();//注解类型
}