Eclipse集合的Java演变

重要要点

  • Eclipse Collections是一个高性能Java Collections框架,针对Java 8及更高版本进行了重新设计,为Java Collections框架添加了丰富的功能。
  • 它在高盛内部开发了10年,然后于2012年作为GS Collections开源。 在2015年,它被迁移到Eclipse Foundation。
  • 它使用原始数据结构,其性能要比传统原始集合好得多。
  • 在Eclipse Collections 8.0发行版之前,EC与Java版本5-7兼容。8.0及更高版本要求Java 8+,并使用Optional处理潜在的空值。
  • 最新版本也已更新为支持Java 9中的模块。

30第二篇简介–什么是Eclipse Collections?

Eclipse Collections是Java Collections框架的替代品。 它具有JDK兼容的List,Set和Map实现,具有丰富的API,以及JDK中找不到的其他类型,例如Bags,Multimaps和BiMaps。 Eclipse Collections还具有完整的原始容器。 它在高盛内部开发了10年,然后于2012年作为GS Collections开源。 在2015年,它被迁移到Eclipse基础,此后,该框架的所有活动开发都以Eclipse Collections的名称和存储库完成。 如果您需要一些入门书籍,请阅读Donald Raab的InfoQ文章,“ GS范例集”第一部分第二部分

领域

在深入探讨任何细节或代码示例之前,让我们逐步介绍本文中用于代码段的领域。 如下图所示:

(点击图片放大)

我们有一个人员列表(类型Person),每个人都可以有一个Pets列表,并且每个宠物都具有某个PetType枚举。

Java 8版本8

在Eclipse Collections发行版之前,EC与Java版本5-7兼容。在使用丰富的API时,您也可以使用Java 8并利用lambda和方法引用,实际上,它工作得很好。

但这就是您真正拥有的全部。 Eclipse Collections与Java 8兼容,但是没有使用或包含它。 现在,从Eclipse Collections开始,我们已经做出了与Java 8+兼容的设计决策,以便开始利用我们自己代码库中的一些很酷的Java 8功能。

可选的

可选的是Java 8最受欢迎的新功能之一。在Javadoc中,“可能包含也可能不包含非null值的容器对象。如果存在值,则isPresent()将返回true,get()将返回true。返回值”。 基本上,Optional通过强制我们处理可能为空的项目来帮助保护我们免受NullPointerExceptions的侵害。 那么,我们可以在Eclipse Collections中使用它吗? RichIterable.detectWith()非常适合。 detectWith接受Predicate参数,并返回满足该条件的集合中的第一个元素。 如果找不到任何元素,则返回null。 因此,在8.0中,我们引入了detectWithOptional()。 它返回一个Optional,而不是返回element或null,然后留给用户处理。 请参见下面的代码示例(摘自我们的kata教程材料):

Person person = this.people.detectWith(Person::named, "Mary Smith");
//null pointer exception
Assert.assertEquals("Mary", person.getFirstName());
Assert.assertEquals("Smith", person.getLastName());

在这里,我们想找到玛丽·史密斯。 当我们调用detectWith时,person被设置为null,因为我们找不到满足谓词的人。 因此,代码将引发NullPointerException。

Person person = this.people.detectWith(Person::named, "Mary Smith");
if (person == null)
{
      person = new Person("Mary", "Smith");
}
Assert.assertEquals("Mary", person.getFirstName());
Assert.assertEquals("Smith", person.getLastName());

接下来,在Java 8之前,我们总是可以使用空检查,如上所示。 但是Java 8提供了一个Optional,所以让我们使用它吧!

Optional<Person> optional = 
          this.people.detectWithOptional(Person::named, "Mary Smith");
Person person = optional.orElseGet(() -> new Person("Mary", "Smith"));
Assert.assertEquals("Mary", person.getFirstName());
Assert.assertEquals("Smith", person.getLastName());

在这里,detectWithOptional而不是返回null,而是返回Person周围的Optional包装器。 现在由我决定如何处理这个可能为空的值。 在我的代码中,如果它为null,我将调用orElseGet()创建一个新的Person实例。 测试通过了,我们避免了任何例外!

收藏家

