- Introduction
- Why I may want to use Koloboke Compile?
- Supported Java versions
- Build configuration
- Basic usage
- Implementation customizations
- Javadocs
- Advanced usage
- Best practices
- Achieving maximum performance
- Samples
- IDE and tools configuration
- Known issues
- Troubleshooting and asking questions
Koloboke Compile is a Java source code generator. It generates implementations for your
collection-like abstract classes or interfaces (currently only Map
-like and Set
-like).
Koloboke Compile runs inside javac
as a standard annotation processor. It reads your abstract
class or interface and infers what the implementation class should look like. It generates source
code, in your package, of a concrete implementation class which extends your class or implements
your interface.
Koloboke Compile is inspired by the Immutables, Google Auto and Dagger projects. If you are familiar with the Google Auto project, the phrase "Koloboke Compile is Auto for collections" will give you an immediate understanding of what Koloboke Compile is. You could read the excellent Immutables or Auto Value user guides to understand the concepts of compile-time source code generation libraries better, also maybe to start using these great projects.
You can use Koloboke Compile with java compiler from Java 7 platform or newer, however Koloboke
Compile generates sources that are compatible with with Java 6, so -source
and -target
versions
could be 1.6 or higher.
javac
and Eclipse Compiler for Java (ECJ) are supported.
To start using Koloboke Compile, you should just add the following dependencies in your Maven
pom.xml
:
<dependency>
<groupId>com.koloboke</groupId>
<artifactId>koloboke-compile</artifactId>
<version>0.5.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.koloboke</groupId>
<!-- `jdk6-7` instead of `jdk8` if you use Java 6 or 7 -->
<artifactId>koloboke-impl-common-jdk8</artifactId>
<version>1.0.0</version>
</dependency>
Or in your Gradle build script, you should first apply the propdeps
Gradle plugin to enable
provided
dependencies, and then configure the dependencies
block:
dependencies {
provided 'com.koloboke:koloboke-compile:0.5.1'
// `jdk6-7` instead of `jdk8` if you use Java 6 or 7
compile 'com.koloboke:koloboke-impl-common-jdk8:1.0.0'
}
The table of compatible versions:
koloboke-compile | koloboke-impl-common-jdk* |
|
---|---|---|
0.5-0.5.1 | 1.0.0 | |
Latest versions |
Class definition:
import com.koloboke.compile.KolobokeMap;
import java.util.Map;
@KolobokeMap
abstract class MyMap<K, V> implements Map<K, V> {
static <K, V> Map<K, V> withExpectedSize(int expectedSize) {
return new KolobokeMyMap<K, V>(expectedSize);
}
}
-
Annotate a Map-like class with
@KolobokeMap
-
Provide a static factory method, that instantiates a class called as your class with
Koloboke
prefix. A constructor of this class accepts anint
parameter, that means the expected size of the map or set to construct. -
Use it:
Map<String, String> tickers = MyMap.withExpectedSize(10); tickers.put("AAPL", "Apple, Inc.");
You can also construct KolobokeMyMap
directly from anywhere within the package of MyMap
, but it
is recommended to reference the generated class only from static factory methods, defined in the
implemented class itself. Note that static factories are recommended in Effective Java, Item 1.
The KolobokeMyMap
identifier is non-existent yet, and will be highlighted with red in your IDE,
but it won't be a project or an IDE build error! Koloboke Compile generates this class during
compilation. See also the IDE configuration section.
Implementation classes, generated by Koloboke Compile, are based on hash tables, not synchronized, and don't keep the insertion order. Currently there are no other options, but in future Koloboke Compile may learn how to generate tree-based, concurrent, ordered implementations, and any combinations of these.
Use Koloboke Compile for Set-like classes the same way as for Map-like classes:
import com.koloboke.compile.KolobokeSet;
import java.util.Set;
@KolobokeSet
abstract class MySet<E> implements Set<E> {
static <E> Set<E> withExpectedSize(int expectedSize) {
return new KolobokeMySet<E>(expectedSize);
}
}
The type to be implemented could also be an interface:
@KolobokeMap
interface MyMap<K, V> extends Map<K, V> { /*...*/ }
But this might be less convenient pre-Java 8, because static methods are not allowed in interfaces prior to Java 8, hence you cannot place static factory methods directly in the implemented interface.
Your class shouldn't be a generic class, like java.util.Map
. It could be specialized for your use
case:
@KolobokeMap
abstract class Tickers implements Map<String, String> {
static Tickers withExpectedSize(int expectedSize) {
return new KolobokeTickers(expectedSize);
}
}
Usage:
Tickers tickers = Tickers.withExpectedSize(10);
tickers.put("AAPL", "Apple, Inc.");
More examples of useful static factory methods, that you could write for your class:
@KolobokeMap
abstract class Tickers implements Map<String, String> {
static Tickers withExpectedSize(int expectedSize) {
return new KolobokeTickers(expectedSize);
}
static Tickers of() {
return withExpectedSize(10);
}
static Tickers fromMap(Map<String, String> m) {
Tickers tickers = withExpectedSize(m.size());
tickers.putAll(m);
return tickers;
}
}
Your class shouldn't extend java.util.Map
. You could declare just a few abstract methods
"from Map interface" that you need directly in your class, and Koloboke Compile will generate a
smaller class, that implements only those methods:
@KolobokeMap
abstract class Cache<K, V> {
static <K, V> Cache<K, V> withExpectedSize(int expectedSize) {
return new KolobokeCache<K, V>(expectedSize);
}
abstract V put(K key, V value);
abstract V get(K key);
abstract void clear();
abstract int size();
}
In fact Koloboke Compile doesn't care about superclasses or superinterfaces of the @KolobokeMap
-
or @KolobokeSet
-annotated class. It just looks at abstract methods in this class, and overrides
them in the generated implementation.
This is a recommended practice to declare just a few methods which you need instead of simply
extending/implementing the Map
or Set
interface.
Note that the get()
method in the Cache
class is defined with a generic K
parameter, while
the get()
method in the Map
interface has an Object
parameter. Koloboke Compile recognizes
"generified" get()
, remove()
, contains()
(in Set-like classes), containsKey()
, etc. methods,
all of which have Object
parameters in the Java Collections Framework. Koloboke Compile allows
both versions ("raw" and "generified") of get()
, remove()
etc. methods, but if you declare these
methods on your own, it is recommended to stick to "generified" versions, because it improves type
safety of usage of your class.
Koloboke Compile supports not just java.util.Map
and java.util.Set
interfaces, but their
extended versions from the Koloboke Collections API, i. e. HashObjObjMap
and HashObjSet
.
They provide many useful API additions beyond the
standard interfaces from the Java Collections Framework:
@KolobokeMap
abstract class ExtendedMap<V> implements HashObjObjMap<String, V> {
static <V> ExtendedMap<V> withExpectedSize(int expectedSize) {
return new KolobokeExtendedMap<V>(expectedSize);
}
}
Usage:
ExtendedMap<String> map = ExtendedMap.withExpectedSize(10);
assertTrue(map.ensureCapacity(1000));
assertTrue(map.shrink());
To make Koloboke Compile to generate useful methods from the Koloboke Collections API, you are not obligated to extend/implement the whole interfaces from the Koloboke Collections API. Again, you should just cherry-pick and declare methods that you need:
@KolobokeMap
abstract class ExtendedMap<V> {
static <V> ExtendedMap<V> withExpectedSize(int expectedSize) {
return new KolobokeExtendedMap<V>(expectedSize);
}
abstract V get(String key);
abstract V put(String key, V value);
abstract int size();
// java.util.function.BiConsumer if using Java 8+,
// com.koloboke.function.BiConsumer if using Java 6 or 7
abstract void forEach(BiConsumer<? super String, ? super V> action);
// java.util.function.BiPredicate if using Java 8+,
// com.koloboke.function.BiPredicate if using Java 6 or 7
abstract boolean removeIf(BiPredicate<? super String, ? super V> predicate);
abstract boolean shrink();
}
If you specialize Map's key and/or value type to a primitive wrapper class on the declaration site, Koloboke Compile will automatically generate an implementation which internally stores keys and/or values in primitive arrays:
@KolobokeMap
abstract class MyIntLongMap implements Map<Integer, Long> {
static Map<Integer, Long> withExpectedSize(int expectedSize) {
return new KolobokeMyIntLongMap(expectedSize);
}
}
Example:
Map<Integer, Long> map = MyIntLongMap.withExpectedSize(10);
// Throws NullPointerException,
// because cannot store null as a primitive long value:
map.put(1, null);
Koloboke Compile (as well as the Koloboke Collections API) supports all Java numeric primitive
types: byte
, char
, short
, int
, long
, float
, double
, as both keys and values. But it
doesn't support primitive boolean
keys and values.
You can specialize most frequently used methods to avoid boxing/unboxing, but leave
Map<Integer, Long>
as a superinterface for interoperability with other Java code:
@KolobokeMap
abstract class MyIntLongMap implements Map<Integer, Long> {
static Map<Integer, Long> withExpectedSize(int expectedSize) {
return new KolobokeMyIntLongMap(expectedSize);
}
abstract long put(int key, long value);
abstract long get(int key);
}
Or declare a minimalistic interface with just a few specialized methods that you need (this is a recommended way):
@KolobokeMap
abstract class MyIntLongMap {
static MyIntLongMap withExpectedSize(int expectedSize) {
return new KolobokeMyIntLongMap(expectedSize);
}
abstract long put(int key, long value);
abstract long get(int key);
abstract int size();
}
Just like with reference key and value types, you can extend interfaces from the Koloboke Collections API, or just cherry-pick some methods from them and declare in these methods in your class or interface directly, without bounding with the Koloboke Collections API:
Key type | Value type | Prototyping interface from the Koloboke Collections API |
---|---|---|
A reference type | A reference type |
HashObjObjMap<KeyType, ValueType>
|
A reference type | A numeric primitive type | HashObjYyyMap<KeyType> , where Yyy is a capitalized name of the
value type, e. g.
HashObjDoubleMap<KeyType> if the value type is double |
A numeric primitive type | A reference type | HashXxxObjMap<ValueType> , where Xxx is a capitalized name of
the key type, e. g.
HashLongObjMap<ValueType> if the key type is long |
A numeric primitive type | A numeric primitive type | HashXxxYyyMap , where Xxx is a capitalized name of the key type and
Yyy is a capitalized name of the value type, e. g.
HashLongDoubleMap if the key type is long and the value type is
double |
Map
's methods, where key or value type (or both) are specialized to primitive usually have the
names, except when the key type is still a reference type (a type variable, or a concrete class
which is not a primitive wrapper class), and the value type is a numeric primitive type, the 2nd row
in the table above. In this case, the specialized version of the get()
method should be called
getYyy()
, where Yyy
is a capitalized name of the value type, and the specialized version of the
remove(Object)
method should be called removeAsYyy()
. For example:
@KolobokeMap
abstract class StringIntMap {
static StringIntMap withExpectedSize(int expectedSize) {
return new KolobokeStringIntMap(expectedSize);
}
abstract int put(String key, int value);
abstract int getInt(String key);
abstract int removeAsInt(String key);
}
You will also see this if you look at the method list of the ObjDoubleMap
interface.
This section describes ways to make Koloboke Compile to generate a different implementation for the same annotated class or interface, using special annotations.
Unless otherwise stated, customization annotations could be applied to any types, suitable for
Koloboke Compile implementation: @KolobokeMap
- or @KolobokeSet
-annotated, with either
reference or a numeric key and value types. If if doubt, see Javadocs of the corresponding annotation. More
than one customization could be applied to the same type, there are no restrictions on how they
could be combined.
Annotate a Map- or a Set-like class or interface with @Updatable
to
make Koloboke Compile to generate implementation which throws UnsupportedOperationException
in
methods like remove()
, removeAll()
, retainAll()
, removeIf()
, etc. The only operation which
removes elements or entries from the set or map is clear()
:
import com.koloboke.compile.KolobokeMap;
import com.koloboke.compile.mutability.Updatable;
@KolobokeMap
@Updatable
interface MyMap<K, V> extends Map<K, V> { /*...*/ }
This is not only recommended in Effective Java, Item 15, but also often allows Koloboke Compile to generate a faster optimized implementation.
By default, Koloboke Compile generates map and set implementations that both disallow the null
key to be inserted and even queried (like map.get(null)
), always throwing NullPointerException
.
If you want to allow the null
key to be inserted into maps or sets, annotate the implemented type
with @NullKeyAllowed
:
import com.koloboke.compile.KolobokeMap;
import com.koloboke.compile.NullKeyAllowed;
@KolobokeMap
@NullKeyAllowed
interface MyMap<K, V> extends Map<K, V> {
static <K, V> Map<K, V> withExpectedSize(int expectedSize) {
return new KolobokeMyMap<K, V>(expectedSize);
}
}
Usage:
Map<String, String> map = MyMap.withExpectedSize(10);
map.put(null, "foo");
map.get(null);
@NullKeyAllowed
could be applied only to a type with a key type that is not a numeric primitive
type or a wrapper class of a numeric primitive type (that is actually the same
thing for Koloboke Compile).
Actually Koloboke Compile-generated classes (those that start with Koloboke-
prefix) has two
constructors: the first (only this one is used in all examples above in this tutorial) accepts a
single int
argument, that means the expected size of the map or set to construct. The second
constructor accepts HashConfig
as the
first argument and int expectedSize
(with the same meaning as in the first constructor) as the
second argument. The first constructor implicitly uses HashConfig.getDefault()
as the HashConfig
for the map or set to construct. Koloboke's hash configuration model is
explained in the HashConfig
Javadoc, it's
more complex, than simple "load factor", used in JDK hash table-based classes like HashMap
. But
in "load factor terms", the default "load factor" in Koloboke is 0.6666... If you want map or set
backing hash table to be sparse, you can use code like this:
import com.koloboke.collect.hash.HashConfig;
import com.koloboke.compile.KolobokeMap;
@KolobokeMap
interface MyMap<K, V> extends Map<K, V> {
static <K, V> Map<K, V> sparseWithExpectedSize(int expectedSize) {
return new KolobokeMyMap<K, V>(HashConfig.fromLoads(0.25, 0.375, 0.5), expectedSize);
}
}
The default hash table algorithm is linear probing
it allows only HashConfig
s with the growth factor of 2.0. If you need a different growth factory,
you have to annotate the implemented type with @QuadraticHashing
:
import com.koloboke.collect.hash.HashConfig;
import com.koloboke.compile.KolobokeMap;
import com.koloboke.compile.hash.algo.openaddressing.QuadraticHashing;
@KolobokeMap
@QuadraticHashing
abstract class QuadraticHashingMap {
static QuadraticHashingMap withExpectedSize(int expectedSize) {
return new KolobokeQuadraticHashingMap(
HashConfig.getDefault().withGrowthFactor(1.5), expectedSize);
}
abstract String get(int key);
abstract String put(int key, String value);
}
You can browse all available hash algorithms in the documentation to the
com.koloboke.compile.hash.algo.openaddressing
package.
Please read @KolobokeMap
or
@KolobokeSet
for formal and
comprehensive information about requirements to annotated classes or interfaces, specifics about
the generated implementations, available implementation customizations, etc.
If a Set-like or a Map-like type has a reference key type (a type variable like K
, or a concrete
reference type like String
), by default Koloboke Compile generates implementation which uses
built-in Java equality (via Object.equals()
and hashCode()
methods) to compare keys. To modify
the key equivalence strategy, you have to annotate the implemented type with
@CustomKeyEquivalence
and
provide two methods, boolean keyEquals(KeyType queriedKey, KeyType keyInContainer)
and
int keyHashCode(KeyType key)
. Koloboke Compile will use those methods to compare keys in the
generated implementation.
Examples:
Fix Java array's equality:
import com.koloboke.compile.CustomKeyEquivalence;
import com.koloboke.compile.KolobokeMap;
@KolobokeMap
@CustomKeyEquivalence
public abstract class CharArrayMap<V> implements Map<char[], V> {
static <V> Map<char[], V> withExpectedSize(int expectedSize) {
return new KolobokeCharArrayMap<V>(expectedSize);
}
final boolean keyEquals(char[] queriedKey, char[] keyInMap) {
return Arrays.equals(queriedKey, keyInMap);
}
final int keyHashCode(char[] key) {
return Arrays.hashCode(key);
}
}
Usage:
Map<char[], String> map = CharArrayMap.withExpectedSize(10);
map.put(new char[] {'h', 'e', 'l', 'l', 'o'}, "hello");
assertEquals("hello", map.get("hello".toCharArray()));
@KolobokeMap
@CustomKeyEquivalence
abstract class IdentityToIntMap<K> implements Map<K, Integer> {
static <K> IdentityToIntMap<K> withExpectedSize(int expectedSize) {
return new KolobokeIdentityToIntMap<K>(expectedSize);
}
abstract int getInt(K key);
abstract int put(K key, int value);
/**
* Returns just {@code false} because keyEquals() contract guarantees that arguments are
* not identical, see {@link CustomKeyEquivalence} javadocs.
*/
final boolean keyEquals(K queriedKey, K keyInMap) {
return false;
}
final int keyHashCode(K key) {
return System.identityHashCode(key);
}
}
@KolobokeMap
- or @KolobokeSet
-annotated types could have non-abstract methods (concrete methods
in abstract classes or default methods in interfaces), Koloboke Compile doesn't override them even
if otherwise it would implement them in generated classes. Auto Value developers coined the term method underriding
for this situation.
For example, you can extend java.util.AbstractMap
in your Map-like class to make Koloboke Compile
to generate a smaller implementation class (because it has to override less methods):
@KolobokeMap
abstract class SmallerLongIntMap extends AbstractMap<Long, Integer> {
static SmallerLongIntMap withExpectedSize(int expectedSize) {
return new KolobokeSmallerLongIntMap(expectedSize);
}
abstract int get(long key);
abstract int put(long key, int value);
}
The Map- or Set-like abstract class could have a single non-private constructor with some parameters. In this case list of parameters of both Koloboke implementation constructors starts with the parameters of the implemented type. Abstract classes could have instance fields and use them in non-abstract methods.
For example, here is how Map with configurable key equivalence and long
values could be
implemented:
import com.koloboke.collect.Equivalence;
import com.koloboke.collect.hash.HashConfig;
import com.koloboke.collect.map.hash.HashObjLongMap;
import com.koloboke.compile.CustomKeyEquivalence;
import com.koloboke.compile.KolobokeMap;
import javax.annotation.Nonnull;
@KolobokeMap
@CustomKeyEquivalence
abstract class ConfigurableKeyEquivalenceMap<K> implements HashObjLongMap<K> {
static <K> HashObjLongMap<K> with(
@Nonnull Equivalence<? super K> keyEquivalence, int expectedSize) {
return new KolobokeConfigurableKeyEquivalenceMap<K>(keyEquivalence, expectedSize);
}
static <K> HashObjLongMap<K> sparseWith(
@Nonnull Equivalence<? super K> keyEquivalence, int expectedSize) {
return new KolobokeConfigurableKeyEquivalenceMap<K>(
keyEquivalence, HashConfig.fromLoads(0.25, 0.375, 0.5), expectedSize);
}
@Nonnull
private final Equivalence<? super K> keyEquivalence;
ConfigurableKeyEquivalenceMap(@Nonnull Equivalence<? super K> keyEquivalence) {
this.keyEquivalence = keyEquivalence;
}
final boolean keyEquals(K queriedKey, K keyInMap) {
return keyEquivalence.equivalent(queriedKey, keyInMap);
}
final int keyHashCode(K key) {
return keyEquivalence.hash(key);
}
@SuppressWarnings("unchecked")
@Nonnull
@Override
public final Equivalence<K> keyEquivalence() {
return (Equivalence<K>) keyEquivalence;
}
}
Note how instead of constructors with int
and HashConfig, int
parameters Koloboke-
implementation for ConfigurableKeyEquivalenceMap
, which has a constructor with an Equivalence
parameter, has constructors with Equivalence, int
and Equivalence, HashConfig, int
parameters.
By default Koloboke Compile generates implementations for methods, that match some method forms,
i. e. have "right" names, parameter and return types, -- corresponding to methods, defined in Map
or Set
interface, or their extensions in the Koloboke Collections API.
@MethodForm
allows to instruct Koloboke Compile on how to implement methods which unheard names, pointing to
the original method form name. This feature could be used to customize Koloboke's method
implementation right in the implemented type:
Examples:
@KolobokeMap
public abstract class SynchronizedMap<K, V> {
public static <K, V> SynchronizedMap<K, V> withExpectedSize(int expectedSize) {
return new KolobokeSynchronizedMap<K, V>(expectedSize);
}
public final synchronized V get(K key) {
return subGet(key);
}
public final synchronized V put(K key, V value) {
return subPut(key, value);
}
public final synchronized int size() {
return subSize();
}
@MethodForm("get")
abstract V subGet(K key);
@MethodForm("put")
abstract V subPut(K key, V value);
@MethodForm("size")
abstract int subSize();
}
Checking some properties of Set keys before adding:
import com.google.common.base.Preconditions;
import com.koloboke.collect.set.IntSet;
import com.koloboke.compile.KolobokeSet;
import com.koloboke.compile.MethodForm;
import javax.annotation.Nonnull;
@KolobokeSet
public abstract class PositiveNumbersSet implements IntSet {
public static IntSet withExpectedSize(int expectedSize) {
return new KolobokePositiveNumbersSet(expectedSize);
}
/**
* {@code replaceUsages=false} to make {@code addAll()} to delegate to checking {@code add()}
* instead of {@code subAdd()}. See {@link MethodForm} javadocs for details.
*/
@MethodForm(value = "add", replaceUsages = false)
abstract boolean subAdd(int e);
@Override
public final boolean add(@Nonnull Integer e) {
return add((int) e);
}
@Override
public final boolean add(int e) {
Preconditions.checkArgument(e > 0, "elements of this set must be positive, {} given", e);
return subAdd(e);
}
}
Maps with numeric primitive values should have some special value e. g. to return from get()
method if the queried key is not found. By default Koloboke Compile generates implementations for
which such default value is zero (for all numeric primitive types). For some applications a
different default value might be more convenient, e. g. a negative value. If the implemented type
defines a non-abstract ValueType defaultValue()
method, Koloboke Compile uses it in the generated
implementation:
import com.koloboke.collect.map.ObjIntMap;
@KolobokeMap
abstract class MinusOneDefaultValueObjIntMap<K> implements ObjIntMap<K> {
static <K> ObjIntMap<K> withExpectedSize(int expectedSize) {
return new KolobokeMinusOneDefaultValueObjIntMap<K>(expectedSize);
}
@Override
public final int defaultValue() {
return -1;
}
}
Usage:
ObjIntMap<String> map = MinusOneDefaultValueObjIntMap.withExpectedSize(10);
map.put("apples", 10);
assertEquals(-1, map.getInt("bananas"));
See the corresponding section
in @KolobokeMap
javadocs for more information.
In the order of importance (more important practices go first):
- Consider that other developers will try to read and understand your value class while looking only at your hand-written class, not the actual (generated) implementation class. If you mark your concrete methods final, they won't have to wonder whether the generated subclass might be overriding them.
- If there is a bug in Koloboke Compile and it will try to override your concrete method, if your
method is
final
the last pass of Java compiler will not let this code compile, saving you from debugging. final
methods could help JVMs to execute code faster.
Do reduce API
- If you want to switch from Koloboke Compile to some other library, it's easier to do that because you have less methods to delegate/implement.
- You explicitly control the set of methods, implemented by Koloboke Compile.
- Not extending
Map
orSet
interfaces allows to generify methods likeget()
andcontains()
, that improves type safety. - Koloboke Compile analyzes the set of methods to implement and applies some optimizations, if
there are less than full
Map
interface to implement. For example, if you don't define remove-like methods, Koloboke Compile applies the same optimizations as if a Map-like class is annotated@Updatable
. - Koloboke Compile generates smaller implementation.
If you need your abstract class or interface to be a subclass of Map
or Set
, but you don't
actually need individual entry or element removal operations on it, you should always reduce
mutability by annotating the implemented type with @Updatable
. Apart from
enforcing intent not to remove individual entries or elements from collection, @Updatable
enables
Koloboke Compile to generate a faster implementation.
- Before Java 8, interfaces cannot directly contain static factory methods.
- Abstract classes allow to define some methods as package-private, i. e. help to reduce visibility.
- You can mark your concrete methods
final
. In Java 8 interfaces could have concretedefault
methods, and Koloboke Compile accounts them, but they couldn't be marked asfinal
. - There are rumors that JVMs sometimes generate faster machine code involving operations with abstract classes than interfaces.
There are a few small advantages to adding a package-private, parameterless constructor to your abstract class. It prevents unwanted subclasses, and prevents an undocumented public constructor showing up in your generated API documentation. Whether these benefits are worth the extra noise in the file is a matter of your judgment.
- Mark all concrete methods
final
- Do reduce API
- Use
@Updatable
when applicable - Prefer abstract classes to interfaces
Koloboke Compile can generate implementations for methods void justPut(KeyType, ValueType)
and
boolean justRemove(KeyType)
, they are slightly more efficient than classic put()
and remove()
because don't have to return previously mapped value:
@KolobokeMap
public abstract class OptimizedMap<K> {
static <K> OptimizedMap<K> withExpectedSize(int expectedSize) {
return new KolobokeOptimizedMap<K>(expectedSize);
}
abstract void justPut(K key, int value);
abstract int getInt(K key);
abstract boolean justRemove(K key);
}
Usage:
OptimizedMap<String> map = OptimizedMap.withExpectedSize(10);
map.justPut("apples", 10);
assertEquals(10, map.getInt("apples"));
assertTrue(map.justRemove("apples"));
assertEquals(0, map.getInt("apples"));
Avoid using @NullKeyAllowed
Allowing the null
key makes Koloboke Compile to generate slower implementations.
You can disable concurrent modification checks by annotating the implemented type with
@ConcurrentModificationUnchecked
.
Use this technique with great caution; read Javadocs
for this annotation for more details.
You can find all samples from this tutorial and Javadocs in the
com.koloboke.compile.fromdocs
package.
Outside this repository, you can find proof of concept implementation of interfaces from the Trove collections library in the trove-over-koloboke-compile project.
If you use Maven or Gradle build, you merely have to check the "Enable Annotation Processing" box on the IntelliJ Annotation Processing Settings screen:
If you use Maven:
- Install the
m2e-apt
Eclipse plugin. Go to Help > Eclipse Marketplace, typem2e-apt
into the search box, install the plugin. - Go to Window > Preferences > Maven > Annotation Processing, use the "Automatically configure JDT APT" option:
- Import your Maven project using File > Import > Existing Maven Projects, or (if you already work with the project in Eclipse), select the project in the Package Explorer, open context menu (right click in Windows) > Maven > Update Project.
You could probably find some useful information in documentation of similar annotation processing tools:
Normally FindBugs shouldn't complain about code generated by Koloboke Compile. Koloboke Compile strives to suppress all FindBugs's false positives on the generated sources. If FindBugs does emit warnings about Koloboke Compile-generated code, the case deserves close investigation and likely indicates some improper use of Koloboke Compile, or bugs in your code, or bugs in Koloboke Compile. If you think this is a bug in Koloboke Compile, please report it via Github Issues.
javac
and ECJ shouldn't generally report warnings about the Koloboke Compile-generated source
code, except about sun.misc.Unsafe
usages. To disable them, you could provide
-XDenableSunApiLintControl -Xlint:-sunapi
arguments to javac
. See instructions on how to
provide compiler arguments if you use Maven build, if you
use Gradle, you can do this as follows:
compileJava {
options.compilerArgs << '-XDenableSunApiLintControl' << '-Xlint:-sunapi'
}
Currently each @KolobokeMap
- or @KolobokeSet
-annotated class adds 2-3 seconds to the overall
Java compilation time (or IDE build time).
Processing time should be improved in future versions of Koloboke Compile.
Currently projects that use Koloboke Compile should depend on
koloboke-impl-common
and transitively on koloboke-api
even if Koloboke
Collections classes and interfaces are never used in the project code. This is because Koloboke
Compile generates implementations that depend on koloboke-api
and koloboke-impl-common
. Total
size of those jars is about 1 MB.
Future versions of Koloboke Compile shouldn't have this requirement, allowing projects to have zero runtime dependencies.
This is by design of Koloboke Compile.
You can ask any question about Koloboke Compile using Github issues or on StackOverflow.