【杂谈】Unchecked cast: ‘java.lang.Object‘ to ‘T‘ 与单元测试

一些杂乱的小问题记录

1. Unchecked cast: ‘java.lang.Object’ to ‘T’

问题描述
有时,我们要自定义一些工具类、工具方法或数据结构,此时需要用到泛型。

我们指导泛型既可以指定类型,也可以作为通配符使用。有时,当我们使用通配符进行类型转换时,IDE会报:

Unchecked cast: 'java.lang.Object' to 'T' 

翻译过来就是:未选中强制转换:‘java.lang.Object’到’T’ 。
这只是个IDE的警告,一般情况下并不影响程序运行,但如果放到实际工作中,这个代码总是报黄,总是不舒服,也容易让人产生代码不规范的感觉。
i1

参考文章
知乎优秀参考文章

解决方案

  • 忽略:添加@SuppressWarnings(“unchecked”)注解。如果直接在单个方法添加这个注解,代表每次只是忽略。根据参考文章的说法,这种单次忽略治标不治本。如果我们可以确定要返回的类型,如List等,则可参考参考文章的做法。
	@Override
    @SuppressWarnings("unchecked")
    public T get(int i) {
        if(i < INIT_LENGTH || i > length-1){
            throw new ArrayIndexOutOfBoundsException();
        }
        return (T) getElements()[i];
    }
  • 使用泛型方法进行类型转换:参考文章提供了List/Map的类型转换。本文借助 Class.cast() 方法进行稍微安全一些的转换,但由于泛型的缘故,工具类依然需要添加上面的忽略注解
	@SuppressWarnings("unchecked")
    public static <T> T typeConversion(Object o, Class<?> tClass){
        return (T) tClass.cast(o);
    }
@Override
    public T get(int i) {
        if(i < INIT_LENGTH || i > length-1){
            throw new ArrayIndexOutOfBoundsException();
        }
        return GenericTypeChangeUtil.typeConversion(getElements()[i], getElements()[i].getClass());
}

2. 单元测试

问题描述
既然在上一小节我们做了一个类型转换方法,该方法又是个人在学习数据结构过程中,自己写的一个顺序表。那么总得测试一下这个顺序表是否可用。

本例我们就用 Mockito + Junit 进行单元测试吧

参考教程
bilibili-Mockito优秀教程

测试
顺序表源码:

import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * 顺序表的实现
 * @author Sharry
 * @since 2023/9/16
 */
public class SequenceTable<T> implements ISequenceTable<T> {

    /**
     * init length : 0
     */
    public final int INIT_LENGTH = 0;

    /**
     * hash code constant
     */
    public final int HASH_TIMES = 31;

    /**
     * array to store sequence data
     */
    private Object [] elements;

    /**
     * this sequence table's length
     */
    private int length;

    public SequenceTable(T[] elements) {
        this.elements = elements;
        this.length = elements.length;
    }

    public SequenceTable(int length) {
        this.elements = new Object[length];
        this.length = length;
    }

    public SequenceTable() {
        this.elements = new Object[INIT_LENGTH];
        this.length = INIT_LENGTH;
    }

    public SequenceTable(T[] elements, int length) {
        if(length < elements.length){
            throw new ArrayIndexOutOfBoundsException();
        }
        this.elements = elements;
        this.length = length;
    }

    public Object[] getElements() {
        return elements;
    }

