@Retention(value=SOURCE) @Target(value=TYPE) public @interface KolobokeSet
@KolobokeSet
-annotated typesSet
model defining method formThe annotated type must have an abstract method (either declared in the body of the
type or inherited) matching (see below) the boolean add(KeyType key)
form,
where KeyType
is either some reference type (a declared type, or an array type, or
a type variable) or a numeric primitive type (byte
, char
, short
,
int
, long
, float
or double
).
"A method matches a form" means that the form and the method have the same signature
(as defined in the Java Language Specification, ยง8.4.2), and return type of the form is identical
to the return type of the method, or, if the return type of the form is a reference type, it is a
subtype of the return type of the method. If the method is annotated with a @MethodForm
annotation, for signature comparison, instead of the method's own name,
the name specified by the @MethodForm
is taken. The method might declare any exceptions
in the throws
clause. Method parameter names are not included in method
signatures in the Java language, hence they shouldn't necessarily be the same in the from and the
matching method.
KeyType
consistencyKeyType
should be the same in all methods (either abstract or not) in the
@KolobokeSet
-annotated type, which match the above form and the forms defined below. For
example, the annotated type couldn't simultaneously have methods boolean add(K)
and
boolean contains(String)
, because according to the first method the KeyType
is
K
(a type variable), and according to the second method the KeyType
is
String
.
Set
model definition)Hereafter in this specification and specifications of other Koloboke Compile annotations, for
some @KolobokeSet
-annotated type, the KeyType
appearing in the abstract method of
this type, which match the above form, is called the key type of the annotated type.
add()
method form for numeric primitive key
typeIf the key type is a numeric primitive type, a method (either abstract or not) could match
additional version of the add()
form: a version with primitive wrapper classes as the
key
parameter. For example, if the key type of the annotated type is int
, it
could have method boolean add(int)
, or boolean add(Integer)
, or both
simultaneously.
A numeric primitive key type could even never appear as itself in the signatures and
return types of the methods of the annotated type (but only as a wrapper class in methods
matching the "boxed" versions of some forms): for example, a @KolobokeSet
-annotated
interface extending Set<Long>
and not defining own methods (hence inheriting
abstract methods like boolean put(Long)
, which match the "boxed" version of the
add()
form) still have primitive long
key type. This is important, because in the
generated implementations for such types keys are stored as primitives, hence insertion of the
null
key into such set is not possible and always leads to NullPointerException
.
The only way to make Koloboke Compile to generate a set implementation that actually stores
primitive wrapper objects (though this is a highly questionable goal) is to annotate with
@KolobokeSet
a generic abstract class or an interface with key type of a type variable,
and parameterize it with primitive wrapper class (e. g. Integer
) at the moment of
instantiation.
Currently Koloboke Compile is able to generate only hash table-based implementations of annotated Set-like classes or interfaces. In the future, ability to generate other kinds of implementations (e. g. tree-based) might be added.
In addition to the add()
method form, a KolobokeSet
-annotated class or
interface could have methods (either abstract or not) matching the following forms:
HashObjSet<KeyType>
. If the key type is a numeric primitive type, the prototyping
interface is HashXxxSet
, where is a capitalized name of the key type, e. g. HashIntSet
if the key type is int
. Method forms have signatures and return types of
the methods in the prototyping interface.
The Koloboke Collections API comes in two different distributions: for Java 6 or 7, and
for Java 8+, and signatures of some methods are different in the same interfaces, provided by
different distributions. For example, the method ObjCollection.forEach(Consumer)
has the
parameter type of java.util.function.Consumer
in the
distribution for Java 8+, and
com.koloboke.function.Consumer
in the distribution for Java 6 and 7. Possible
method forms have signatures of the methods in that version of the prototyping interface,
which is present in the compilation classpath at the moment of Koloboke Compile generation
.
Forms, defined by methods, which are not abstract in the prototyping interface, couldn't
be matched by abstract methods in the KolobokeSet
-annotated class or interface, but
if there are some non-abstract matching methods, they should be consistent with the key and
value types of the annotated type, as specified above.
This means, for example, that if the project, containing the @KolobokeSet
-annotated
type, depends on JDK 8 and the Koloboke Collections API distribution for Java 6 and 7
(although Koloboke Compile emits a warning in this case, suggesting to change the dependency
to the Koloboke Collections API distribution for Java 8+), method Iterable.forEach(java.util.function.Consumer)
will have a default implementation (i. e. it will
be non-abstract) in the HashObjSet
interface, so the
@KolobokeSet
-annotated type couldn't declare or inherit such a method as abstract.
The Koloboke Collections API distribution for Java 8+ doesn't currently declare
stream()
and spliterator()
methods as abstract, so they couldn't be abstract
in @KolobokeSet
-annotated types as well.
Set
interfacecontains()
and remove()
in the Set
interface, and
consequently in subinterfaces (including primitive specializations) from the Koloboke
Collections API, have the key parameter of Object
type, that is a double-edged design
decision. Along with the forms, prototyped by those methods, similar
forms with generified parameter type are also allowed in
@KolobokeSet
-annotated types:
Return type | Signature |
---|---|
boolean | contains(KeyType key) |
boolean | remove(KeyType key) |
Koloboke Compile not only permits generified versions of the
"Object
-parameterized" methods, but effectively opts out the feature of making
queries with arguments of unrelated types for Sets with concrete (not a type variable) key
type. For example, if a @KolobokeSet
-annotated class has ArrayList
key type, it may have a method contains(Object)
, but an attempt to call this method
with LinkedList
argument will result in ClassCastException
, with a message
like "LinkedList couldn't be cast to ArrayList". However, if the key type of a
@KolobokeSet
-annotated class or interface is a type variable, and it is ArrayList
at the instantiation point, set.contains(linkedList)
will proceed without
exception and may return true
, if there is an equivalent ArrayList
key in
this set.
The @KolobokeSet
-annotated type could have an abstract method of the form of the
Object.equals(Object)
method, only if it is a subtype of Set
.
The @KolobokeSet
-annotated type could have any static methods and non-abstract
methods with any signatures and return types, as long as they don't match any of the forms
defined above (but if they do match some forms, they should have
consistent signatures and return types).
The annotated type could be a nested type, but in this case it should be static and non-private. The annotated type couldn't be an inner or an anonymous class.
If the annotated type is an abstract class, it must have exactly one non-private constructor (the implicit no-argument constructor counts).
The annotated type couldn't have a @KolobokeMap
annotation (any type could
either have a @KolobokeSet
or a @KolobokeMap
annotation, but not both).
@KolobokeSet
-annotated type is MySet
, the name of the implementation class is KolobokeMySet
. If the implemented type is
a nested type, instead of the simple name of the implemented type, it's canonical name (excluding
package) is used with dots replaced with underscores: _
. For example, if a
@KolobokeSet
-annotated type named InnerSet
is a nested type in a type named
Outer
, the name of the Koloboke Compile-generated implementation class is KolobokeOuter_InnerSet
.
The implementation class is located in the same package, as the implemented type, and has package-private visibility in this package.
If the @KolobokeSet
-annotated type has type parameters, the implementation class has
the same number of type parameters with equivalent types in the same order ("equivalence of types
of type parameters" essentially means equivalence of their bounds).
The implementation class is non-abstract. It overrides all abstract methods in the implemented type, and doesn't override non-abstract methods. Methods in the implementation class, overriding some methods in the implemented type, have contracts of the methods in interfaces from the Koloboke Collections API, prototyping the forms, matched by the overridden methods in the implemented type.
The implementation may call any (even not documented as methods forms in this document)
non-abstract methods of the implemented type, which it would otherwise generate itself. To ensure
that user implementations of some methods don't change semantics of Koloboke Compile-generated
methods in some unintended way (because they call underridden methods), it is recommended to
manually check all usages of non-abstract methods of the implemented type in the Koloboke
Compile-generated implementation class. If it appears that behaviour of some methods indeed
become wrong because of cross-dependencies between methods in the implemented and implementation
classes, a possible workaround is to declare new abstract method(s) having the same parameter and
return types as the underridden method(s) which caused undesirable semantics change of other
methods, but with different names, and annotate them with @MethodForm("nameOfTheUnderriddenMethod"
. This makes Koloboke Compile to replace
usages of the underridden methods in the generated implementation (usages, which statically
resolve to the underridden methods, i. e. where the type of the method call
receiver is the implemented type or the implementation type, but not a supertype of the
implemented type) with the newly declared abstract methods as well as generate implementations
for these methods.
No guarantee is provided on whether in the generated class such bulk methods as Set.containsAll(java.util.Collection<?>)
, Set.addAll(java.util.Collection<? extends E>)
, Set.removeAll(java.util.Collection<?>)
, Set.retainAll(java.util.Collection<?>)
, etc. are
implemented via single-key query methods such as Set.contains(Object)
, Set.add(Object)
, Set.remove(Object)
etc. or not. More generally, is it not specified how
and if any methods of the implemented class, it's iterators and cursors may call each other.
equals()
and hashCode()
The implementation class overrides Object.equals(Object)
, Object.hashCode()
if
the implemented type is a subtype of Set
and the methods are abstract in the implemented
type. If any of them is non-abstract, but inherited directly from Object
(i. e. inherited
the default implementation), it is still overridden.
toString()
The implementation class overrides Object.toString()
if it is abstract in the implemented
type. If it is non-abstract, but inherited directly from Object
, it is still overridden.
Contracts of the methods in the generated implementation class might be affected by additional annotations applied to the implemented type, see the Implementation Customizations section below.
The implementation class is declared final
(not extensible).
Koloboke Compile "doesn't know" about Serializable
and Cloneable
interfaces:
it doesn't check if @KolobokeSet
-annotated classes or interfaces implement or extend
these interfaces, nor overrides Object.clone()
or implements readObject()
and
writeObject()
methods, specified by the Serializable
interface. The
implementation classes don't declare any fields transient
. However,
@KolobokeSet
-annotated types could intercept by providing implementations for
clone()
or serialization methods itself.
The implementation class is not synchronized in any way. It doesn't internally use the
intrinsic object lock of the receiver instance nor any input objects. (So it doesn't declare any
methods synchronized
.) Abstract @KolobokeSet
-annotated types are free to use the
intrinsic lock (e. g. declare some non-abstract methods synchronized
).
The implementation class has two constructors. One constructor accepts a parameter of
the int
type, the expected size of the set being constructed. Another constructor
accepts two parameters: the first of the HashConfig
type -- a hash configuration for the
constructed set, the second of the int
type, the expected size of the set. Calling the
first constructor is equivalent to calling the second, with the default hash config passed as the first argument.
If the implemented type is an abstract class and it's non-private constructor has some
parameters, parameters lists of both constructors of the implementation type are preceded
with parameters of the same types as the parameters of the implemented type's constructor,
and they are passed over to the implemented class constructor in a super()
call. For
example, if a @KolobokeSet
-annotated abstract class has a non-private constructor with
parameter types int
, String
and Object
, parameter types of the
constructors of the generated class have the following types:
int, String, Object, int (expectedSize)
int, String, Object, HashConfig, int (expectedSize)
If the non-private constructor of the implemented class has type parameters, equal type parameters are present in both constructors in the implementation class as well.
If the @KolobokeSet
-annotated type is annotated with one of the annotations from the
com.koloboke.compile.mutability
package, Koloboke Compile generates an implementation
with the specified mutability profile. If none of the annotations from this package is
applied to a @KolobokeSet
-annotated type, Koloboke Compile generates
an implementation with the least mutability, which supports all the abstract methods in
an implemented type. See the package documentation
for more information.
By default, if a @KolobokeSet
-annotated type has a reference key type, Koloboke
Compile generates an implementation that disallows insertion and querying the null
key,
i. e. calls like set.add(null)
, set.remove(null)
and set.contains(null)
on instances of such implementation result in NullPointerException
. Koloboke Compile
generates an implementation that allows the null
key, only if the implemented type is
annotated with @NullKeyAllowed
.
By default Koloboke Compile generates a Set implementation (for a model with a reference
key type) which relies on the Java built-in object equality (Object.equals(Object)
and Object.hashCode()
) for comparing keys, just as Set
interface specifies. But
if the implemented type has two non-abstract methods which match boolean keyEquals(KeyType, KeyType)
int keyHashCode(KeyType)
forms, the generated
implementation class relies on these methods for determining hash codes and comparing the queried
keys. This may be used for "redefining" inefficiently implemented equals()
and hashCode()
for some key type, or making key equivalence configurable, or using a non-standard equivalence relationship in
the keys domain, e. g. defining a IdentityHashMap
-like set type. See the @CustomKeyEquivalence
specification for more information.
See the documentation for the
com.koloboke.compile.hash.algo.openaddressing
package.
By default Koloboke Compile generates a Set implementation that returns so-called fail-fast
iterators and cursors, and tries to identify concurrent structural modifications during bulk
operations like forEach()
, removeIf()
, containsAll()
, addAll()
,
etc. This is consistent with HashSet
's behaviour. If instances of the Koloboke
Compile-generated implementation of the @KolobokeSet
-annotated type are going to be
accessed only via single-key queries (e. g. only via add()
and contains()
),
disabling concurrent modification checks by applying @ConcurrentModificationUnchecked
might improve the
implementation performance a bit.