Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
Clone this wiki locally
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
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:
TransformingMapand more: let you specify a transformation
E <~> Xto make, e.g., a
EqualityTransformingMap: let you substitute
OptionalTransformingSet: make, e.g., a
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.
// "[0, 2] ~ [0, 2]" are the output of
System.out.println(innerSet + " ~ " + tranformingSet);.
Transforming The Element Type
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]"
Removing Optionality From A Collection
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>> 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]"