0
点赞
收藏
分享

微信扫一扫

使用 Lombok @Singular 注解需要注意的 BUG


先看下面一个 java 类, 这是在业务代码中的一段真实代码,做了部分简化。

@Getter
@Builder
@NoArgsConstructor
public class PatientQuery {
@Singular
private List<Long> patientIds = Lists.newArrayList();
}

首先我们先分析下上面这段代码存在的问题:

**第一:**添加了 Lombok 的 @Builder 注解,但是 patientIds 属性有默认值,却没有添加 ​​@Builder.Default​​ 注解,这样使用 builder 模式构建这个对象时会导致 属性 patientIds 是个 null。

第二: 当我们添加了 ​​@Builder.Default​​​注解后,编译器会报错, ​​@Builder.Default​​​和​​@Singular​​ 不能一块使用。

所以上面的代码, 如果想用 ​​@Singluar​​ 注解要写成下面这样才对:

@Getter
@Builder
@NoArgsConstructor
public class PatientQuery {
@Singular
private List<Long> patientIds;
}

如果想用 ​​@Builder.Default​​注解给 patientIds 属性添加默认值,要写成下面这样:

@Getter
@Builder
@NoArgsConstructor
public class PatientQuery {
@Builder.Default
private List<Long> patientIds = Lists.newArrayList();
}

当我们使用 ​​@Singular​​的时候,可能还会犯下面的错误:

public static void main(String[] args) {

PatientQuery query = PatientQuery.builder().build();

query.getPatientIds().add(1L)

}

大家可以先思考一下,上面的代码会出现什么错误。

上面的代码运行时会报这样的异常:

java.lang.UnsupportedOperationException: null
at java.base/java.util.AbstractList.add(AbstractList.java:153)

异常信息指示我们,​​List#add()​​​是个不支持的操作,查看 ​​AbstractList.add()​​方法源码:

public void add(int index, E element) {
throw new UnsupportedOperationException();
}

默认不支持 add 操作,需要子类去实现。现在我们可以大胆推测, ​​@Singular​​ 注解为我们生成的 AbstractList实现类,并没有重写 add 方法。

为了验证我们的想法,反编译 Lombok 为我们编译后的 class 文件,进行查看,以下是简化后的代码:

protected PatientQuery(final PatientQuery.PatientQueryBuilder<?, ?> b) {
List patientIds;
switch(b.patientIds == null ? 0 : b.patientIds.size()) {
case 0:
patientIds = Collections.emptyList();
break;
case 1:
patientIds = Collections.singletonList((Long)b.patientIds.get(0));
break;
default:
patientIds = Collections.unmodifiableList(new ArrayList(b.patientIds));
}

this.patientIds = patientIds;
}

可以看到啊,​​@Singular​​做了一些条件判断为我们实例化 patientIds 属性,

  • 当 patientIds==null,使用 Collections.emptyList(); 初始化
  • 当 patientIds.size()==1,使用 Collections.singletonList((Long)b.patientIds.get(0)); 初始化
  • 当 patientIds.size() > 1,使用 Collections.unmodifiableList(new ArrayList(b.patientIds)); 初始化

但是不论哪种方式,最终使用 Collections 生成的 List 都是个不可变的 List,以Collections.emptyList(); 为例,会返回一个内部类的实例 EmptyList:

private static class EmptyList<E>
extends AbstractList<E>
implements RandomAccess, Serializable {
private static final long serialVersionUID = 8842843931221139166L;

public Iterator<E> iterator() {
return emptyIterator();
}
public ListIterator<E> listIterator() {
return emptyListIterator();
}

public int size() {return 0;}
public boolean isEmpty() {return true;}
public void clear() {}

public boolean contains(Object obj) {return false;}
public boolean containsAll(Collection<?> c) { return c.isEmpty(); }

public Object[] toArray() { return new Object[0]; }

public <T> T[] toArray(T[] a) {
if (a.length > 0)
a[0] = null;
return a;
}

public E get(int index) {
throw new IndexOutOfBoundsException("Index: "+index);
}

public boolean equals(Object o) {
return (o instanceof List) && ((List<?>)o).isEmpty();
}

public int hashCode() { return 1; }

@Override
public boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
return false;
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
}
@Override
public void sort(Comparator<? super E> c) {
}

// Override default methods in Collection
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
}

@Override
public Spliterator<E> spliterator() { return Spliterators.emptySpliterator(); }

// Preserves singleton property
private Object readResolve() {
return EMPTY_LIST;
}
}

EmptyList 内部类继承了 AbstractList 抽象类,但并没有重写 add() 方法, 导致我们 ​​query.getPatientIds().add(1L);​​进行 add 操作时报 UnsupportedOperationException 异常。

到此,关于 ​​@Singular​​注解使用时的问题都已经分析完了,大家在使用时一定谨慎小心,稍不注意就会导致 BUG。


举报

相关推荐

0 条评论