如果在代码中使用流,则很可能以前使用过收集器。 收集器是一种实现可变还原操作的方法。 例如,Collectors.toList()允许我们将流中的项目累积到列表中。 JDK有几个“内置”收集器,可以在Collectors类中找到。 参见下面的一些Java 8(无Eclipse Collections)示例。

List<String> names = this.people.stream()
                         .map(Person::getFirstName)
                         .collect(Collectors.toList());
// Output:
// [Bob, Ted, Jake]
int total = this.people.stream().collect(
          Collectors.summingInt(Person::getNumberOfPets));
// Output:
// 4

由于我们现在可以使用Eclipse Collections来利用流,因此我们也应该拥有自己的内置Collector-Collectors2。 这些收集器中有许多是针对Eclipse Collections特定的数据结构的。 不能从JDK中获得的功能,例如toBag(),toImmutableSet()等。

(点击图片放大)

这张图刮擦了Collectors2 API的表面。 顶部的框是您可以将Collectors2的结果放入的所有不同的数据结构,下面的项目符号是一些可用的不同API。 您可以看到我们对JDK和Eclipse Collections数据结构以及原始集合都具有类型支持。 您甚至可以通过Collectors2使用我们熟悉的collect(),select(),reject()等API。

还可能与收集器和收集器2互操作; 两者并不互相排斥。 参见下面的示例,我们使用的是JDK 8 Collectors,但是为了方便起见,然后使用EC 8.0 Collectors2:

Map<Integer, String> people = this.people
   .stream()
   .collect(Collectors.groupingBy(
      Person::getNumberOfPets,
      Collectors2.makeString()));

Map<Integer, String> people2 = this.people
   .stream()
   .collect(Collectors.groupingBy(
      Person::getNumberOfPets,
      Collectors.mapping(
         Object::toString,
         Collectors.joining(","))));
// Output: {1=Ted, Jake, 2=Bob}

这两个代码片段产生相同的输出,但是要注意细微的差别:Eclipse Collections提供了makeString()功能,该功能创建一个用逗号分隔的以String表示的元素集合。 为了仅使用Java 8来执行此操作,它需要通过调用Collectors.mapping(),将每个对象转换为其toString值并以逗号将其连接来进行更多工作。

默认方法

对于像Eclipse Collections这样的框架,默认方法是JDK的一个很棒的新功能。 我们可以在一些最高级的接口上实现新的API,而不必更改下面的许多实现。 reduceInPlace()是我们添加到RichIterable中的新方法之一–它有什么作用?

/**
     * This method produces the equivalent result as 
     * {@link Stream#collect(Collector)}.
     * <p>
     * <pre>
     * MutableObjectLongMap<Integer> map2 =
     *     Lists.mutable
                .with(1, 2, 3, 4, 5)
                .reduceInPlace(
                  Collectors2.sumByInt(
                    i -> Integer.valueOf(i % 2), Integer::intValue));
     * </pre>
     * @since 8.0
     */
    default <R, A> R reduceInPlace(Collector<? super T, A, R> collector)
    {
        A mutableResult = collector.supplier().get();
        BiConsumer<A, ? super T> accumulator = collector.accumulator();
        this.each(each -> accumulator.accept(mutableResult, each));
        return collector.finisher().apply(mutableResult);
    }

reduceInPlace等效于在流上使用Collector。 但是为什么我们需要将其添加到Eclipse Collections? 原因很有趣。 一旦进入Eclipse Collections提供的Immutable或Lazy API,我们将无法再利用流式API。 那时,我们无法获得使用收集器所具有的功能,因为stream()不再对我们可用,后续的API调用也不再可用。 这就是reduceInPlace发挥作用的地方。

如下所示,一旦在集合上调用.toImmutable()或.asLazy(),就无法再调用.stream()。 因此,如果我们想利用收集器,我们现在可以使用.reduceInPlace()并获得相同的结果。

原始集合

自GS Collections 3.0以来,我们受益于原始集合。 Eclipse集合为所有基本类型添加了内存优化的集合,具有与对象类型相似的接口以及基本类型之间的对称性。

(点击图片放大)

如上所示,使用原始集合有很多好处。 可以节省大量内存,因为您可以避免将非原始类型装箱。 从Java 8开始,我们有三种原始类型(int,long和double),它们使用专门的原始流和lambda表达式。 在Eclipse Collections中,如果您想要相同的延迟评估,我们将在所有八个原始类型上直接提供该API。 让我们看一些代码示例:

