-
Notifications
You must be signed in to change notification settings - Fork 9
TransformingCollections
Transforming collections are a view onto another collection, making it appear to be of a different parametric type. This can be used to remove optionality from a collection or substitute equals
and hashCode
.
The feature's concept is described below before examples demonstrates its use. A set of small, self-contained demos showcases it. The comment on the package org.codefx.libfx.collection.transform
provides further documentation. The main types to start using this feature are:
-
TransformingList
,TransformingSet
,TransformingMap
and more: let you specify a transformationE <~> X
to make, e.g., aSet<E>
into aSet<X>
-
EqualityTransformingSet
,EqualityTransformingMap
: let you substituteequals
andhashCode
-
OptionalTransformingCollection
,OptionalTransformingList
,OptionalTransformingSet
: make, e.g., aList<Optional<E>>
into aList<E>
I also wrote a blog post about it which presents the general idea, some details and examples. But note that - unlike this article - it will not be updated.
A transforming collection is a view onto another collection (e.g. list onto list, map onto map, …), which appears to contain elements of a different type (e.g. integers instead of strings).
The view elements are created from the original elements by applying a transformation. This happens on demand so the transforming collection itself is stateless. Being a proper view, all changes to the inner collection as well as to the transforming view are reflected in the other (like, e.g., Map and its entrySet).
A transforming collection can also be seen as a decorator. I will refer to the decorated collection as the inner collection and it’s generic type accordingly as the inner type. The transforming collection and its generic type are referred to as outer collection and outer type, respectively.
Make sure to read the package comment on org.codefx.libfx.collection.transform
before using the feature. Doing that now before continuing with the examples would be helpful.
Transforming collections can be used to achieve different goals. We will cover three distinct possibilities.
Comments like // "[0, 2] ~ [0, 2]"
are the output of System.out.println(innerSet + " ~ " + tranformingSet);
.
Let's use a TransformingSet
to view a set of strings, which we know to only contain natural numbers, as a set of integers:
Set<String> innerSet = new HashSet<>();
Set<Integer> transformingSet = TransformingCollectionBuilder
.forInnerAndOuterType(String.class, Integer.class)
.toOuter(Integer::parseInt)
.toInner(Object::toString)
.transformSet(innerSet);
// both sets are initially empty: "[] ~ []"
// now let's add some elements to the inner set
innerSet.add("0");
innerSet.add("1");
innerSet.add("2");
// these elements can be found in the view: "[0, 1, 2] ~ [0, 1, 2]"
// modifying the view reflects on the inner set
transformingSet.remove(1);
// again, the mutation is visible in both sets: "[0, 2] ~ [0, 2]"
Let’s say you want to use strings as set elements but for comparison you are only interested in their length:
lengthSet = EqualityTransformingCollectionBuilder
.forType(String.class)
.withEquals((a, b) -> a.length() == b.length())
.withHash(String::length)
.buildSet();
lengthSet.add("a");
lengthSet.add("b");
System.out.println(lengthSet); // "[a]"
Maybe you’re working with someone who took the idea of using Optional
everywhere, ran wild with it and now you’re stuck with a Set<Optional<String>>
.
Set<Optional<String>> innerSet = // ... existing set
Set<String> transformingSet =
new OptionalTransformingSet<String>(innerSet, String.class);
innerSet.add(Optional.empty());
innerSet.add(Optional.of("A"));
// "[Optional.empty, Optional[A]] ~ [null, A]"
Note how the empty Optional
is represented as null
. This is the default behavior but you can also specify another string as a value for empty optionals:
Set<String> transformingSet =
new OptionalTransformingSet<String>(innerSet, String.class, "DEFAULT");
innerSet.add(Optional.empty());
innerSet.add(Optional.of("A"));
// "[Optional.empty, Optional[A]] ~ [DEFAULT, A]"