29Java基础之高级技术
单元测试
- 单元测试:就是针对最小的功能单元(方法),编写测试代码对其进行正确性测试。
之前是如何进行单元测试的?有什么问题?
- 之前只能在main方法编写测试代码,去调用其他方法进行测试。
- 无法实现自动化测试,一个方法测试失败,可能影响其他方法的测试。
- 无法得到测试的报告,需要程序员自己去观察测试是否成功。
Junit单元测试框架
- 可以用来对方法进行测试,它是第三方公司开源出来的(很多开发工具已经集成了Junit框架,比如IDEA)
优点
- 可以灵活的编写测试代码,可以针对某个方法执行测试,也支持一键完成对全部方法的自动化测试,且各自独立。
- 不需要程序员去分析测试的结果,会自动生成测试报告出来。
![image]()
Junit单元测试——快速入门
需求
- 某个系统,有多个业务方法,请使用Junit单元测试框架,编写测试代码,完成对这些方法的正确性测试。
具体步骤
- 将Junit框架的jar包导入到项目中(注意:IDEA集成Junit框架,不需要我们自己手工导入了)
- 为需要测试的业务类,定义对应的测试类,并为每个业务方法,编写对应的测试方法(必须:公共、无参、无返回值)
- 测试方法上必须声明@Test注释,然后在测试方法中,编写代码调用被测试的业务方法进行测试;
- 开始测试:选中测试方法,右键选择“Junit运行”,如果测试通过则是绿色;如果测试失败,则是红色。
案例
字符串工具类:
/*
* 字符串工具类
* */
public class StringUtill {
public static void printNumber(String name){
if(name == null){
System.out.println("名字是null");
return;
}
System.out.println("名字的长度是:" + name.length());
}
// 求字符串的最大索引
public static int getMaxIndex(String data){
if(data == null){
return -1;
}
return data.length() - 1;
}
}
测试类:
// 测试类
public class StringUtillTest {
@Test
public void testPrintNumber(){
StringUtill.printNumber(null);
StringUtill.printNumber("");
StringUtill.printNumber("admin");
}
@Test
public void testGetMaxIndex(){
// System.out.println(StringUtill.getMaxIndex(null));
// System.out.println(StringUtill.getMaxIndex(""));
// System.out.println(StringUtill.getMaxIndex("admin"));
// 断言: 预期结果和实际结果是否一致
int i1 = StringUtill.getMaxIndex(null);
Assert.assertEquals("null测试失败", -1, i1);
int i2 = StringUtill.getMaxIndex("");
Assert.assertEquals("空字符串测试失败", -1, i2);
int i3 = StringUtill.getMaxIndex("admin");
Assert.assertEquals("字符串测试失败", 4, i3);
System.out.println("测试通过!");
}
}
Junit单元测试框架的常用注解(Junit 4.XXX版本)

- 在测试方法执行前执行的方法,常用于:初始化资源。
- 在测试方法执行完后再执行的方法,常用于:释放资源。
案例
// 测试类
public class StringUtillTest {
@Before // 修饰实例方法,每个测试方法前都执行一次
public void before(){
System.out.println("==============before ==============");
}
@After // 修饰实例方法,每个测试方法后都执行一次
public void after(){
System.out.println("==============after ==============");
}
@BeforeClass //修饰静态方法,全部测试方法前只执行一次
public static void beforeClass(){
System.out.println("==============beforeClass ==============");
}
@AfterClass //修饰静态方法,全部测试方法后只执行一次
public static void afterClass(){
System.out.println("==============afterClass ==============");
}
@Test
public void testPrintNumber(){
StringUtill.printNumber(null);
StringUtill.printNumber("");
StringUtill.printNumber("admin");
}
@Test
public void testGetMaxIndex(){
// System.out.println(StringUtill.getMaxIndex(null));
// System.out.println(StringUtill.getMaxIndex(""));
// System.out.println(StringUtill.getMaxIndex("admin"));
// 断言: 预期结果和实际结果是否一致
int i1 = StringUtill.getMaxIndex(null);
Assert.assertEquals("null测试失败", -1, i1);
int i2 = StringUtill.getMaxIndex("");
Assert.assertEquals("空字符串测试失败", -1, i2);
int i3 = StringUtill.getMaxIndex("admin");
Assert.assertEquals("字符串测试失败", 4, i3);
System.out.println("测试通过!");
}
}
Junit单元测试框架的常用注解(Junit 5.xxx版本)

