This library is a carefully designed and efficient extension of the Java Collections Framework with primitive specializations and more.

Overview

With a few exceptions, the library API consists of three types of classes:
  1. Interface hierarchy, which generally repeats and extends Java Collection Framework (JCF) interface and class hierarchy. Also, all interfaces are populated for all 7 primitive type specializations, and "object specialization" for API symmetry (see primitive specializations naming convention for more information on this). For example, in JCF {@link java.util.HashSet} class extends {@link java.util.Set} extends {@link java.util.Collection}. In this library {@link com.koloboke.collect.set.hash.HashCharSet} interface extends {@link com.koloboke.collect.set.CharSet}, which extends {@link java.util.Set} and {@link com.koloboke.collect.CharCollection}, which extends {@link java.util.Collection} and {@link com.koloboke.collect.Container}.

    Also, the {@code com.koloboke.function} package polyfills {@link java.util.function} functional interface set with the rest specializations ({@link java.util.function} package defines only some specializations for {@code int}, {@code long} and {@code double} types.)

  2. Factory interfaces, each of them defines several dozens of factory methods which construct corresponding container interface instances. You can construct instances of three mutability profiles. Factory interfaces also form a hierarchy, which follow container's interface hierarchy, to ease making common configurations. For example, you can define a method which accept a {@link com.koloboke.collect.hash.HashContainerFactory} to configure all factories which produce hash sets and maps in the application.

    Note that all factories in the library are immutable, on changing any configuration a new copy of the factory is returned, with the target configuration changed.

  3. Final uninstantiable classes for each "leaf" container and the corresponding factory interface, which define the same set of static methods as the factory interface does, all of them just delegate to the default factory instance, obtained via {@link java.util.ServiceLoader}. This default factory instance is returned by {@code getDefaultFactory()} static method in each static factory method holder class. These classes have a name of container interface plus {@code -s} suffix, for example, {@link com.koloboke.collect.map.hash.HashIntShortMaps} define static factory methods which return {@link com.koloboke.collect.map.hash.HashIntShortMap} instances, delegating to the default {@link com.koloboke.collect.map.hash.HashIntShortMapFactory} implementation.

Table of equivalents of JDK collection patterns

JDK The closest equivalent from this library The recommended equivalent from this library
 {@code new HashMap();}
 {@code HashObjObjMaps.getDefaultFactory()

     .withNullKeyAllowed(true)

     .newMutableMap();}
 {@code HashObjObjMaps.newUpdatableMap();}
 {@code // unclear how "capacity" (100)

 // is translated to size. 50? 75? 100?

 new HashMap(100);}
 {@code HashIntObjMaps.newMutableMap(

     (int) (100 * HashConfig.getDefault().getTargetLoad()));}
 {@code // 50 is expected _size_

 HashIntObjMaps.newUpdatableMap(50);}
 {@code new IdentityHashMap(map);}
 {@code HashObjDoubleMaps.getDefaultFactory()

     // these loads used in IdentityHashMap internally

     .withHashConfig(HashConfig.fromLoads(1./3., 2./3., 2./3.))

     .withNullKeyAllowed(true)

     .withKeyEquivalence(Equivalence.identity())

     .newMutableMap(map);}
 {@code HashObjDoubleMaps.getDefaultFactory()

     .withKeyEquivalence(Equivalence.identity())

     .newImmutableMap(map);}
 {@code Collections.unmodifiableSet(new HashSet<>() {{

     add("Summer");

     add("Autumn");

     add("Winter");

     add("Spring");

 }});}
 {@code HashObjSets.newImmutableSetOf("Summer", "Autumn", "Winter", "Spring");}

Mutability profiles

Container factories allow to construct containers with several distinct degrees of mutability. It is useful for two main purposes: first, to defend your data from occasional (or intentional) container misuse, i. e. the same purpose for what {@code Collections.unmodifiable*} methods exist. Second, containers of lesser mutability are implemented in more efficient manner, whenever possible. So using immutable collections when applicable could improve your application's performance a bit.

Immutable