流-像Iterator

IntStream stream = IntStream.of(1, 2, 3);
Assert.assertEquals(1, stream.min().getAsInt());
Assert.assertEquals(3, stream.max().getAsInt());

java.lang.IllegalStateException: stream has already been operated upon or closed
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
	at java.base/java.util.stream.IntPipeline.reduce(IntPipeline.java:474)
	at java.base/java.util.stream.IntPipeline.max(IntPipeline.java:437)

LazyIntIterable lazy = IntLists.mutable.with(1, 2, 3).asLazy();
Assert.assertEquals(1, lazy.min());
Assert.assertEquals(3, lazy.max()); //reuse!

上面,我们正在创建一个1、2和3的IntStream,并尝试在其上调用min()和max()。 Java 8流就像一个迭代器,因为您不能重复使用它们。 Eclipse集合LazyIterables允许重用。 让我们看一个更复杂的例子:

List<Map.Entry<Integer, Long>> counts = this.people.stream().flatMap(
                                  person -> person.getPets().stream())
       .collect(Collectors.groupingBy(Pet::getAge, Collectors.counting()))
       .entrySet()
       .stream()
       .filter(e -> e.getValue().equals(Long.valueOf(1)))
       .collect(Collectors.toList());
// Output: [3=1, 4=1]

MutableIntBag counts2 = this.people.asLazy()
       .flatCollect(Person::getPets)
       .collectInt(Pet::getAge)
       .toBag()
       .selectByOccurrences(IntPredicates.equal(1));
// Output: [3, 4]

在这里,我们要过滤只出现一次的宠物年龄。 由于Java 8没有Bag数据结构(该数据结构将项目映射到其计数),因此我们必须通过计数将集合归类到映射中。 请注意,一旦在宠物上调用了collectInt(),我们现在就可以移至原始集合和API。 当我们调用.toBag()时,我们有一个专门的原始IntBag。 selectByOccurrences()是Bag特定的API,它使我们可以根据Bag出现的次数过滤出Bag中的项目。

Java 9-下一步是什么?

众所周知,Java 9对Java生态系统进行了许多有趣的更改,例如新的模块系统和内部API封装。 Eclipse Collections必须更改才能兼容。

在8.2版本中,我们必须修改任何使用反射的方法才能构建项目。 您可以在ArrayListIterate中看到一个示例:

public final class ArrayListIterate
{
   private static final Field ELEMENT_DATA_FIELD;
   private static final Field SIZE_FIELD;
   private static final int MIN_DIRECT_ARRAY_ACCESS_SIZE = 100;

   static
   {
       Field data = null;
       Field size = null;
       try
       {
           data = ArrayList.class.getDeclaredField("elementData");
           size = ArrayList.class.getDeclaredField("size");
           data.setAccessible(true);
           size.setAccessible(true);
       }
       catch (Exception ignored)
       {
           data = null;
           size = null;
       }
       ELEMENT_DATA_FIELD = data;
       SIZE_FIELD = size;
   }

在此示例中,一旦我们调用data.setAccessible(true),就会引发异常。 作为解决方法,我们只需将data和size设置为null即可继续。 不幸的是,我们不能再使用这些字段来优化我们的迭代模式,但是我们现在与Java 9兼容,因为此修复解决了我们的反射问题。

如果您尚未准备好迁移当前代码,则有一些解决方法可以进行反射。 您可以通过添加命令行参数来避免抛出这些异常,但是作为框架,我们不想给用户带来负担。 主动解决了所有反射问题,因此您可以开始使用Java 9进行编码!

结论

Eclipse Collections随着Java格局的不断变化而不断发展壮大。 如果您尚未这样做,请尝试使用Java 8代码进行尝试,并查看上面描述的新功能! 如果您是该框架的新手,并且需要一个开始的地方,那么这里有一些适合您的资源:

更多信息:

Eclipse集合

训练材料

参考指南

您还可以在这里观看Eclipse集合的Java演变的完整视频。

祝您编码愉快!

翻译自: https://www.infoq.com/articles/eclipse-collections/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值