java高级技术

java高级技术

1.0 junit单元测试

junit是一个可以快捷对函数进行测试的第三方的包

我们如果写了一个方法,想要测试一下是否有bug,如果选择在main方法中主动去调用测试,但这种测试方

法面临着很多问题,比如:

  1. 需要手动查看控制台输出,人工判断结果是否正确

  2. 多个测试场景混在一起,难以定位具体哪个场景失败

  3. 无法自动化验证,每次都要人工检查

  4. 异常情况下程序可能直接终止,后续测试无法执行

此时我们就可以使用junit来进行自动化测试,其优点有:

  1. 自动断言验证,测试失败会明确报告期望值和实际值

  2. 每个 @Test 方法独立运行,失败不影响其他测试

  3. IDE 提供可视化测试报告,清晰显示通过/失败状态

  4. 可批量运行所有测试,无需逐个手动执行

下面来看一个例子

首先我们来写一个工具类:

package com.String.test;

public class StringUtil{
    public static void printString(String name){
        if(name==null) {
            System.out.println("不能为空");
        }
        System.out.println(name);
    }
    public  static int getIndexMax(String data){
        if(data==null) {
            return -1;
        }
        else {
            return data.length();
        }
    }


}

再准备一个测试类对这个工具类的方法进行测试:

我们使用注解,也就是下面的@Test对测试方法进行修饰

package com.String.test;

import org.junit.Test;

public class StringTest {
    @Test
    public void testPrintNumber(){
        StringUtil.printString("");
        StringUtil.printString(null );
        StringUtil.printString("admin");
    }
    @Test
    public void testMaxIndex(){
        System.out.println(StringUtil.getIndexMax("null"));
        System.out.println(StringUtil.getIndexMax(""));
        System.out.println(StringUtil.getIndexMax("1"));
    }
}

我们此时运行测试类:

image-20250805233231925

就可以自动化完成测试,如果我们输入的测试样例会出错,此时就会提醒错误,此时一次完成了多个方法的

测试,但是观察我们的第二个方法,发现这是一个寻找字符串最大索引的方法,查看我们的输出结果:

image-20250805233353403

发现输出结果似乎不正确,因为如果单单像上面这样写,在进行测试的时候只能反应报错的方法,当我们的

返回值与期待值不符合时,不会显示错误,想要解决这个问题,需要使用到断言:

具体的用法为:

package com.String.test;

import org.junit.Assert;
import org.junit.Test;

public class StringTest {
    @Test
    public void testPrintNumber(){
        StringUtil.printString("");
        StringUtil.printString(null );
        StringUtil.printString("admin");
    }
    @Test
    public void testMaxIndex(){
        int out1=StringUtil.getIndexMax(null);
        Assert.assertEquals("与预期不符合",-1,out1);
        int out2=StringUtil.getIndexMax("admin");
        Assert.assertEquals("与预期不符",4,out2);
    }
}

其中Assert.assertEquals("与预期不符合",-1,out1); 传入的第一个参数是当预测值与实际值不符合时

的报错信息,第二个是预测值,第三个参数是传入的实际值

通过断言,如果有人更改我们的方法,因为返回的值与原来不符,也可以检测出来

需要注意的是,单元测试的方法需要是公开的,无参数和返回值的

1.1 junit4 常用注解

image-20250805235019906

这几个注解的使用很清楚

先看一下Before和After

package com.String.test;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class StringTest {
    @Before
    public void printBefor(){
        System.out.println("before执行了一次");
    }
    @After
    public void printAfter(){
        System.out.println("after执行了一次");
    }
    @Test
    public void testPrintNumber(){
        StringUtil.printString("");
        StringUtil.printString(null );
        StringUtil.printString("admin");
    }
    @Test
    public void testMaxIndex(){
        int out1=StringUtil.getIndexMax(null);
        Assert.assertEquals("与预期不符合",-1,out1);
        int out2=StringUtil.getIndexMax("admin");
        Assert.assertEquals("与预期不符",4,out2);
    }
}

此时的输出为

before执行了一次

不能为空
null
admin
after执行了一次
before执行了一次
after执行了一次

可以看到在执行每一个测试方法之前时,都运行了Before修饰的方法,在执行每一个测试方法之后,都执行

了After修饰的方法

一般Before用来初始化资源After用来释放资源

需要注,JUnit4 中的 @Before/@After 只会在它所在的测试类里对每个 @Test 方法生效,不会作用于其他

测试类。

如果先要实现对多个测试类生效,可以讲Before和After修饰的方法写到一个抽象类中,然后让测试类继承

这个抽象类即可

AfterClass 和BeforeClass 有一点需要注意,那就是只能修饰静态方法

适合对通用资源的初始化和释放

对于junit5 来说,这几个注解的作用和使用方法没变,不过注解名发生了改变,需要注意一下

