点击上方蓝色“方志朋”,选择“设为星标”
回复“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到下面几个注意事项:
过滤后列表的迭代器不支持remove()操作
add不符合过滤条件的元素,会抛出IllegalArgumentException
当过滤后列表中的元素不再满足过滤条件时,会影响到已经过滤出来的列表出于程序员的本能,决定还是看下源码心里更踏实。核心源码如下:
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 领取,更多内容陆续奉上。
明天见(。・ω・。)ノ♡