slides.md

March 30, 2026 · View on GitHub

Diagram

API Design: Eclipse Collections

  • space: next page
  • down arrow: next page in the current section
  • right arrow: next section

Agenda

  • What is Eclipse Collections?
  • Design Goals
  • Symmetric Sympathy
  • Strategies for evolving a Collections API
  • Memory Efficiency
  • Performance Optimization Strategies
  • New Features in Eclipse Collections 9.x
  • How did we get here?
  • The Past and the Future

What is Eclipse Collections?

  • An open source Java Collections Framework
    • Functional, Fluent, Friendly and Fun
    • A mature library with an evolutionary design
    • 18+ years of development
  • A Brief History
    • Caramel (2005)
      • Internal library developed at Goldman Sachs
    • GS Collections (2012)
      • First open source project from Goldman Sachs
    • Eclipse Collections (2015)
      • Moved to the Eclipse Foundation

Project Information

Eclipse Collections Evangelism

  • Conferences
    • QCon New York, Devnexus, JavaOne, JavaDay Tokyo, Devoxx US, JCrete, EclipseCon, EclipseCon Europe, ECOOP, ScalaDays, GOTO Chicago, JVM Language Summit, GIDS
  • Java User Group Meetups
    • Americas - NY Java SIG, NY JUG, NJ Java SIG, Pittsburgh JUG, Utah JUG, Delaware JUG
    • Europe - London Java Community, Dublin JUG, Belfast JUG, Edinburgh JUG, Manchester JC, West Midlands JUG
    • Asia - JJUG CCC, Bengaluru JUG, Chennai JUG, Delhi-NCR JUG, Hyderabad JUG, Kerala JUG

Design Goals

  • Rich “Functional, fluent and fun” API
    • Eager, Lazy, Serial, Parallel
  • Memory efficient data structures
  • Optimized algorithms
  • Java Interoperability
    • Java container types, Streams
  • Immutability
  • Missing Types
    • Intervals, Stacks, Bags, Multimaps, BiMaps
  • Primitive Containers

Eclipse Collections Today

  • Open for contribution at the Eclipse Foundation
  • Symmetry drives the design
  • Interoperability w/ Java 8 and Java 9

Diagram

Symmetric Sympathy

Symmetry: Protocols and Interfaces

Diagram

Symmetric Sympathy - Part 1

APIEagerLazyParallelEager(p)Lazy(p)
selectco*Lazyco*co*Lazy
rejectco*Lazyco*co*Lazy
collectco*Lazyco*co*Lazy
detectTTTPrimitivePrimitive
injectIntoRRN/ARR
any/all/nonebooleanbooleanbooleanbooleanboolean
toSetmSetmSetmSetm(p)Setm(p)Set
toBagmBagmBagmSetm(p)Bagm(p)Bag
toListmListmListmListm(p)Listm(p)List

co*=covariant, m=mutable, p=primitive

Symmetric Sympathy - Part 2

APIEagerLazyParallelEager(p)Lazy(p)
flatCollectco*Lazyco*RLazy
groupByco*Multimapco*N/AN/A
partitionco*RIN/AN/AN/A
chunkRIRIN/Aco*Lazy
zipco*LazyN/Aco*N/A
makeStringStringStringStringStringString
appendStringvoidvoidvoidvoidvoid
countintintintintint
min/maxTTT(p)(p)

co*=covariant, m=mutable, p=primitive

API Evolution Strategies

StrategyCostComplexity
Static UtilityLowLow
Default methodsLow - MedMedium
Lazy APIMed - HighMedium
Parallel UtilityMediumMed - High
Eager APIHighMedium
New Data Type (Object)HighMed - High
Parallel Lazy APIHighHigh
New Data Type (Primitive)HighHigh

Memory Efficiency

Memory Efficiency - Sets

Diagram

Memory Efficiency - Immutable

Diagram

Memory Efficiency - Primitives

Diagram

Performance Optimization Strategies

Object vs. Primitive Performance

  • Use Cases: Filter, Map, Sum, Filter/Map/Sum
  • Data: 1 Million Ints
  • Processor Name: 12-Core Intel Xeon E5
  • Processor Speed: 2.7 GHz
  • Number of Processors: 1
  • Total Number of Cores: 12
  • L2 Cache (per Core): 256 KB
  • L3 Cache: 30 MB
  • Memory: 64 GB
  • Unit of Measure: Operations per second