1.2 反射

反射就是加载类,将类作为一个对象加载到内存中,然后获取其中的方法和属性

反射的第一步是获得Class对象,获得Class对象的方法有三种,下面一一展示

package com.String.test;

public class TestClass {
    public static void main(String[] args) throws Exception{
        Class c1=Student.class;
        System.out.println(c1);
        Class c2 =Class.forName("com.String.test.Student");
        System.out.println(c2);
        Student student=new Student(11,"bob");
        Class c3=student.getClass();
        System.out.println(c3);
        if(c1==c2&&c1==c3&&c2==c1){
            System.out.println("同一个对象");
        }
    }
}

接下来是获取构造器对象:

image-20250806112747189

package com.String.test;

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.util.concurrent.Callable;

public class TestConstructer {
    @Test
    public void testConstructer()throws Exception{
        Class c=Student.class;
        Constructor [] constructors=c.getDeclaredConstructors();//获取全部构造器
        for (Constructor constructor : constructors) {
            System.out.println(constructor+":"+constructor.getParameterCount());
        }
        Constructor constructor1=c.getDeclaredConstructor();//获取无参构造器
        Constructor constructor2=c.getDeclaredConstructor(int.class,String.class);//根据参数获取构造器
        System.out.println(constructor1+":"+constructor1.getParameterCount());
        System.out.println(constructor2+":"+constructor2.getParameterCount());

    }
}

获取类构造器的作用依然是初始化对象返回

image-20250806121322555

我们来尝试一下通过构造器对象创建学生类

package com.String.test;

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.util.concurrent.Callable;

public class TestConstructer {
    @Test
    public void testConstructer()throws Exception{
        Class c=Student.class;
        Constructor constructor1=c.getDeclaredConstructor();//获取无参构造器
        Student student1=(Student) constructor1.newInstance();
        System.out.println(student1);
        Constructor constructor2=c.getDeclaredConstructor(int.class,String.class);
        Student student2=(Student) constructor2.newInstance(11,"bob");
    }
}

发现对于无参构造器,我们可以直接获得其产生的对象,但对于有参构造器,产生报错

为什么会这样呢,因为我们的有参构造器是一个私有的构造器,所以无法直接调用构造器进行对象创建,那

我们应该怎么解决呢?

这时候就需要使用暴力反射,也就是禁止对该构造器的访问权限进行检测

通过constructor2.setAccessible(true);实现,加上去之后为:

package com.String.test;

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.util.concurrent.Callable;

public class TestConstructer {
    @Test
    public void testConstructer()throws Exception{
        Class c=Student.class;
        Constructor constructor1=c.getDeclaredConstructor();//获取无参构造器
        Student student1=(Student) constructor1.newInstance();
        System.out.println(student1);
        Constructor constructor2=c.getDeclaredConstructor(int.class,String.class);
        constructor2.setAccessible(true);
        Student student2=(Student) constructor2.newInstance(11,"bob");
    }
}

此时再运行就不会报错了

接下来我们来学习获取成员变量,常用的api如下:

image-20250806124522336

package com.String.test;

import org.junit.Test;

import java.lang.reflect.Field;

public class TestField {
    @Test
    public void testFiled()throws Exception{
        Class c=Student.class;
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field.getType()+field.getName());
        }
        Field field1=c.getDeclaredField("age");//获得成员变量
        System.out.println(field1.getName());
        Student student=new Student();
        field1.setAccessible(true);
        field1.set(student,111);//给当前成员变量对象赋值,并将其传到对象中
        int age=(Integer) field1.get(student);//获得先前赋的值
        System.out.println(age);

    }
}

获取成员方法对象和上面没什么区别

package com.String.test;

import org.junit.Test;

import java.lang.reflect.Method;

public class TestMethod {
    @Test
    public void testMethod()throws Exception{
        Class c=Student.class;
        Method[] methods = c.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method.getName());
        }
        Method method1=c.getDeclaredMethod("print", int.class);
        //获取单个方法,由于存在方法重载,所以要指明方法名和参数,如果不加参数说明指定的就是无参方法
        Student student=new Student();
        method1.setAccessible(true);//如果我们要调用无参方法,则需要关闭访问权限检查
        Object result=method1.invoke(student,111);//由于方法是基于对象的,所以调用方法要指明对象和参数

    }
}

1.3 反射的作用

反射可以得到一个类的全部成分然后操作,可以破坏封装性,基本的主流框架都会用到反射

下面是一个简单例子:获得给定对象的所有字段名

package com.String.test;

import java.io.PrintWriter;
import java.lang.reflect.Field;

public class ObjectFrame {
    public static void saveObject(Object o) throws Exception{
        Class c=o.getClass();
        Field [] fields=c.getDeclaredFields();
        PrintWriter out=new PrintWriter("./data.txt");
        for (Field field : fields) {
            System.out.println(field.getName());
            field.setAccessible(true);
            out.write(field.getName()+":"+field.get(o)+"\r\n");
        }
        out.close();
    }
}

