一些杂乱的小问题记录
1. Unchecked cast: ‘java.lang.Object’ to ‘T’
问题描述
有时,我们要自定义一些工具类、工具方法或数据结构,此时需要用到泛型。
我们指导泛型既可以指定类型,也可以作为通配符使用。有时,当我们使用通配符进行类型转换时,IDE会报:
Unchecked cast: 'java.lang.Object' to 'T'
翻译过来就是:未选中强制转换:‘java.lang.Object’到’T’ 。
这只是个IDE的警告,一般情况下并不影响程序运行,但如果放到实际工作中,这个代码总是报黄,总是不舒服,也容易让人产生代码不规范的感觉。
参考文章
知乎优秀参考文章
解决方案
- 忽略:添加@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的对象注入到该注解修饰的对象中