Object vs. Primitive - Filter Evens

Diagram

  • Use case: Filter evens into new List
  • Larger numbers are better

Object vs. Primitive - Map x 2

Diagram

  • Use case: Multiply each x 2 and return result in new List
  • Larger numbers are better

Object vs. Primitive - Sum

Diagram

  • Use case: Sum all of the numbers
  • Larger numbers are better

Object vs. Primitive - Filter, Map, Sum

Diagram

  • Use case: Filter evens, Map x 2, Sum
  • Larger numbers are better

Eclipse Collections 10.4.0

  • Mandatory fixes to make the library compatible with JDK-15.

Eclipse Collections 10.3.0 - (1)

Eclipse Collections 10.3.0 - (2)

Eclipse Collections 10.2.0

  • Exposed the allocateTable method as protected in Primitive Maps and Primitive Sets.

Eclipse Collections 10.1.0

Eclipse Collections 10.0.0 - (1)

Eclipse Collections 10.0.0 - (2)

  • UnifiedSetWithHashingStrategy
  • PrimitiveIterable
  • PrimitiveList
  • PrimitiveMap
  • LazyIterate
  • Added ability to create ObjectPrimitiveMap, PrimitiveObjectMap, PrimitivePrimitiveMap from Iterable.
  • Implemented factory methods to convert Iterable to PrimitiveList, PrimitiveSet, PrimitiveBag, PrimitiveStack.
  • Implemented fromStream(Stream) on Mutable Collection Factories and Immutable Collection Factories.
  • Implemented ofInitialCapacity() and withInitialCapacity() to Primitive Map Factories and in HashingStrategySets.

Eclipse Collections 9.2

Eclipse Collections 9.1

Eclipse Collections 9.0

CountBy

// Eclipse Collections 7.x - 8.x

MutableBag<String> countsOld =
        this.company.getCustomers()
                .asLazy().collect(Customer::getCity).toBag();


// Eclipse Collections 9.x

MutableBag<String> countsNew =
        this.company.getCustomers()
                .countBy(Customer::getCity);

DistinctBy

// Eclipse Collections 7.x - 8.x

MutableList<Customer> distinctOld =
        this.company.getCustomers()
                .distinct(HashingStrategies
                              .fromFunction(Customer::getCity));


// Eclipse Collections 9.x

MutableList<Customer> distinctNew =
        this.company.getCustomers()
                .distinctBy(Customer::getCity);

Factories for Primitive Streams

// Eclipse Collections 7.x - 8.x

MutableIntList listOld =
        IntStream.rangeClosed(1, 100)
                .collect(IntLists.mutable::empty,
                         MutableIntList::add,
                         MutableIntList::withAll);


// Eclipse Collections 9.x

MutableIntList listNew =
        IntLists.mutable.withAll(
                IntStream.rangeClosed(1, 100));   

Stream on Immutable Collections

// Eclipse Collections 7.x - 8.x

boolean result =
        Lists.immutable.with(1, 2, 3)
                .castToList()
                .stream()
                .anyMatch(i -> i % 2 == 0);


// Eclipse Collections 9.x

boolean result =
        Lists.immutable.with(1, 2, 3)
                .stream()
                .anyMatch(i -> i % 2 == 0);

ImmutableList API Design

Diagram

Structural and Contractual Immutability

How did we get here?

  • Once upon a time...

An old dude who knows Smalltalk

  • Clipper / CA-Visual Objects (‘90 –‘95)
    • Arrays
    • Code Blocks
      • { |each| expression }
  • IBM VisualAge Smalltalk (‘94 – ‘00)
    • Collections
    • Code Blocks
      • [ :each | expression ]

Smalltalk-80 Collection Hierarchy

Diagram

"It's turtles all the way down"

Smalltalk Collection Factories

|set list bag array string map interval|

set := Set with: 1 with: 2 with: 3 with: 4.
list := OrderedCollection with: 1 with: 2 with: 3 with: 4. 
bag := Bag with: 1 with: 2 with: 3 with: 4.

array := #( 1 2 3 4 ).

string := ‘The Quick Brown Fox jumps over a lazy dog.’.

map := Dictionary newFromPairs: #( 1 ‘1’ 2 ‘2’ 3 ‘3’ 4 ‘4’).

