震惊 Guava 竟然有"坑"

点击上方蓝色“方志朋”,选择“设为星标”

回复“666”获取独家整理的学习资料!

转自:Java布道

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

 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之前多看看源码,至少看看源码的注释。

热门内容:

最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
明天见(。・ω・。)ノ♡
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值