这只是一个简单样例,实际使用方式还有很多

1.4 注解

像我们之前使用的@Test和@Override 都是注解,用于对程序进行标记,让虚拟机进行特殊处理

首先来学习怎么自定义注解

我们先定义一个自定义注解:

package com.String.test;

public @interface Mytest {
    String value();
    int age() default 21;
}

上面每一个属性后面都需要加括号,还可以给默认值

再去使用

package com.String.test;
@Mytest("bob")
public class AnotationTest {
    @Mytest(value = "bob",age = 11)
    public static void main(String[] args) {

    }
}

类,方法,属性前都可以加我们的自定义注解,需要注意的是,如果自定义注解的值只有一个且名字为

value,那我们再使用注解时可以省略调属性名和等于号,只写值就可以了

注解本身就是一个接口,定义的属性都是方法,定义的值都是方法的返回值,我们在@使用注解时就是在定

义一个实现类对象

1.5 元注解

我们使用元注解对注解进行注解,常用的元注解有两个,我们先看第一个:

image-20250806205928625

对于我们先前写的注解,如果使用元注解:

package com.String.test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
public @interface Mytest {
    String value();
    int age() default 21;
}

此时我们的自定义注解就只能注解方法

如果我们想要注解多个内容,可以传递多个参数

package com.String.test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ElementType.METHOD,ElementType.TYPE})
public @interface Mytest {
    String value();
    int age() default 21;
}

像这样也是可以的

我们再来看第二个元注解:

image-20250806210201290

该元注解声明了注解的生命周期,当我们选择第一个参数SOURCE时,我们在源码阶段,也就是直接写代码

的代码文件中是存在的,但当我们对其进行导出成字节码文件,比如Jar包时,就不存在了,第二个选项保留

到字节码中,但运行时加载到内存中就不存在了,最后一个选项将我们的自定义注解永久保留

package com.String.test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface Mytest {
    String value();
    int age() default 21;
}

我们一般只使用RetentionPolicy.RUNTIME,期望我们的注解一直存活

1.6 注解解析

注解解析就是判断类上,方法上,成员变量上是否存在注解,并将其解析出来

下面是解析注解的方法:

下面是使用方式:

package com.String.test;

import org.junit.Test;

import java.lang.reflect.Method;

public class AnotationTest{
    @Test
    public void TestAno() throws Exception{
        Class c= AnoTest.class;
        if(c.isAnnotationPresent(Mytest.class)){//判断该类中是否存在注解
            Mytest mytest=(Mytest) c.getDeclaredAnnotation(Mytest.class);
            System.out.println(mytest.age());
        }
        Method method=c.getDeclaredMethod("print");
        if(method.isAnnotationPresent(Mytest.class)){
            Mytest mytest=method.getDeclaredAnnotation(Mytest.class);
            System.out.println(String.join(",", mytest.card()));
        }
    }
}

@Mytest(value = "bob",card = {"1","2"})
class AnoTest {
    @Mytest(value = "bob",card = {"1","2"})
    public static void print() {
        System.out.println(111);
    }
}

1.7 动态代理

动态代理就是用代理类去执行全部功能,核心类只执行核心功能

package com.proxy.test;

public interface Star {
    String sing(String name);
    String dance(String name);
}

package com.proxy.test;

public class Bigstar implements Star{
    private String name;
    public Bigstar(String name){
        this.name=name;
    }
    @Override
    public String sing(String name){
        System.out.println("sing"+name);
        return "Sing Finish";
    }
    @Override
    public String dance(String name){
        System.out.println("danging"+name);
        return "Dance Finish";
    }
}

package com.proxy.test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class CreatProxy {
    public static Star creatProxy(Bigstar bigstar){
        Star starproxy=(Star) Proxy.newProxyInstance(CreatProxy.class.getClassLoader(),
                new Class[]{Star.class},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if (method.getName() == "sing") {
                            System.out.println("准备唱歌");
                        }
                        else if(method.getName()=="dance"){
                            System.out.println("准备跳舞");
                        }
                        return method.invoke(bigstar,args);
                    }
                }
        );
        return starproxy;
    }
}

package com.proxy.test;

public class ProxyTest {
    public static void main(String[] args) {
        Bigstar yui=new Bigstar("yui");
        Star proxy=CreatProxy.creatProxy(yui);
        String rs;
        rs=proxy.sing("a song");
        System.out.println(rs);
        rs=proxy.dance("dancing");
        System.out.println(rs);
    }
}

这就完成了整个使用代理加测试的全部流程

posted @ 2025-08-06 00:28  折翼的小鸟先生  阅读(18)  评论(0)    收藏  举报