使用 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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值