Truly immutable collections, functional errors handling, laziness and measurement utilities.
This library has no dependencies (except the test scope).
If you want to contribute, check out the Contributing Guide.
master
branch provides the latest DEV-SNAPSHOT
documentation.
You can find the specific version info by git tags.
You need Java 8+ to use the library.
Maven:
<dependency>
<groupId>com.kirekov</groupId>
<artifactId>java-useful-utils</artifactId>
</dependency>
Gradle:
implementation 'com.kirekov:java-useful-utils'
The library consists of three big parts.
- Measurement
- Monads (functional style programming)
- Collections
Sometimes we all face with the problem when we need to measure time of execution of one function or method. Usually it suppose to be done like this:
long before = System.currentTimeMillis();
int result = doSomething();
long after = System.currentTimeMillis();
long measurementResult = after - before;
It might be OK, if we need to do this once. But if there are several functions that have to be measured, writing the same code snippets every time is gonna be exhausting.
So here is the solution:
ExecutionResult<Integer> exec =
Measure.executionTime(this::doSomething)
.inMillis();
int res = exec.getResult();
long time = exec.getTime();
assert exec.getMeasureUnit() == MeasureUnit.MILLIS;
Simple, right? If you need to measure something in different
units, just call the appropriate method
(inMillis()
, inNanos()
or inSeconds()
).
And what if you need to measure code block from one point to another?
Well, Profiler
is what you need.
Profiler profiler = Profiler.startMeasuringInMillis();
// do some useful stuff
...
long time = profiler.stopMeasuring();
Such code style is much clearer than putting System.currentTimeInMillis()
everywhere.
I think every java developer used at least one monad - java.util.Optional
.
This class allows to work with nullable values much more efficiently.
return Optional.ofNullable(getNullableString())
.map(str -> str + "I've just edited it")
.orElse("Oh my god. It is empty");
This library has one monads: Try
.
Try
allows you to work with methods that may throw an exception
in the same way as Optional
in a lazy way. For instance, suppose we have such code:
int num;
try {
String value = getStringValue();
num = Integer.parseInt(value);
}
catch (NumberFormatException e) {
num = getDefaultIntValue();
}
...
try {
return executeRpc(num);
}
catch (RPCException e) {
return SOME_DEFAULT_VALUE;
}
In this case we don't care about the exception type but the error fact itself. JUU allows to rewrite this snippet as two equations:
int num = Try.of(() -> Integer.parseInt(getStringValue()))
.orElseGet(() -> getDefaultIntValue());
return Try.of(() -> executeRpc(num))
.orElse(SOME_DEFAULT_VALUE);
Also we can use map
, flatMap
and filter
functions.
int value = Try.of(this::getStringValue)
.map(this::reverseString)
.flatMap(str -> Try.of(() -> Integer.parseInt(str)))
.filter(num -> num > 0)
.orElseThrow(IllegalArgumentException::new);
Let's deconstruct this monad step by step:
- Receives some string value from
getStringValue()
method. - Reverses string.
- Converts string to integer.
- Checks if number is positive.
- If any of previous steps fails, throws
IllegalArgumentException
.
And here is the same code written in pure java:
int value;
try {
String val = getStringValue();
String reversed = reverseString(val);
value = Integer.parseInt(reversed);
if (value <= 0) {
throw new RuntimeException("no no no");
}
}
catch (Exception e) {
throw new IllegalArgumentException();
}
If you need the exception that led to the error, you can use
orElseGet
variation.
Try.of(() -> Integer.parseInt(getStringValue))
.map(x -> 2 % x)
.orElseGet((Exception reason) -> {
log.error("Something went wrong", reason);
return 0;
})
reason
has the type of Exception
and it's the instance of that very
exception that broke the chain.
For instance, if Integer.parseInt
threw an exception,
the reason
would be the type of NumberFormatException
.
On the other hand, if x == 0
in the map
callback,
the reason would be the type of ArithmeticException
.
The class only catches Exception
type.
It means that all Throwable
instances are skipped.
The motivation is that Error
extends from Throwable
but these exceptions should not be caught manually.
The fact that Try
monad acts lazily means
that you build a pipeline of execution that triggers on any terminal operation.
Try<Integer> t = Try.of(() -> {
println("First step");
return 1;
}).map(val -> {
println("Second step");
return val + 1;
}).filter(val -> {
println("Third step");
return val > 0;
});
// nothing prints here
assert 2 == t.orElseThrow();
// First step
// Second step
// Third step
All terminal operations are listed in the javadoc.
The "Collections" part consists of two subparts: "Immutable collections" and "Mutable containers".
One of the most irritating thing for me in Java is total absence of immutable collections. For instance, suppose we have such code:
List<Integer> numbers = getNumbers();
List<Integer> result = doSomething(numbers);
Have this list been changed? What have been returned by
doSomething()
? The same list or the new one?
If we delete an element from numbers
,
will it have an effect on result
?
Well, we can implement our own "immutable" list
that inherits java.util.List
and make mutating
methods (add
, clear
, set
...) throw an exception.
But how do we know what implementation has been
passed as a parameter? It is not convenient to write
try {} catch (Exception e) {}
or use Try
on every add
call.
JUU library provides new collections, which interfaces do not inherits from java native Collections. Here is the scheme
The recommended way to instantiate immutable collections is to use Immutable
class.
// lists
ImmutableList<Integer> list1 =
Immutable.listOf(1, 2, 3) // accepts T...
ImmutableList<String> list2 =
Immutable.listOf(Arrays.asList("1", "2", "3")) // accepts Iterable<T>
// sets
ImmutableSet<Integer> set1 =
Immutable.setOf(1, 2, 3);
ImmutableSet<String> set2 =
Immutable.setOf(Arrays.asList("1", "2", "3"));
// maps
ImmutableMap<String, Integer> map1 =
Immutable.mapOf("1", 1, "2", 2, "3", 3);
ImmutableMap<String, Integer> map2 =
Immutable.mapOf(Arrays.asList(
Pair.of("1", 2),
Pair.of("2", 2),
Pair.of("3", 3)
))
You can also use collectors from ImmutableCollectors
to create immutable collections
from Stream
.
ImmutableList<String> list =
getNumbersList().stream()
.map(String::valueOf)
.collect(ImmutableCollectors.toList());
ImmutableSet<String> set =
getNumbersList().stream()
.map(String::valueOf)
.collect(ImmutableCollectors.toSet());
ImmutableMap<String, Integer> map =
getNumbersList().stream()
.collect(ImmutableCollectors.toMap(
String::valueOf,
value -> value
));
You can user Stream API with immutable collections as well,
but ImmutableList
and ImmutableSet
provides kotlin-like methods:
map
, flatMap
, filter
, min
, max
and zip
(for lists).
ImmutableList
also has sorted
, zipWith
, zipWithNext
and indexed methods
mapIndexed
, flatMapIndexed
, filterIndexed
.
ImmutableList<Integer> mappedList = list.map(Integer::parseInt);
ImmutableSet<String> mappedSet = set.flatMap(str -> Arrays.asList(str, str + "1"));
ImmutableList<Integer> filtered1 = mappedList.filter(x -> x > 0);
ImmutableList<Integer> filtered2 =
mappedList.filterIndexed((index, val) -> index % 2 == 0);
ImmutableList
provides Python-like Slice API.
Which means, that you can use negative indices, steps and negative steps.
ImmutableList<Integer> list = getList(); // [1, 2, 3, 4, 5, 6]
assert list.get(list.size() - 1) == list.get(-1) // 6
// startIndex (inclusively), endIndex (exclusively), stepSize
list.slice(0, 3, 1); // [1, 2, 3]
list.slice(-1, 2, -1); // [6, 5, 4]
list.slice(0, 6, 2); // [1, 3, 5]
list.step(3) // [1, 4]
// if stepSize is negative, startIndex is -1
list.step(-2) // [6, 4, 2]
Sometimes we need to return several values from method. Problem can be solved by creating values wrapper. But it may produce tons of "infrastructural code", especially if types vary. JUU has special mutable containers that can be passed as a parameter. For instance
List<Row> someList = ...;
MutableValue<Row> biggest = new MutableValue<>(null);
// suppose to call `biggest.setValue(row)`
int affectedRows = fillWithMagic(someList, biggest);
...
return biggest.getValue();
Also lib has implementations for each primitive type.
MutableInt
, MutableDouble
, MutableShort
,
MutableLong
, MutableFloat
, MutableChar
,
MutableByte
, MutableBoolean
.