    public void setElements(Object[] elements) {
        this.elements = elements;
    }

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof SequenceTable)) {
            return false;
        }
        SequenceTable<?> that = (SequenceTable<?>) o;
        return getLength() == that.getLength() && Arrays.equals(getElements(), that.getElements());
    }

    @Override
    public int hashCode() {
        int result = Objects.hash(getLength());
        result =HASH_TIMES * result + Arrays.hashCode(getElements());
        return result;
    }

    @Override
    public String toString() {
        return "SequenceTable{" +
                "elements=" + Arrays.toString(elements) +
                ", length=" + length +
                '}';
    }

    @Override
    public boolean isEmpty() {
        if (elements.length == 0){
            return this.getLength() == INIT_LENGTH || this.getElements() == null;
        }
        for (Object element : elements) {
            if (null != element) {
                return false;
            }
        }
        return true;
    }

    @Override
    public int size() {
        return getLength();
    }

    @Override
    public T get(int i) {
        if(i < INIT_LENGTH || i > length-1){
            throw new ArrayIndexOutOfBoundsException();
        }
        return GenericTypeChangeUtil.typeConversion(getElements()[i], getElements()[i].getClass());
    }

    @Override
    public boolean set(int i, T x) {
        if(isOutOfBounds(i)){
            throw new ArrayIndexOutOfBoundsException();
        }
        Object[] eArr = getElements();
        Object e = getElements()[i];
        if ( e == null || !e.equals(x)) {
            eArr[i] = x;
            setElements(eArr);
            return true;
        }
        return false;
    }

    @Override
    public int insert(int i, T x) {
        if(isOutOfBounds(i)){
            throw new ArrayIndexOutOfBoundsException();
        }
        if(set(i,x)){
            return i;
        }
        return -1;
    }

    @Override
    public int insert(T x) {
        if(set(length-1,x)){
            return length-1;
        }
        return -1;
    }

    @Override
    public T remove(int i) {
        if(isOutOfBounds(i)){
            throw new ArrayIndexOutOfBoundsException();
        }
        Object e = getElements()[i];
        Object[] eArr = getElements();
        eArr[i] = null;
        setElements(eArr);
        return GenericTypeChangeUtil.typeConversion(e,e.getClass());
    }

    @Override
    public int search(T key) {
        Object[] eArr = getElements();
        for (int i = 0 ; i < eArr.length ; i++){
            boolean ifNull = eArr[i] != null && eArr[i] == key;
            if(ifNull || Objects.equals(eArr[i], key)){
                return i;
            }
        }
        return -1;
    }

    @Override
    public boolean contains(T key) {
        return search(key) != -1;
    }

    @Override
    public int insertDifferent(T x) {
        if(contains(x)){
            return -1;
        }
        insert(x);
        return length-1;
    }

    @Override
    public T remove(T key) {
        T element;
        if (!contains(key)) {
            return null;
        } else {
            element = GenericTypeChangeUtil.typeConversion(getElements()[search(key)],getElements()[search(key)].getClass());
            set(search(key), null);
        }
        return GenericTypeChangeUtil.typeConversion(element,element.getClass());
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean addAll(List<T> list) {
        if (list == null || list.size() == 0){
            return false;
        }
        Object[] source = getElements();
        Object [] tar = new Object[this.getLength() + list.size()];
        int tarLength = tar.length;
        if (this.length >= 0) {
            System.arraycopy(source, 0, tar, 0, this.length);
        }
        for(int i = this.length-1; i < tarLength; i++){
            tar[i] = list.get(tarLength-i);
        }
        SequenceTable<T> ns = new SequenceTable<T>((T[])tar,tarLength);
        setElements(ns.getElements());
        setLength(ns.length);
        return true;
    }

    private boolean isOutOfBounds(int i){
        return i < INIT_LENGTH || i > length - 1;
    }
}

工具类源码:

/**
 * Generic type change util
 *
 * @author Sharry
 * @since 2023/9/21
 */
public class GenericTypeChangeUtil {
    @SuppressWarnings("unchecked")
    public static <T> T typeConversion(Object o, Class<?> tClass){
        return (T) tClass.cast(o);
    }
}

分别对顺序表、工具类进行Mocktio + Junit 5进行测试:

依赖:

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.8.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
            <version>4.3.1</version>
            <scope>test</scope>
        </dependency>

顺序表测试:

	@Test
    void getElements() {
        Mockito.when(sequenceTable.getElements()).thenCallRealMethod();
        log.info("Forward value:{}", Arrays.toString(sequenceTable.getElements()));
        Object[] elements = sequenceTable.getElements();
        Assertions.assertEquals(elements,sequenceTable.getElements());
    }
// 其余测试代码略.....

工具类测试:

@Test
    void typeConversion() {
        try(MockedStatic<GenericTypeChangeUtilTest> testUtil = Mockito.mockStatic(GenericTypeChangeUtilTest.class)){
            Assertions.assertEquals(1, (Integer) GenericTypeChangeUtil.typeConversion(1,Integer.class));
        }
    }

由于日常工作时测试是个大佬,因此我们普通马仔在日常开发中若是交期紧,就会在开发完后Swagger文档稍微测试一下就给测试了,没有顾及太多单元测试,上面的测试也只是照着参考教程写的。趁此机会就补补单元测试的概念吧:

为什么要Mockito + Junit

  • Junit : 最常见的Java单元测试工具之一。一般情况下,Junit已集成在一些工具或框架中。如IDEA就自带了Junit,可以用IDEA的Generate生成对应测试类。
  • Mockito:使用Mock对象的方式进行测试。使用Mock可以让我们在相对独立的测试环境,使用专用的对象进行模拟,可以进一步规范对象的动作,捕捉对象的行为。简单来说就是更独立,不用去动实际要动的对象。

常见概念

  • 断言:assert。assert具体在代码的体现就是Junit提供的方法了,其作用为:当断言里的结果是我们预期的结果时,测试通过;否则IDE会报红。具体assert的用法会根据Junit的版本不同而有差异,使用示例在上面的代码中。

  • 测试类常用注解:

    • @BeforeEach : 准备工作。在上述例子中的体现就是对Mock进行初始化。
    • @AfterEach : 结束工作。
  • Mokito常用注解与方法

    • @Spy : 使用真实的对象(有些对象难以Mock)
    • @Mock : 模拟对象
    • MockitoAnnotations.openMocks(class) 配合初始化注解使用,建议放在@BefoeEach方法上
    • Mockito.when().thenCallRealMethod() 方法,使用执行该方法的返回值,而不是自己定义的thenReturn值
    • Mokito.when().thenThrow()方法 ,抛出异常
    • @InjectMocks :将@Mock的对象注入到该注解修饰的对象中
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值