谷歌的guava竟然也有坑

简介: 谷歌的guava竟然也有坑

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


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()操作


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




目录
相关文章
|
5月前
Google Guava ListeningExecutorService
Google Guava ListeningExecutorService
33 0
|
8月前
|
缓存 安全 Java
Google guava工具类的介绍和使用
Google guava工具类的介绍和使用
241 1
|
8月前
|
缓存 Java Maven
Google guava工具类库的介绍和使用
Google guava工具类库的介绍和使用
|
缓存 安全 Java
谷歌Guava LoadingCache介绍
CacheLoader的作用就是为了在Cache中数据缺失时加载数据,其中最重要的方法就是load()方法,你可以在load() 方法中实现对应key加载数据的逻辑。在调用LoadingCache的get(key)方法时,如果key对应的value不存在,LoadingCache就会调起你在创建cache时传入的CacheLoader的load方法。
235 0
|
API
Google Guava之Joiner
日常开发中,我们经常需要将几个字符串,或者字符串数组、列表之类的数据,拼接成一个以指定符号分隔各个元素的字符串,比如把[1, 2, 3]拼接成"1-2-3",如果自己实现的话,基本上就需要编写循环去实现这个功能,代码就变得晦涩起来。
230 0
Google Guava之Joiner
|
存储 缓存 监控
真正的缓存之王,Google Guava 只是弟弟(一)
前面刚说到Guava Cache,他的优点是封装了get,put操作;提供线程安全的缓存操作;提供过期策略;提供回收策略;缓存监控。当缓存的数据超过最大值时,使用LRU算法替换。这一篇我们将要谈到一个新的本地缓存框架:Caffeine Cache。它也是站在巨人的肩膀上-Guava Cache,借着他的思想优化了算法发展而来。 本篇博文主要介绍Caffine Cache 的使用方式,以及Caffine Cache在SpringBoot中的使用。
真正的缓存之王,Google Guava 只是弟弟(一)
|
缓存 安全 Java
《Guava》基础 入门
《Guava》基础 入门
215 0
《Guava》基础 入门
|
存储 缓存 JSON
Google Guava本地缓存的实战
Google Guava本地缓存的实战
682 0
Google Guava本地缓存的实战
|
存储 缓存 NoSQL
真正的缓存之王,Google Guava 只是弟弟(二)
真正的缓存之王,Google Guava 只是弟弟(二)