震惊 Guava 竟然有"坑"

点击上方 "后端架构师"关注, 星标或置顶一起成长

后台回复“大礼包”有惊喜礼包!

关注订阅号「后端架构师」,收看更多精彩内容

每日英文

Actually being alone is not lonely.The real loneliness is when you miss someone.

其实一个人并不孤单,想念一个人的时候才是真正的孤单。

每日掏心

你给了生活意境,那么生活才能给你风景。你风声鹤唳,生活也就只好四面楚歌。

来自:Java布道 | 责编:乐乐

后端架构师(ID:study_tech)第 1053 次推文 图 / 图虫

往日回顾:如何优雅地给妹子优化电脑(Windows)?

     

   正文   

最近,团队里边一个兄弟突然叫我:快来看,有个奇怪的事情,无法解释…跑过去一看,是这么一段代码:

 private static class Person {
        private int age;
        private String name;

        public Person(int age, String name) {
            this.age = age;
            this.name = name;
        }

        public void setAge(int age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return name + ":" + age;
        }
    }

    @Test
    public void test_collections2_filter() {
        Person lxy = new Person(35, "lxy");
        Person xhf = new Person(34, "xhf");
        Person nws = new Person(31, "nws");
        List<Person> names = Lists.newArrayList(lxy, xhf, nws);

        Collection<Person> personAgeOver30 = Collections2.filter(names, p -> p.age > 30);
        System.out.println(personAgeOver30);//[lxy:35, xhf:34, nws:31]

        nws.setAge(25);
        System.out.println(personAgeOver30);//[lxy:35, xhf:34]
    }

确实是比较奇怪,personAgeGt30中的元素怎么会少了一个呢?本着任何表现奇怪的程序都有其背后原因的指导思想,打开了Guava Collections2类的源代码。其实,源代码的注释已经解释得非常清楚了:returned collection is a live view of {@code unfiltered};changes to one affect the other.

/**
   * Returns the elements of {@code unfiltered} that satisfy a predicate. The returned collection is
   * a live view of {@code unfiltered}; changes to one affect the other.
   *
   * <p>The resulting collection's iterator does not support {@code remove()}, but all other
   * collection methods are supported. When given an element that doesn't satisfy the predicate, the
   * collection's {@code add()} and {@code addAll()} methods throw an {@link
   * IllegalArgumentException}. When methods such as {@code removeAll()} and {@code clear()} are
   * called on the filtered collection, only elements that satisfy the filter will be removed from
   * the underlying collection.
   *
   * <p>The returned collection isn't threadsafe or serializable, even if {@code unfiltered} is.
   *
   * <p>Many of the filtered collection's methods, such as {@code size()}, iterate across every
   * element in the underlying collection and determine which elements satisfy the filter. When a
   * live view is <i>not</i> needed, it may be faster to copy {@code Iterables.filter(unfiltered,
   * predicate)} and use the copy.
   *
   * <p><b>Warning:</b> {@code predicate} must be <i>consistent with equals</i>, as documented at
   * {@link Predicate#apply}. Do not provide a predicate such as {@code
   * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. (See {@link
   * Iterables#filter(Iterable, Class)} for related functionality.)
   *
   * <p><b>{@code Stream} equivalent:</b> {@link java.util.stream.Stream#filter Stream.filter}.
   */

Collections2.filter方法返回的只是原有列表的一个视图。所以:改变被过滤列表会影响过滤后列表,反之亦然。并且我们从这段文字中还能get到下面几个注意事项:

  1. 过滤后列表的迭代器不支持remove()操作

  2. add不符合过滤条件的元素,会抛出IllegalArgumentException

  3. 当过滤后列表中的元素不再满足过滤条件时,会影响到已经过滤出来的列表出于程序员的本能,决定还是看下源码心里更踏实。核心源码如下:

1. add方法:
   public boolean add(E element) {
       //不符合过滤条件抛IllegalArgumentException的原因
       checkArgument(predicate.apply(element));
       return unfiltered.add(element);
   }
   不支持通过迭代器删除的原因:
   public abstract class UnmodifiableIterator<E> implements Iterator<E> {
   /** Constructor for use by subclasses. */
   protected UnmodifiableIterator() {}
   /**

* Guaranteed to throw an exception and leave the underlying data unmodified.
  *
* @throws UnsupportedOperationException always
* @deprecated Unsupported operation.
  */
  @Deprecated
  @Override
  public final void remove() {
   throw new UnsupportedOperationException();
  }
  打印结果变化的原因:
  public String toString() {
      Iterator<E> it = iterator();
      if (! it.hasNext())
       return "[]";
      StringBuilder sb = new StringBuilder();
      sb.append('[');
      for (;;) {
          E e = it.next();
          sb.append(e == this ? "(this Collection)" : e);
          if (! it.hasNext())
          return sb.append(']').toString();
          sb.append(',').append(' ');
      }
  }
  @Override
  public Iterator<E> iterator() {
   return Iterators.filter(unfiltered.iterator(), predicate);
  }

问题已经弄清楚了,怎么修改这个问题呢?

方案一(可行):

干脆不用guava,

List<Person> names = Lists.newArrayList(lxy, xhf, nws);
    ArrayList<Person> persons = new ArrayList<>();
    for (Person p : names) {
    if(p.age > 30) {
     persons.add(p);
    }
}

方案二(可行):

用个容器再包装一下:

Collection<Person> personAgeGt30 = new ArrayList<(Collections2.filter(names, p -> p.age > 30));

方案三(可行,改用Java8的过滤器):

List<Person> personAgeGt30 = names.stream().filter((Predicate<Person>) p -> p.age >30).collect(Collectors.toList());

方案四(可行,改用Guava的连贯接口,IDEA编辑器会提示你替换成Java8 API)

ImmutableList<Person> personAgeGt30 = FluentIterable.from(names).filter(p -> p.age > 30).toList();

上述方案中,支持java8的生产环境推荐方案三,不支持java8的生产环境推荐方案二。

总结

其实,Java语言中类似的坑还有很多,比如:

  • 1.Arrays.asList()生成的列表是不可变的。

  • 2.subList生成的子列表,对原列表元素的修改,会导致子列表的遍历、增加、删除抛出ConcurrentModificationException,

  • 3.subList对子列表的修改会影响到原列表数据

  • 4.修改Map的keySet()方法和values()生成的容器,都会影响Map本身。

总之,使用任何API之前多看看源码,至少看看源码的注释。

PS:欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。

欢迎加入后端架构师交流群,在后台回复“007”即可。

猜你还想看

阿里、腾讯、百度、华为、京东最新面试题汇集

自从上线了 Prometheus 监控告警,真香!

一文详解微服务架构,看这篇就够了!

5年内禁用支付宝和微信支付!多地公安出手:这些人摊上大事了

嘿,你在看吗

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值