interval := 1 to: 4.

Smalltalk Protocols (Eager)

set select: [ :each | each odd ].         // Set(1 3)
list reject: [ :each | each even].        // OrderedCollection(1 3)
array collect: [ :each | each asString].  // #(‘1’ ‘2’ ‘3’ ‘4’)

string detect: [ :c | c isLowercase].     // $h
map inject: '' into: [ :r :s | r, s].     // ‘1234’

string anySatisfy: [ :c | c = $e].        // true
set allSatisfy: [ :each | each < 5 ].     // true
map noneSatisfy: [ :s | s isString ].     // false

interval asSet.                           // Set(1 2 3 4)
map asBag.                                // Bag(‘1’ ‘2’ ‘3’ ‘4’) 
list asArray.                             // #(1 2 3 4)

Smalltalk + Java = Eclipse Collections

Smalltalk InfluencedJava Influenced
SymmetryBasic Collection Types
Blocks (Lambdas)Primitive Collections
Internal IteratorsLazy Iterables
Protocol NamingParallelism
Eager APIsImmutability
Bag, IntervalMultimap, BiMap
Factory Methods

Designing API Names

  • Naming is hard
    • You can't make everyone happy
  • Choose a naming influence style
    • I chose Smalltalk as my influence
  • Find patterns of implementation and name them
    • Does your name communicate well?
    • Get opinions of others
  • Stick to your patterns and names - be consistent!

Primary API Comparison

Eclipse CollectionsJava 8 Streams
selectfilter
rejectfilter (!)
collectmap
flatCollectflatMap
detectfilter().findFirst().orElse(null)
detectIfNonefilter().findFirst().orElseGet()
detectOptionalfilter().findFirst()
countfilter().count()
injectIntoreduce
any
all
noneSatisfyany/all/noneMatch

Advanced API Comparison

Eclipse CollectionsJava 8 Streams
groupBycollect(Collectors.groupingBy)
countBycollect(Collectors.groupingBy(Collectors.counting))
aggregateBycollect(Collectors.groupingBy(Collectors.reducing))
minByOptionalmin(Comparator.comparing)
maxByOptionalmax(Comparator.comparing)
distinctdistinct.collect(Collectors.toList)
partitioncollect(Collectors.partitioningBy)
chunkN/A
zipN/A
makeStringcollect(Collectors.joining)

Eclipse Collections Factories

MutableSet<Integer> set = Sets.mutable.with(1, 2, 3, 4);
MutableList<Integer> list = Lists.mutable.with(1, 2, 3, 4); 
MutableBag<Integer> bag = Bags.mutable.with(1, 2, 3, 4);

MutableList<Integer> array = ArrayAdapter.adapt(1, 2, 3, 4);

CharAdapter string = 
    CharAdapter.adapt("The Quick Brown Fox jumps over a lazy dog.");

MutableMap<Integer, String> map := 
    Maps.mutable.with(1, "1", 2, "2", 3, "3", 4, "4").

Interval interval = Interval.oneTo(4);

Eclipse Collections (Eager)

set.select(each -> each % 2 == 1);         // Set(1 3)
list.reject(each -> each % 2 == 0);        // List(1 3)
array.collect(Object::toString);           // List("1" "2" "3" "4")

bag.detect(each -> each < 2);              // Integer.valueOf(1)
map.injectInto("", (r, s) -> r + s);       // "1234"

string.anySatisfy(c -> c == 'e');          // true
set.allSatisfy(each -> each < 5);          // true
map.noneSatisfy(String.class::isInstance); // false

interval.toSet();                          // Set(1 2 3 4)
map.toBag();                               // Bag("1" "2" "3" "4")
set.toList();                              // List(1 2 3 4)

Eclipse Collections (Lazy)

set.asLazy().select(each -> each % 2 == 1);    // Set(1 3)
list.asLazy().reject(each -> each % 2 == 0);   // List(1 3)
array.asLazy().collect(Object::toString);      // List("1" "2" "3" "4")

bag.asLazy().detect(each -> each < 2);         // Integer.valueOf(1)
map.asLazy().injectInto("", (r, s) -> r + s);  // "1234"

string.asLazy().anySatisfy(c -> c == 'e');          // true
set.asLazy().allSatisfy(each -> each < 5);          // true
map.asLazy().noneSatisfy(String.class::isInstance); // false

