先看下面一个 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。