- 开始执行的方法:初始化资源。
- 执行完之后的方法:释放资源。
案例
// 目标:获取class对象
public class Test1Class {
public static void main(String[] args) throws Exception {
//反射第一步,获取class对象
//1. 方式一:类名.class
Class c1 = Student.class;
System.out.println(c1);
//2. 方式二:Class.forName("全类名")
Class c2 = Class.forName("com.javabase.d2_reflect.Student");
System.out.println(c2);
//3. 方式三:对象.getClass()
Student s1 = new Student();
Class c3 = s1.getClass();
System.out.println(c1 == c2);
System.out.println(c2 == c3);
}
}
获取类的构造器并对其进行操作
- Class提供了从类中获取构造器的方法
![image]()
获取构造器的作用:依然是初始化对象返回

案例
猫咪类:
/**
* 猫咪类
*/
public class Cat {
public static int a;
public static final String COUNTRY="中国";
private String name;
private int age;
public Cat() {
}
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
private void run(){
System.out.println("🐱跑的贼快~~");
}
public void eat(){
System.out.println("🐱爱吃猫粮~~");
}
private String eat(String name){return "🐱最爱吃:" + name;}
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;
}
}
测试类:
//目标:掌握获取类的构造器,并对其进行操作
public class Test2Constructor {
@Test
public void testGetConstructors(){
//1. 反射第一步:必须先得到这个类的Class对象
Class c = Cat.class;
//2.获取类的全部构造器
// Constructor[] cs = c.getConstructors(); //只能拿public的构造器
Constructor[] cs = c.getDeclaredConstructors(); //可以拿全部的构造器
//3. 遍历构造器数组
for (Constructor constructor : cs) {
System.out.println(constructor.getName()+ "===>" + constructor.getParameterCount());
}
}
@Test
public void testGetConstructor() throws Exception {
//1. 反射第一步:必须先得到这个类的Class对象
Class c = Cat.class;
//2.获取类的指定构造器:无参构造器
Constructor c1 = c.getDeclaredConstructor();//定位无参的
Constructor c2 = c.getDeclaredConstructor(String.class, int.class); //定位有参的
//3. 得到构造器的目的依然是初始化对象返回
Cat cat1 = (Cat) c1.newInstance();
System.out.println(cat1);
c2.setAccessible(true);//静止检查访问权限(暴力反射)
Cat cat2 = (Cat) c2.newInstance("hello", 12);
System.out.println(cat2);
}
}
获取类的成员变量
- Class提供了从类中获取成员变量的方法
![image]()
获取成员变量的的作用:依然是赋值、取值。

