29Java基础之高级技术

单元测试

  • 单元测试:就是针对最小的功能单元(方法),编写测试代码对其进行正确性测试。

之前是如何进行单元测试的?有什么问题?

  • 之前只能在main方法编写测试代码,去调用其他方法进行测试。
  • 无法实现自动化测试,一个方法测试失败,可能影响其他方法的测试。
  • 无法得到测试的报告,需要程序员自己去观察测试是否成功。

Junit单元测试框架

  • 可以用来对方法进行测试,它是第三方公司开源出来的(很多开发工具已经集成了Junit框架,比如IDEA)

优点

  • 可以灵活的编写测试代码,可以针对某个方法执行测试,也支持一键完成对全部方法的自动化测试,且各自独立。
  • 不需要程序员去分析测试的结果,会自动生成测试报告出来。
    image

Junit单元测试——快速入门

需求

  • 某个系统,有多个业务方法,请使用Junit单元测试框架,编写测试代码,完成对这些方法的正确性测试。

具体步骤

  1. 将Junit框架的jar包导入到项目中(注意:IDEA集成Junit框架,不需要我们自己手工导入了)
  2. 为需要测试的业务类,定义对应的测试类,并为每个业务方法,编写对应的测试方法(必须:公共、无参、无返回值)
  3. 测试方法上必须声明@Test注释,然后在测试方法中,编写代码调用被测试的业务方法进行测试;
  4. 开始测试:选中测试方法,右键选择“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版本)
image

  • 在测试方法执行前执行的方法,常用于:初始化资源。
  • 在测试方法执行完后再执行的方法,常用于:释放资源。

案例

// 测试类
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版本)
image

  • 开始执行的方法:初始化资源。
  • 执行完之后的方法:释放资源。

案例

// 目标:获取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

获取构造器的作用:依然是初始化对象返回
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

获取成员变量的的作用:依然是赋值、取值。
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

实现步骤

  1. 定义一个方法,可以接收任意对象。
  2. 没收到一个对象后,使用发射获取该对象的class对象,然后获取全部的成员变量。
  3. 遍历成员变量然后提取成员变量在该对象中具体值。
  4. 把成员变量名和其值,写出到文件中去即可。

实现

学生类:
/**
 * 学生类
 */
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名称可以不写。

## 注解的原理
![image](https://img2024.cnblogs.com/blog/3656114/202511/3656114-20251119090959230-103192132.png)
- 注解本质是一个接口,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

案例:解析注解的案例
解析注解的案例,具体需求如下:

  1. 定义注解MyTest4,要求如:
  • 包含属性:String value()
  • 包含属性:double aaa(),默认值为100
  • 包含属性:String[] bbb()
  • 限制注解使用的位置:类和成员方法上
  • 指定注解的有效范围:一直到运行时
  1. 定义一个类叫:Demo,在类中定义一个test1方法,并在该类和其他方法上使用MyTest4注解
  2. 定义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注解,就会触发该方法执行。
    分析
  1. 定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在。
  2. 定义若干个方法,部分方法加上@MyTest注解修饰,部分方法不加。
  3. 模拟一个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]
-----------------------------------
posted @ 2025-12-11 10:21  狂风将军  阅读(2)  评论(0)    收藏  举报