Any operations that change the conceptual container state, e. g. insertions and removals, as well as that could touch internal representation, e. g. {@link com.koloboke.collect.Container#shrink()}, are disallowed. Other ones are allowed.

Updatable

Everything is allowed, except removals of individual elements (entries), typically names of these operations include "remove" or "retain" verb. Emphasis on "individual" means that {@link com.koloboke.collect.Container#clear()} (i. e. removal all elements or entries at once) is allowed.

Think about updatable containers as "non-decreasing", which could be "reset" from time to time by calling {@code clear()}.

In real practice individual removals are rarely needed, so most of the time you should use updatable containers rather than fully mutable ones. On the other hand, prohibition of removals permits faster implementation of {@linkplain com.koloboke.collect.hash.HashContainer hash containers} and iterators over many data structures.

Mutable

All operations are allowed.

In Koloboke Compile, the {@link com.koloboke.compile.mutability.Updatable @Updatable} and {@link com.koloboke.compile.mutability.Mutable @Mutable} annotations specify that Koloboke Compile should generate an updatable or a mutable implementation of a container interface, respectively.

{@code Collection} mutability matrix

This matrix shows which methods in the {@link com.koloboke.collect.ObjCollection}, {@link com.koloboke.collect.IntCollection}, etc. interfaces are supported by collections with different mutability profiles.
Method \ MutabilityMutableUpdatableImmutable
{@link java.util.Collection#size() size()}
{@link com.koloboke.collect.Container#sizeAsLong() sizeAsLong()}
{@link java.util.Collection#isEmpty() isEmpty()}
{@code contains(e)}
{@link java.util.Collection#containsAll(java.util.Collection) containsAll(c)}
{@link java.util.Collection#toArray() toArray()}
{@link java.util.Collection#toArray(Object[]) toArray(array)}
{@link java.util.Collection#iterator() iterator()} ✓, except {@link java.util.Iterator#remove()}
{@code cursor()} ✓, except {@link com.koloboke.collect.Cursor#remove()}
{@link com.koloboke.collect.Container#ensureCapacity(long) ensureCapacity(minSize)} -
{@link com.koloboke.collect.Container#shrink() shrink()} -
{@link java.util.Collection#clear() clear()} -
{@code add(e)} -
{@link java.util.Collection#addAll(java.util.Collection) addAll(c)} -
{@code remove(e)} --
{@link java.util.Collection#removeAll(java.util.Collection) removeAll(c)} --
{@link java.util.Collection#retainAll(java.util.Collection) retainAll(c)} --
{@code removeIf(filter)}--

{@code Map} mutability matrix

This matrix shows which methods in the {@link com.koloboke.collect.map.ObjObjMap}, {@link com.koloboke.collect.map.ObjIntMap}, {@link com.koloboke.collect.map.LongObjMap}, etc. interfaces are supported by maps with different mutability profiles.
Method \ MutabilityMutableUpdatableImmutable
{@link java.util.Map#size() size()}
{@link com.koloboke.collect.Container#sizeAsLong() sizeAsLong()}
{@link java.util.Map#isEmpty() isEmpty()}
{@code containsKey(key)}
{@code containsValue(value)}
{@code get(key)}
{@code getOrDefault(key, defaultValue)}
{@code forEach(action)}
{@code cursor()} ✓, except {@link com.koloboke.collect.Cursor#remove()}
{@link java.util.Map#keySet() keySet()} ✓, same mutability applied to the returned {@code Set}
{@link java.util.Map#values() values()} ✓, same mutability applied to the returned {@code Collection}
{@link java.util.Map#entrySet() entrySet()} ✓, same mutability applied to the returned {@code Set},
if Immutable - additionally, {@link java.util.Map.Entry#setValue(java.lang.Object) Map.Entry.setValue(value)} not supported
{@link com.koloboke.collect.Container#ensureCapacity(long) ensureCapacity(minSize)} -
{@link com.koloboke.collect.Container#shrink() shrink()} -
{@link java.util.Map#clear() clear()} -
{@code put(key, value)} -
{@code putIfAbsent(key, value)} -
{@code computeIfAbsent(key, mappingFunction)} -
{@code replace(key, value)} -
{@code replace(key, oldValue, newValue)} -
{@link java.util.Map#putAll(java.util.Map) putAll(m)} -
{@code replaceAll(function)} -
{@code compute(key, remappingFunction)} ✓, except removing entry on returning {@code null} from the {@code remappingFunction}-
{@code computeIfPresent(key, remappingFunction)} ✓, except removing entry on returning {@code null} from the {@code remappingFunction}-
{@code merge(key, value, remappingFunction)} ✓, except removing entry on returning {@code null} from the {@code remappingFunction}-
{@code remove(key)} --
{@code remove(key, value)} --
{@code removeIf(filter)} --

See other matrices for information if the concrete method is supported by the given mutability profile: {@code Container}.

Comparison of iteration ways

In addition to the standard ways — {@linkplain java.util.Iterator iterators} and {@code forEach()}-like methods which accept closures, the library supplies {@linkplain com.koloboke.collect.Cursor cursors} for every container .
Overview comparison of the ways to iterate over containers within the library
{@link java.util.Iterator} {@link com.koloboke.collect.Cursor} {@code forEach()} {@code forEachWhile()} {@code removeIf()}
Available for {@link java.util.Collection} sub-interfaces in the library Yes
Available for {@link java.util.Map} sub-interfaces in the library Yes
Coding convenience High, if elements aren't removed and generic version of {@link java.util.Iterator#next()} method is used, Java "for-each" syntax is applicable. Medium otherwise. Medium High, lambda syntax
Supports early break from the iteration Yes, by simple break from the loop Yes, by simple break from the loop No Yes, by returning {@code false} No
Supports remove of iterated elements (entries) Yes, by {@link java.util.Iterator#remove()} Yes, by {@link com.koloboke.collect.Cursor#remove()} No No Yes, by returning {@code true}
Performance, iteration over {@link java.util.Collection} High, if specialized version of {@link java.util.Iterator#next()} method is used. Medium otherwise, because every element is boxed. Very high
Performance, iteration over {@link java.util.Map} Medium, {@link java.util.Map.Entry} objects are allocated Very high

Compatibility with Java Collections Framework

All containers from the library have least possible (given initial design decisions) semantic difference with the most widely used implementation from JCF of the same parental interface. For example, {@link com.koloboke.collect.set.hash.HashCharSet} extends {@code java.util.Set}, and made as similar as possible to {@code java.util.HashSet}, which extends the same interface. Non-obvious things, made compatible with JCF in the library:

Known incompatibilities

Primitive specializations naming convention

  1. The name of the specialized class is the name of the "basic" class with prefix equal to capitalized Java primitive type name of the element specialization, or key specialization type name followed value specialization type name without anything in between. Examples: {@link com.koloboke.collect.CharCollection} extends {@link java.util.Collection}, {@link com.koloboke.collect.map.IntFloatMap} extends {@link java.util.Map}. There are also classes with {@code Obj-} prefix, they bring API additions to collections of objects, if there are no additions for the class or interface, {@code Obj-} "specializations" are present anyway, for global API symmetry.
  2. If the specialized method has arguments of the specialized type, it has the same name as the non-specialized, thanks to Java's method overloading feature. There could be compilation issues in the client code, due to ambiguity, if there are several specialized arguments and some of them are boxed. You should "cast" them to unboxed values. For example:
     {@code
    
     IntIntMap map = HashIntIntMaps.newUpdatableMap();
    
     Integer key = 1;
    
     map.put(key, 2); // ambiguous method call
    
     map.put((int) key, 2); // correct}
    There is one exception from this rule: {@link com.koloboke.collect.ByteCollection#removeByte(byte)} is a specialized version of {@link java.util.Collection#remove(Object)}, but have a different name (the same for {@link com.koloboke.collect.LongCollection}, {@link com.koloboke.collect.FloatCollection}, etc. for symmetry). This is because {@code remove(int)} in {@link com.koloboke.collect.IntCollection} will conflict with {@link java.util.List#remove(int)} method in {@code IntList} (not implemented yet, however).
  3. If the specialized method doesn't have arguments of the specialized types, but return the specialized type, capitalized primitive type name, optionally preceded by {@code -As-} infix, is added to the original method name. Examples:
    • {@link com.koloboke.collect.map.ObjCharMap#getChar(Object)}
    • {@link com.koloboke.collect.map.ObjCharMap#removeAsChar(Object)}
    • {@link com.koloboke.collect.CharIterator#nextChar()}
    • {@link com.koloboke.function.ToCharFunction#applyAsChar(Object)}
  4. Method {@link com.koloboke.collect.DoubleCollection#toDoubleArray()}, and others similar, is exceptional from those rules and have special name.

API additions beyond JCF interfaces

The library brings some extra functionality beyond implementing JCF interfaces and generating primitive specializations for each interface and method.

The concept of pluggable element (key, value) equivalences

JCF interfaces and implementations rely on Java built-in equality and hash code infrastructure: {@link java.lang.Object#equals(Object)} and {@link java.lang.Object#hashCode()}. Container factories in the library allow to configure {@linkplain com.koloboke.collect.Equivalence equivalences} for elements, keys and values. This allows to implement some functionality very easy, without defining new subclasses of the existing collections implementations. See the documentation to {@link com.koloboke.collect.ObjCollection#equivalence()}, {@link com.koloboke.collect.map.ObjObjMap#keyEquivalence()} and {@link com.koloboke.collect.map.ObjObjMap#valueEquivalence()} methods for more information.

Functional additions to {@link java.util.Collection} interface:

Of cause, there are appropriate specialized methods in the {@link java.util.Collection} interface primitive specializations: {@link com.koloboke.collect.ByteCollection}, {@link com.koloboke.collect.CharCollection}, etc.

Functional additions to {@link java.util.Map} interface:

Additional control over hash table behaviour

The single thing in the API of JDK hash table implementations, including {@link java.util.HashMap}, {@link java.util.LinkedHashMap}, {@link java.util.HashSet} and {@link java.util.WeakHashMap}, that allows to control it's memory footprint and performance characteristics, is {@code loadFactor} constructor argument. {@link java.util.IdentityHashMap} don't have even this one. This library allows to tune hash tables very precisely via a bunch of per-instance methods and factory configurations. See the documentation to {@link com.koloboke.collect.hash.HashContainer} and {@link com.koloboke.collect.hash.HashConfig} classes for more information.