案例
测试:
// 目标:掌握获取类的成员变量,并对其进行操作
public class Test3Field {
@Test
public void testGetFields() throws Exception {
//1. 反射第一步:必须是先得到类的Class对象
Class c = Cat.class;
//2.获取类的全部成员变量。
Field[] fields = c.getDeclaredFields();
//3. 遍历这个成员变量数组
for (Field field : fields) {
System.out.println(field.getType() + "===>" + field.getName());
}
//4. 定位某个成员变量
Field fname = c.getDeclaredField("name");
System.out.println(fname.getType() + "===>" + fname.getName());
//5. 成员变量的作用依然是:取值和赋值。
Cat cat = new Cat();
//5.1 暴力反射
fname.setAccessible(true);
fname.set(cat, "机器猫");
String name = (String) fname.get(cat);
System.out.println(name);
}
}
cat类复用上边提到的
获取类的成员方法
-
Class提供了从类中获取成员方法的API
![image]()
-
成员方法的作用:依然是执行
![image]()
案例
//目标:掌握获取类的成员方法,并对其进行操作
public class Test4Method {
@Test
public void testGetMethods() throws Exception {
//1. 反射第一步:先得到class
Class c = Cat.class;
//2. 获取类的全部方法
Method[] methods = c.getDeclaredMethods();
//3. 遍历这个方法数组
for (Method method : methods) {
System.out.println(method.getName()+ "===>" + method.getParameterCount());
}
//4. 定位某个方法
Method eat1 = c.getDeclaredMethod("eat");
Method eat2 = c.getDeclaredMethod("eat", String.class);
//5. 方法的作用依然是:执行它
Cat cat = new Cat();
Object res = eat1.invoke(cat);
System.out.println(res);
//5.1 暴力反射
eat2.setAccessible(true);
Object res1 = eat2.invoke(cat, "猫粮");
System.out.println(res1);
}
}
作用、应用场景
反射的作用?
- 基本作用:可以得到一个类的全部成分然后操作。
- 可以破坏封装性。
- 最重要的用途是:适合做Java的高级框架,基本上,主流的框架都会基于反射设计出一些通用的功能。
案例:使用反射做一个简易版的框架
需求:
- 对于任意一个对象,该框架都可以把对象的字段名和对应的值,保存到文件中去。
![image]()
实现步骤
- 定义一个方法,可以接收任意对象。
- 没收到一个对象后,使用发射获取该对象的class对象,然后获取全部的成员变量。
- 遍历成员变量然后提取成员变量在该对象中具体值。
- 把成员变量名和其值,写出到文件中去即可。
实现
学生类:
/**
* 学生类
*/
public class Student {
private String name;
private int age;
private char sex;
private double height;
private String hobby;
public Student() {
}
public Student(String name, int age, char sex, double height, String hobby) {
this.name = name;
this.age = age;
this.sex = sex;
this.height = height;
this.hobby = hobby;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSex(char sex) {
this.sex = sex;
}
public void setHeight(double height) {
this.height = height;
}
public void setHobby(String hobby) {
this.hobby = hobby;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public char getSex() {
return sex;
}
public double getHeight() {
return height;
}
public String getHobby() {
return hobby;
}
}
老师类:
public class Teacher {
private String name;
private double salary;
public Teacher() {
}
public Teacher(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
}
ObjectFrame类:
public class ObjectFrame {
// 目标:保存任意对象的字段和其票据到文件中去
public static void saveObject(Object obj) throws Exception {
// 但是这个对象中有多少个字段,我们不清楚,谁清楚?只有反射可以解决
// 创建打印流
PrintWriter pw = new PrintWriter(new FileWriter("day14-junit-reflect-annotation-proxy\\src\\obj.txt",
true));
// 获取字节码对象
Class c = obj.getClass();
// 0. 得知道是啥对象
// String classname = c.getName();// com.javabase.d2_reflect.Student
String classname = c.getSimpleName(); // Student
pw.println("==================" + classname + "===============");
//1. 提取这个class对象中的全部字段
Field[] fields = c.getDeclaredFields();
//2. 遍历fields数组,获取每个字段的名字和值
for (Field field : fields) {
String name = field.getName();
field.setAccessible(true);
String value = field.get(obj).toString();
pw.println(name + "=" + value);
}
pw.close();
}
}
测试:
//目标:使用发射技术:设计一个保存对象的简易版框架
public class Test5Frame {
public static void main(String[] args) throws Exception {
//1. 创建对象
Student s1 = new Student("吴彦祖", 45, '男', 175.3, "阅读,篮球,冰球");
Teacher t1 = new Teacher("张三", 999.9);
//2. 需求:把任意对象的字段名和其对应值等信息,保存到文件中去。
ObjectFrame.saveObject(s1);
ObjectFrame.saveObject(t1);
}
}
注解
概述、自定义注解
注解(Annotation)
- 就是Java代码里的特殊标记,比如:@Override、@Test等。
- 作用是:让其他程序根据注解信息来决定怎么执行该程序。
- 注意:注解可以用在类上、构造器上、方法上、成员变量上、参数上等位置处。
![image]()
自定义注解
- 就是自己定义注解。
格式:
public @interface 注解名称{
public 属性类型 属性名() default 默认值;
}
**特殊属性名:value**
- 如果注解中只有一个value属性,使用注解时,value名称可以不写。
## 注解的原理

- 注解本质是一个接口,Java中所有注解都是继承了Annotation接口的。
- @注解(...):其实就是一个匿名类对象也是实现类对象,实现了该注解以及Annotation接口。
**案例**
``` java
MyTest注解类:
//自定义注解
public @interface MyTest {
String name();
double money() default 100;
String[] authors();
}
MyTest1注解类:
public @interface Mytest2 {
String value() ;
}
测试:
//目标:掌握注解的使用
@MyTest(name = "从入门到跑路", money = 9.9, authors = {"阿猫", "阿狗"})
public class AnnotationDemo01 {
// @Mytest2(value = "开始")
@Mytest2("开始")
public static void main(String[] args) {
}
}
元注解
- 指的是:修饰注解的注解。
![image]()
![image]()
案例
MyTest3注解类:
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD,ElementType.CONSTRUCTOR}) //声明注解范围
@Retention(RetentionPolicy.RUNTIME) // 声明注解的保留周期
public @interface MyTest3 {
}
案例:
//目标:原注解
@MyTest3
public class AnnotationDemo02 {
@MyTest3
private String name;
@MyTest3
public AnnotationDemo02(){}
@MyTest3
public static void run(){}
@MyTest3
public static void main(String[] args) {
}
}
注解的解析
什么是注解的解析?
- 就是判断类上、方法上、成员变量上是否存在注解,并把注解里的内容解析出来。
如何解析注解
- 指导思想:要解析谁上面的注解,就应该先拿到谁。
- 比如要解析类上面的注解,则应该先获取类的Class对象,再通过Class对象解析其上面的注解。
- 比如要解析成员方法上的注解,则应该获取到该成员方法的Method对象,再通过Method对象解析其上的注解。
- Class、Method、Field,Constructor、都实现了AnnotatedElement接口,他们都拥有解析注解的能力。
![image]()
案例:解析注解的案例
解析注解的案例,具体需求如下:
- 定义注解MyTest4,要求如:
- 包含属性:String value()
- 包含属性:double aaa(),默认值为100
- 包含属性:String[] bbb()
- 限制注解使用的位置:类和成员方法上
- 指定注解的有效范围:一直到运行时
- 定义一个类叫:Demo,在类中定义一个test1方法,并在该类和其他方法上使用MyTest4注解
- 定义AnnotationTest3测试类,解析Demo类中的全部注解。
代码实现
Annotation类:
@Target({ElementType.TYPE, ElementType.METHOD,})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest4 {
String value();
double aaa() default 100;
String[] bbb();
}
测试类:
// 目标:注解的解析
public class AnnotationDemo03 {
@Test
// 测试方法:公开的,无参数的,无返回值的
public void parseClass(){
// 1. 获取class对象
Class c = Demo.class;
//2. 判断类上面是否陈列了该注解
if(c.isAnnotationPresent(MyTest4.class)){
// 3. 获取注解对象
Annotation myTest4 = c.getDeclaredAnnotation(MyTest4.class);
System.out.println(((MyTest4) myTest4).value());
System.out.println(((MyTest4) myTest4).aaa());
System.out.println(Arrays.toString(((MyTest4) myTest4).bbb()));
}
}
@Test
public void parseMethod() throws Exception {
// 1. 获取class对象
Class c = Demo.class;
Method method = c.getDeclaredMethod("test1");
//2. 判断方法上面是否陈列了该注解
if(method.isAnnotationPresent(MyTest4.class)){
// 3. 获取注解对象
Annotation myTest4 = method.getDeclaredAnnotation(MyTest4.class);
System.out.println(((MyTest4) myTest4).value());
System.out.println(((MyTest4) myTest4).aaa());
System.out.println(Arrays.toString(((MyTest4) myTest4).bbb()));
}
}
}
@MyTest4(value="我爱学习", aaa = 200, bbb = {"小明", "春明"})
class Demo{
@MyTest4(value = "我爱中国", aaa = 999, bbb = {"1", "2", "3"})
public void test1(){
}
}
输出结果:
我爱学习
200.0
[小明, 春明]
我爱中国
999.0
[1, 2, 3]
注解的应用场景
案例:模拟Junit框架
需求
- 定义若干个方法,只要加了MyTest注解,就会触发该方法执行。
分析
- 定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在。
- 定义若干个方法,部分方法加上@MyTest注解修饰,部分方法不加。
- 模拟一个junit程序,可以触发加了@MyTest注解的方法执行。
代码实现
annotation类:
@Target(ElementType.METHOD)// 注解只能注解方法
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {}
测试类:
// 目标:模拟Junit框架的设计。
public class AnnotationDemo04 {
// @MyTest
public void test1(){
System.out.println("test1");
}
@MyTest
public void test2(){
System.out.println("test2");
}
// @MyTest
public void test3(){
System.out.println("test3");
}
@MyTest
public void test4(){
System.out.println("test4");
}
public static void main(String[] args) throws Exception{
AnnotationDemo04 test = new AnnotationDemo04();
//启动程序!
//1. 得到Class对象
Class c = AnnotationDemo04.class;
//2. 得到所有的方法
Method[] methods = c.getDeclaredMethods();
//3. 遍历所有的方法,判断是否有@MyTest注解
for (Method method : methods) {
if(method.isAnnotationPresent(MyTest.class)){
//4. 说明当前方法上存在这个注解,触发当前方法执行。
method.invoke(test);
}
}
}
}
结果:
test2
test4
动态代理
程序为什么需要代理?代理长什么样?
- 对象如果嫌身上干的事太多的话,可以通过代理来转移部分职责。
- 对象有什么方法想被代理,代理就一定要对应的方法。
![image]()
中介如何知道要派有唱歌、跳舞方法的代理呢? - 是通过接口
如何为Java对象创建一个代理对象?
- java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法:
![image]()
代码实现
明星类:
public class BigStar implements Star{
private String name;
public BigStar(String name) {
this.name = name;
}
public String sing(String name){
System.out.println(this.name + "正在唱:"+ name);
return "谢谢!谢谢!";
}
public void dance(){
System.out.println(this.name + "正在优美的跳舞~");
}
}
代理接口类:
public interface Star {
String sing(String name);
void dance();
}
代理中介类:
public class ProxyUtil {
public static Star createProxy(BigStar bigstar){
/*
* ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h
*
* 参数1:用于指定一个类加载器
* 参数2:指定生成的代理长什么样子,也就是有哪些方法
* 参数3:用来指定生成的代理对象要干什么事情,也就是代理类中方法的具体实现
**/
Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
new Class[]{Star.class}, new InvocationHandler() {
@Override// 回调方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 代理对象要做的事情,会在这里写代码
if(method.getName().equals("sing")){
System.out.println("准备话筒,收钱20万元!");
}
else if(method.getName().equals("dance")){
System.out.println("准备场地,收钱50万元!");
}
return method.invoke(bigstar, args);
}
});
return starProxy;
}
}
测试类:
public class Test {
public static void main(String[] args) {
BigStar bigstar = new BigStar("杨超越");
Star starProxy = ProxyUtil.createProxy(bigstar);
String rs = starProxy.sing("好日子");
System.out.println(rs);
starProxy.dance();
}
}
输出结果:
准备话筒,收钱20万元!
杨超越正在唱:好日子
谢谢!谢谢!
准备场地,收钱50万元!
杨超越正在优美的跳舞~
解决实际问题、掌握使用代理的好处
案例:使用代理优化用户管理类
场景
- 某系统有一个用户管理类,包含用户登录,删除用户,查询用户登功能,系统要求统计每个功能的执行耗时情况,以便后期观察程序性能。
需求
- 现在,某个初级程序员已经开发好了该模块,请观察该模块的代码,找出目前存在的问题,并对其进行改造。
代码实现
接口类:
// 用户业务接口
public interface UserService {
//登录功能
void login(String loginName, String passWord) throws Exception;
// 删除用户
void deleteUsers() throws Exception;
// 查询用户,返回数组的形式。
String[] selectUsers() throws Exception;
}
接口实现类:
//用户业务实现类(面向接口编程)
public class UserServiceImpl implements UserService{
@Override
public void login(String loginName, String passWord) throws Exception {
if("admin".equals(loginName) && "123456".equals(passWord)){
System.out.println("登录成功, 欢迎光临本系统!");
}
else{
System.out.println("登录失败,用户名或密码错误~");
}
Thread.sleep(1000);
}
@Override
public void deleteUsers() throws Exception {
System.out.println("删除用户成功!");
Thread.sleep(1500);
}
@Override
public String[] selectUsers() throws Exception {
String[] names = new String[]{"admin", "jack", "rose"};
Thread.sleep(500);
return names;
}
}
代理类:
public class ProxyUtil {
public static UserService createProxy(UserService userService){
//创建代理对象
UserService userServiceProxy = (UserService)Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
new Class[]{UserService.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("login") || method.getName().equals("deleteUsers") || method.getName().equals("selectUsers")){
long starTime = System.currentTimeMillis();
Object rs = method.invoke(userService, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + "方法执行耗时:" + (endTime - starTime)/1000 + "s");
return rs;
}
else{
Object rs = method.invoke(userService, args);
return rs;
}
}
});
return userServiceProxy;
}
}
测试类:
public class Test {
public static void main(String[] args) throws Exception {
//1. 创建用户业务对象
UserService userService = new UserServiceImpl();
//2. 调用用户业务的功能
userService.login("admin", "123456");
System.out.println("-----------------------------------");
userService.deleteUsers();
System.out.println("-----------------------------------");
String[] names = userService.selectUsers();
System.out.println("查询到的用户有:" + Arrays.toString(names));
System.out.println("-----------------------------------");
}
}
输出结果:
登录成功, 欢迎光临本系统!
-----------------------------------
删除用户成功!
-----------------------------------
查询到的用户有:[admin, jack, rose]
-----------------------------------













浙公网安备 33010602011771号