TECHNOLOGY DIVISION GS CollectionsとJava 8 実用的で流暢なAPIで楽しい開発を! ゴールドマン・サックス テクノロジー部 ヴァイス・プレジデント 伊藤博志 JJUG CCC 2015 Spring #ccc_f3 1 TECHNOLOGY DIVISION ゴールドマン・サックスのエンジニアリング 2 TECHNOLOGY DIVISION ゴールドマン・サックスのエンジニアリング ゴールドマン・サックスとは 投資銀行業務、証券業務および投資運用業務を中心に、企業、金融機関、政府機関、富 裕層など多岐にわたるお客様を対象に幅広い金融サービスを提供している世界有数の金 融機関です。1869年に創業、ニューヨークを本拠地として、世界の主要な金融市場に拠点 を擁しています。 ゴールドマン・サックス エンジニアリング 複雑な問題へのソリューションの構築、時代を変えるテクノロジーの創出、ビジネスと金融 マーケットをグローバルに牽引するシステム開発を行っています。 http://www.goldmansachs.com/japan/what-we-do/engineering/index.html 3 TECHNOLOGY DIVISION ゴールドマン・サックスのエンジニアリング 4 Agenda • • • • TECHNOLOGY DIVISION イントロダクション 古き良き時代の再到来 Java 8のStream APIは大海原への入り口 大海原(GS Collections)を探検してみよう – – – – API 流暢さ メモリ効率 メソッド参照 • フレームワークの比較 5 GS Collectionsとは? TECHNOLOGY DIVISION • ゴールドマン・サックスの開発したオープンソースコレク ションフレームワーク – 2004年より開発を開始 – GitHubにて公開 (Apache 2.0 License) • github.com/goldmansachs/gs-collections • GS Collections Kata – 2007年に開発されたトレーニング教材 – 1,500人以上のGS Javaエンジニアが受講 – GitHubにて公開 (Apache 2.0 License) • github.com/goldmansachs/gs-collections-kata 6 古き良き時代 1997 – ケント・ベックのSmalltalk ベストプラクティスパターン • do: • select: • reject: • collect: • detect: • detect:ifNone: • inject:into: •… (Dr. Seuss API) TECHNOLOGY DIVISION 2007 – 実装パターン(ケント・ベック) • Map • List • Set • コレクションのイテレーショ ンパターンが消える • 残ったのは型のみ 7 良き時代の再到来 Pattern in Classic Java Pattern in GS Collections w/ Lambdas list detect: [:each | each > 50]. list select: [:each | each > 50]. List<Integer> result = new ArrayList<>(); for (Integer each : list) if (each > 50) result.add(each); list.select(each -> each > 50); list reject: [:each | each > 50]. List<Integer> result = new ArrayList<>(); for (Integer each : list) if (each <= 50) result.add(v); list.reject(each -> each > 50); list anySatisfy: [:each | each > 50]. for (Integer each : list) if (each > 50) return true; return false; list.anySatisfy(each -> each > 50); list allSatisfy: [:each | each > 50]. for (Integer each : list) if (each <= 50) return false; return true; list.allSatisfy(each -> each > 50); list collect: [:e | e printString]. List<String> result = new ArrayList<>(); for (Integer each : list) result.add(each.toString()); list.collect(Object::toString); list inject: 3 into: [:x :y | x + y]. int result = 3; for (Integer each : list) result = result + each; list.injectInto(3, Integer::sum); inject into collect all satisfy any satisfy reject detect for (Integer each : list) if (each > 50) return each; return null; select Pattern in Smalltalk-80 TECHNOLOGY DIVISION list.detect(each -> each > 50); 8 遅延実行 TECHNOLOGY DIVISION findAny filter filter boolean all = list.asLazy().allSatisfy(e -> e > 50); inject into collect boolean any = list.asLazy().anySatisfy(e -> e > 50); LazyIterable<String> result = list.asLazy().collect(Object::toString); Integer result = list.asLazy().injectInto(3, Integer::sum); any Match Stream<Integer> result = list.stream().filter(e -> e <= 50); boolean any = list.stream().anyMatch(e -> e > 50); all Match LazyIterable<Integer> result = list.asLazy().reject(e -> e > 50); boolean all = list.stream().allMatch(e -> e > 50); map Stream<Integer> result = list.stream().filter(e -> e > 50); Stream<String> result = list.stream().map(Object::toString); reduce detect LazyIterable<Integer> result = list.asLazy().select(e -> e > 50); any satisfy Integer result = list.stream() .filter(e -> e > 50).findFirst().orElse(null); all satisfy Integer result = list.asLazy() .detectIfNone(e -> e > 50, () -> null); select Java 8 Streams reject GS Collections LazyIterable Integer result = list.stream().reduce(3, Integer::sum); 9 TECHNOLOGY DIVISION GS Collectionsの即時実行 vs Stream APIの遅延実行 findAny filter MutableList<Integer> result = list.reject(e -> e > 50); filter List<Integer> result = list.stream().filter(e -> e <= 50).collect(Collectors.toList()); boolean result = list.stream().allMatch(e -> e > 50); inject into MutableList<String> result = list.collect(Object::toString); Integer result = list.injectInto(3, Integer::sum); map boolean all = list.allSatisfy(e -> e > 50); List<String> result = list.stream().map(Object::toString).collect(Collectors.toList()); reduce boolean result = list.stream().anyMatch(e -> e > 50); collect boolean any = list.anySatisfy(e -> e > 50); any Match List<Integer> result = list.stream().filter(e -> e > 50).collect(Collectors.toList()); all Match detect MutableList<Integer> result = list.select(e -> e > 50); any satisfy Integer result = list.stream().filter(e -> e > 50).findFirst().orElse(null); all satisfy Integer result = list.detect(e -> e > 50); select Java 8 Streams reject Eager GS Collections Integer result = list.stream().reduce(3, Integer::sum); 10 Java 8 Stream API • • • • • TECHNOLOGY DIVISION 機能豊富な関数型APIを提供する素晴らしいフレームワーク デフォルトで遅延実行のイテレーション 直列と並列のサポート 3種類のプリミティブ型をサポートするStream Collectorの実装による拡張可能性 Java 8のStream APIは大海原への入り口! 11 GS Collectionsの豊富な機能 TECHNOLOGY DIVISION 大海原を探検してみよう • コレクションの即時実行イテレーションパターン • より豊富なイテレーションパターン – zip(), chunk(), partition(), makeString(), tap() 他多数 • コレクションプロトコルにおける共変型の戻り値 • 新しいコレクション型 – Bag, SortedBag, BiMap, Multimap • メモリ効率のよいSetとMapの実装 • プリミティブ型のコンテナ • イミュータブルなコンテナ 12 Agenda • • • • TECHNOLOGY DIVISION イントロダクション 古き良き時代の再到来 Java 8のStream APIは大海原への入り口 大海原(GS Collections)を探検してみよう – – – – API 流暢さ メモリ効率 メソッド参照 • フレームワークの比較 13 海は広ければ広いほうがいい! Java 8 GS Collections Stream vs. LazyIterable Interfaces 5 9 Functional Interfaces 46 298 Object Container Interfaces 11 75 Primitive Container Interfaces 0 309 Stream vs. RichIterable API 47 109 Primitive Stream vs. Iterable API 48 x 3 = 144 38 x 8 = 304 LOC (Streams vs. GSC w/o code gen) ~15k ~400k TECHNOLOGY DIVISION 14 より多くのイテレーションパターン • • • • • • • TECHNOLOGY DIVISION flatCollect partition makeString / appendString groupBy aggregateBy sumOf sumBy 15 ユーティリティは無用! TECHNOLOGY DIVISION • Utility – 後方互換性を保った状態で新しい機能が容易に追加 できる • API – – – – – 新しい機能が容易に発見できる 容易に最適化できる 可読性が良い 特定の戻り値により理解がしやすい 動詞 vs 動名詞 16 Joining vs. MakeString TECHNOLOGY DIVISION String joined = things.stream() .map(Object::toString) .collect(Collectors.joining(", ")); String joined = things.makeString(", "); 17 SummingInt vs. SumOfInt TECHNOLOGY DIVISION int total = employees.stream() .collect( Collectors.summingInt(Employee::getSalary)); long total = employees.sumOfInt(Employee::getSalary); 18 GroupingBy vs. GroupBy TECHNOLOGY DIVISION Map<Department, List<Employee>> byDept = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment)); Multimap<Department, Employee> byDept = employees.groupBy(Employee::getDepartment); 19 TECHNOLOGY DIVISION GroupingBy/SummingBy vs. SumBy Map<Department, Integer> totalByDept = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.summingInt(Employee::getSalary))); ObjectLongMap<Department> totalByDept = employees.sumByInt( Employee::getDepartment, Employee::getSalary); 20 PartitioningBy vs. Partition TECHNOLOGY DIVISION Map<Boolean, List<Student>> passingFailing = students.stream() .collect(Collectors.partitioningBy( s -> s.getGrade() >= PASS_THRESHOLD)); PartitionList<Student> passingFailing = students.partition( s -> s.getGrade() >= PASS_THRESHOLD); 21 スタックを見てみると? TECHNOLOGY DIVISION Stream API GS Collections 22 Agenda • • • • TECHNOLOGY DIVISION イントロダクション 古き良き時代の再到来 Java 8のStream APIは大海原への入り口 大海原(GS Collections)を探検してみよう – – – – API 流暢さ メモリ効率 メソッド参照 • フレームワークの比較 23 アナグラム・チュートリアル TECHNOLOGY DIVISION http://docs.oracle.com/javase/tutorial/collections/algorithms/ • 辞書内のすべてのWordオブジェクトからスタート • それぞれアルファグラムごとにグルーピング – アルファグラムとは自身の文字をソートしたもの – alerts aelrst – stelar aelrst • アナグラムが少なくとも8個存在するグループをフィルタ • グループをアナグラムの数でソート(降順) • 下記のフォーマットでプリント 11: [alerts, alters, artels, estral, laster, ratels, salter, slater, staler, stelar, talers] 24 アナグラム・チュートリアル TECHNOLOGY DIVISION this.getWords() .stream() .collect(Collectors.groupingBy(Alphagram::new)) .values() .stream() .filter(each -> each.size() >= SIZE_THRESHOLD) .sorted(Comparator.<List<?>>comparingInt(List::size).reversed()) .map(each -> each.size() + ": " + each) .forEach(System.out::println); 25 アナグラム・チュートリアル TECHNOLOGY DIVISION this.getWords() .groupBy(Alphagram::new) .multiValuesView() .select(each -> each.size() >= SIZE_THRESHOLD) .toSortedListBy(RichIterable::size) .asReversed() .collect(each -> each.size() + ": " + each) .each(System.out::println); 26 アナグラム・チュートリアル TECHNOLOGY DIVISION this.getWords() .groupBy(Alphagram::new) .multiValuesView() .select(each -> each.size() >= SIZE_THRESHOLD) .toSortedListBy(RichIterable::size) .asReversed() .collect(each -> each.size() + ": " + each) .each(System.out::println); Type: MutableListMultimap<Alphagram, String> 27 アナグラム・チュートリアル TECHNOLOGY DIVISION this.getWords() .groupBy(Alphagram::new) .multiValuesView() .select(each -> each.size() >= SIZE_THRESHOLD) .toSortedListBy(RichIterable::size) .asReversed() .collect(each -> each.size() + ": " + each) .each(System.out::println); Type: RichIterable<RichIterable<String>> 28 アナグラム・チュートリアル TECHNOLOGY DIVISION this.getWords() .groupBy(Alphagram::new) .multiValuesView() .select(each -> each.size() >= SIZE_THRESHOLD) .toSortedListBy(RichIterable::size) .asReversed() .collect(each -> each.size() + ": " + each) .each(System.out::println); Type: RichIterable<RichIterable<String>> 29 アナグラム・チュートリアル TECHNOLOGY DIVISION this.getWords() .groupBy(Alphagram::new) .multiValuesView() .select(each -> each.size() >= SIZE_THRESHOLD) .toSortedListBy(RichIterable::size) .asReversed() .collect(each -> each.size() + ": " + each) .each(System.out::println); Type: MutableList<RichIterable<String>> 30 アナグラム・チュートリアル TECHNOLOGY DIVISION this.getWords() .groupBy(Alphagram::new) .multiValuesView() .select(each -> each.size() >= SIZE_THRESHOLD) .toSortedListBy(RichIterable::size) .asReversed() .collect(each -> each.size() + ": " + each) .each(System.out::println); Type: LazyIterable<RichIterable<String>> 31 アナグラム・チュートリアル TECHNOLOGY DIVISION this.getWords() .groupBy(Alphagram::new) .multiValuesView() .select(each -> each.size() >= SIZE_THRESHOLD) .toSortedListBy(RichIterable::size) .asReversed() .collect(each -> each.size() + ": " + each) .each(System.out::println); Type: LazyIterable<String> 32 アナグラム・チュートリアル TECHNOLOGY DIVISION this.getWords() .groupBy(Alphagram::new) .multiValuesView() .select(each -> each.size() >= SIZE_THRESHOLD) .toSortedListBy(RichIterable::size) .asReversed() .collect(each -> each.size() + ": " + each) .each(System.out::println); 11: [alerts, alters, artels, estral, laster, ratels, salter, slater, staler, stelar, talers] 33 Parallel Lazy Iteration TECHNOLOGY DIVISION Stream<Address> addresses = people.parallelStream() .map(Person::getAddress); ParallelListIterable<Address> addresses = people.asParallel(executor, batchSize) .collect(Person::getAddress); http://www.infoq.com/presentations/java-streams-scala-parallel-collections 34 Agenda • • • • TECHNOLOGY DIVISION イントロダクション 古き良き時代の再到来 Java 8のStream APIは大海原への入り口 大海原(GS Collections)を探検してみよう – – – – API 流暢さ メモリ効率 メソッド参照 • フレームワークの比較 35 Mapのメモリ消費量の比較 TECHNOLOGY DIVISION 45 40 JDK HashMap 35 Size (Mb) 30 25 GSC UnifiedMap Trove THashMap 20 15 10 5 0 Elements 36 メモリの最適化 TECHNOLOGY DIVISION JDK HashMap • • • • Entry はkey, value, next, hashを持つ keyとvalueを配列に持ったほうが省スペース 平均して半分のメモリスペース Map.entrySet()に注目 – 抽象性の破綻 • MapがEntryのテーブルとして実装されていることを仮定 – O(1)ではなくO(n) – 代わりにforEachKeyValue()を使うべき 37 Setのメモリ消費量の比較 TECHNOLOGY DIVISION Size (Mb) 60 50 JDK HashSet 40 GSC UnifiedSet 30 Trove THashSet 20 10 0 Elements 38 メモリの最適化 TECHNOLOGY DIVISION JDK HashSet • • • • HashSetはHashMapに委譲する形で実装されている やはりEntryはスペースの無駄である それぞれの(key, value)に対してのvalueも無駄である 平均して4倍のメモリ消費量 39 はるか昔の誤った判断 TECHNOLOGY DIVISION 40 プリミティブのコレクションによるメモリ削減 TECHNOLOGY DIVISION 25 20 Size (Mb) 15 10 JDK ArrayList GSC IntArrayList Trove TIntArrayList 5 0 Elements 41 List<Integer> vs. IntList TECHNOLOGY DIVISION • JavaはObjectとプリミティブの配列を持つ – 配列はメソッドを持たない • JavaにはプリミティブのList、Set、Mapが存在しない – プリミティブ型を使うにはBoxingが必要 – Boxingはメモリの浪費 • Reference + Header + alignment 42 Agenda • • • • TECHNOLOGY DIVISION イントロダクション 古き良き時代の再到来 Java 8のStream APIは大海原への入り口 大海原(GS Collections)を探検してみよう – – – – API 流暢さ メモリ効率 メソッド参照 • フレームワークの比較 43 ラムダ式とメソッド参照 TECHNOLOGY DIVISION • Kata をJava 7からJava 8にアップグレード • 無名内部クラスを用いていた箇所をメソッド参照に変換 MutableList<String> customerCities = customers.collect(Customer::getCity); • ラムダ式に変換したところもある MutableList<Customer> customersFromLondon = customers.select(customer -> customer.livesIn("London")); 44 ラムダ式とメソッド参照 TECHNOLOGY DIVISION • メソッド参照のシンタックスは魅力的 • selectの例をメソッド参照で書けないか? MutableList<Customer> customersFromLondon = customers.select(Customer::livesInLondon); • 誰もこんなメソッドは書かない。。 45 ラムダ式とメソッド参照 TECHNOLOGY DIVISION • 現在メソッド参照を使用している箇所 • かつてはクラス定数として定義していた (Java 8 以前) MutableList<String> customerCities = customers.collect(Customer.TO_CITY); public static final Function<Customer, String> TO_CITY = new Function<Customer, String>() { public String valueOf(Customer customer) { return customer.getCity(); } }; 46 ラムダ式とメソッド参照 • selectの例はガベージをつくる TECHNOLOGY DIVISION (Java 8 以前) MutableList<Customer> customersFromLondon = customers.select(new Predicate<Customer>() { public boolean accept(Customer customer) { return customer.livesIn("London"); } }); 47 ラムダ式とメソッド参照 TECHNOLOGY DIVISION • ガベージを避けるためselectWith(Predicate2)を導入 (Java 8 以前) MutableList<Customer> customersFromLondon = customers.selectWith(Customer.LIVES_IN, "London"); public static final Predicate2<Customer, String> LIVES_IN = new Predicate2<Customer, String>() { public boolean accept(Customer customer, String city) { return customer.livesIn(city); } }; 48 ラムダ式とメソッド参照 TECHNOLOGY DIVISION • この*With()メソッドはメソッド参照と非常に相性が良い MutableList<Customer> customersFromLondon = customers.selectWith(Customer::livesIn, "London"); • おかげでメソッド参照が使える箇所が数多く生じた 49 フレームワークの比較 Features GS Collections Java 8 Guava Rich API Interfaces Readable, Mutable, Immutable, FixedSize, Lazy Mutable, Stream Mutable, Fluent Optimized Set & Map (+Bag) Immutable Collections Primitive Collections (+Bag, +Immutable) Multimaps (+Bag, +SortedBag) (+Linked) Bags (Multisets) BiMaps Iteration Styles Eager/Lazy, Serial/Parallel TECHNOLOGY DIVISION Trove Scala Mutable Readable, Mutable, Immutable, Lazy Lazy, Serial/Parallel Lazy, Serial (Multimap trait) Eager, Serial Eager/Lazy, Serial/Parallel (Lazy Only) 50 TECHNOLOGY DIVISION 付録 51 GS Collectionsを始めてみよう Maven <dependency> <groupId>com.goldmansachs</groupId> <artifactId>gs-collections-api</artifactId> <version>6.1.0</version> </dependency> <dependency> <groupId>com.goldmansachs</groupId> <artifactId>gs-collections</artifactId> <version>6.1.0</version> </dependency> <dependency> <groupId>com.goldmansachs</groupId> <artifactId>gs-collections-testutils</artifactId> <version>6.1.0</version> <scope>test</scope> </dependency> <dependency> <groupId>com.goldmansachs</groupId> <artifactId>gs-collections-forkjoin</artifactId> <version>6.1.0</version> </dependency> TECHNOLOGY DIVISION お気に入りのビルド・依存性管理ツールで Ivy <dependency org="com.goldmansachs" name="gs-collections-api" rev="6.1.0" /> <dependency org="com.goldmansachs" name="gs-collections" rev="6.1.0" /> <dependency org="com.goldmansachs" name="gs-collections-testutils" rev="6.1.0" /> <dependency org="com.goldmansachs" name="gs-collections-forkjoin" rev="6.1.0"/> Gradle dependencies { compile group: 'com.goldmansachs', name: 'gs-collections-api', version: '6.1.0' compile group: 'com.goldmansachs', name: 'gs-collections', version: '6.1.0' testCompile group: 'com.goldmansachs', name: 'gs-collections-testutils', version: '6.1.0' compile group: 'com.goldmansachs', name: 'gs-collections-forkjoin', version: '6.1.0' } 52 GS Collectionsを始めてみよう TECHNOLOGY DIVISION 様々なコンテナの初期化の例 List, Set, Bag, Map, Multimap他、com.gs.collections.imple.factory下に多数存在 MutableSet<String> mutableSet = Sets.mutable.of("One", "One", "Two", "Three"); MutableBag<String> mutableBag = Bags.mutable.of("One", "One", "Two", "Three"); MutableMap<String, String> mutableMap = Maps.mutable.of("key1", "value1", "key2", "value2", "key3", "value3"); Multimap<String, String> multimapWithList = Multimaps.mutable.list.with("key1", "value1-1", "key1", "value1-2", "key2", "value2-1"); それぞれMutable, Immutableのファクトリが存在 MutableList<String> mutableList = Lists.mutable.of("One", "Two", "Three"); //Equivalent of FastList.newList("One", "Two", "Three") MutableList<String> mutableListWithBlank = Lists.mutable.with(); //You have a choice to use of() or with(), whichever you like. ImmutableList<String> immutableList= Lists.immutable.of("One", "Two", "Three"); List, Set, Map等のコンテナにはメモリ効率のよいFixedSize用のファクトリも存在 FixedSizeList<String> fixedSizeList= Lists.fixedSize.of("One", "Two"); //Memory efficient if we know the size beforehand 53 GS Collectionsを始めてみよう 即時実行のイテレーションパターン Interval list = Interval.fromTo(0, 100); list.detect(each -> each > 50); list.select(each -> each > 50); list.reject(each -> each > 50); TECHNOLOGY DIVISION 遅延実行のイテレーションパターン list.asLazy() .select(each -> each > 95) .collect(Object::toString) .makeString("[", ",", "]"); => [96,97,98,99,100] list.anySatisfy(each -> each > 50); list.allSatisfy(each -> each > 50); list.collect(Object::toString); list.injectInto(3, Integer::sum); 54 GS Collectionsを始めてみよう TECHNOLOGY DIVISION GS Collectionsにはこ こでは紹介しきれない 様々な機能が数多くあ ります! まずはRichIterable上 に存在する様々なAPI を堪能し、JavaDoc、 パッケージやインター フェースを探索してみ てください。 55 セッション - Parallel-lazy Performance: Java 8 vs Scala vs GS Collections TECHNOLOGY DIVISION http://www.infoq.com/presentations/java-streams-scala-parallel-collections QCon NY発表後に注目を浴びページビュートップに 出典:http://www.infoq.com/news/2014/12/qcon-ny-2015 より引用 (オレンジの囲みは発表者による) 56 研修資料 - GS Collections Kata TECHNOLOGY DIVISION https://github.com/goldmansachs/gs-collections-kata - ユニットテストをひとつずつパスしていくTDD型トレーニングマテリアル - ゴールドマン・サックスの研修にも使用 - GS Collectionsの使用法を学習するのに最適 57 セッション - Scala Collections Performance TECHNOLOGY DIVISION http://www.goldmansachs.com/gs-collections/presentations/2015-0317%20Scala%20Days%202015%20-%20Scala%20Collections%20Performance.pptx - Scala Days San Francisco 2015 - Scalaのコレクション実装のパフォーマンス分析 - GS Collectionsで得られた知見から、Scalaのコレクション 実装のパフォーマンス改善案を提言 58 Resources TECHNOLOGY DIVISION • GS Collections on GitHub https://github.com/goldmansachs/gs-collections https://github.com/goldmansachs/gs-collections/wiki https://github.com/goldmansachs/gs-collections-kata • GS Collections Memory Benchmark http://www.goldmansachs.com/gscollections/presentations/GSC_Memory_Tests.pdf • 実例で学ぶGS Collections (InfoQ 日本語翻訳記事) http://www.infoq.com/jp/articles/GS-Collections-by-Example-1 http://www.infoq.com/jp/articles/GS-Collections-by-Example-2 Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners. 59 TECHNOLOGY DIVISION Learn more at GS.com/Engineering © 2015 Goldman Sachs. This presentation should not be relied upon or considered investment advice. Goldman Sachs does not warrant or guarantee to anyone the accuracy, completeness or efficacy of this presentation, and recipients should not rely on it except at their own risk. This presentation may not be forwarded or disclosed except with this disclaimer intact. 60
© Copyright 2025 ExpyDoc