interval.asLazy().toSet();                     // Set(1 2 3 4)
map.asLazy().toBag();                          // Bag("1" "2" "3" "4")
set.asLazy().toList();                         // List(1 2 3 4)

The Past

  • Memory efficient Small Collections (2004)
  • Java 5 - Generics (2005)
  • Open Source GS Collections (2012)
  • Java 8 - Lambdas (2014)
  • Eclipse Collections (2015)

The Future

  • Modularization
  • Improve Symmetry
    • Object / Primitive
  • New Data Structures
    • More Primitive, Ordered, Trees, Persistent Structures, Off-heap
  • Continued Java integration and inter-op
  • Integrate with Eclipse Build Train

Appendix

Static Utility - Iterate (Eager)

Iterate.select(Iterable, Predicate);              // Collection
Iterate.reject(Iterable, Predicate);              // Collection
Iterate.collect(Iterable, Function);              // Collection

Iterate.detect(Iterable, Predicate);              // Object
Iterate.injectInto(Iterable, Object, Function2);  // Object

Iterate.anySatisfy(Iterable, Predicate);          // boolean
Iterate.allSatisfy(Iterable, Predicate);          // boolean
Iterate.noneSatisfy(Iterable, Predicate);         // boolean

Iterate.toArray(Iterable);                        // Object[]
Iterate.toMap(Iterable, Function, Function);      // Map

Static Utility - ListIterate (Eager)

ListIterate.select(List, Predicate);              // MutableList
ListIterate.reject(List, Predicate);              // MutableList
ListIterate.collect(List, Function);              // MutableList

ListIterate.detect(List, Predicate);              // Object
ListIterate.injectInto(List, Object, Function2);  // Object

ListIterate.anySatisfy(List, Predicate);          // boolean
ListIterate.allSatisfy(List, Predicate);          // boolean
ListIterate.noneSatisfy(List, Predicate);         // boolean

ListIterate.toArray(List);                        // Object[]
ListIterate.toMap(List, Function, Function);      // Map

Eclipse Collections Primitive (Eager)

set.select(each -> each % 2 == 1);         // IntSet(1 3)
list.reject(each -> each % 2 == 0);        // IntList(1 3)
bag.collect(Object::toString);             // Bag("1" "2" "3" "4")

string.detect(Character::isLowerCase);     // 'h'
map.injectInto("", (r, s) -> r + s);       // "1234"

string.anySatisfy(c -> c == 'e');          // true
set.allSatisfy(each -> each < 5);          // true
map.noneSatisfy(String.class::isInstance); // false

interval.toSet();                          // IntSet(1 2 3 4)
map.toBag();                               // IntBag(1 2 3 4)
set.toList();                              // IntList(1 2 3 4)

Eclipse Collections Primitive (Lazy)

set.asLazy().select(each -> each % 2 == 1);  // LazyIntIterable
list.asLazy().reject(each -> each % 2 == 0); // LazyIntIterable
bag.asLazy().collect(Object::toString);      // LazyIterable

string.asLazy().detect(Character::isLowerCase); // 'h'
map.asLazy().injectInto("", (r, s) -> r + s);   // "1234"

string.asLazy().anySatisfy(c -> c == 'e');          // true
set.asLazy().allSatisfy(each -> each < 5);          // true
map.asLazy().noneSatisfy(String.class::isInstance); // false

interval.asLazy().toSet();                    // IntSet(1 2 3 4)
map.asLazy().toBag();                         // IntBag(1 2 3 4)
set.asLazy().toList();                        // IntList(1 2 3 4)

Eclipse Collections (Streams)

set.stream().filter(each -> each % 2 == 1);     // Stream
list.stream().filter(each -> each % 2 != 0);    // Stream
array.stream().map(Object::toString);           // Stream

bag.stream().filter(each -> each < 2).findFirst(); // Optional(1)
map.stream().reduce("", (r, s) -> r + s);          // "1234"

string.chars().anyMatch(c -> c == 'e');            // true
set.stream().allMatch(each -> each < 5);           // true
map.stream().noneMatch(String.class::isInstance);  // false

interval.stream().collect(Collectors.toSet());  // Set(1 2 3 4)
map.stream().collect(Collectors2.toBag());      // Bag(1 2 3 4)
set.stream().collect(Collectors2.toList());     // List(1 2 3 4)