Git Product home page Git Product logo

simonharmonicminor / java-useful-utils Goto Github PK

View Code? Open in Web Editor NEW
13.0 1.0 2.0 1.28 MB

Truly immutable collections, functional errors handling, laziness, and measurement utilities

Home Page: https://simonharmonicminor.github.io/Java-Useful-Utils/

License: MIT License

Java 99.98% Shell 0.02%
java laziness measure immutable immutable-collections monads mutable-containers monad functional-programming

java-useful-utils's Introduction

Java Useful Utils

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.

Table of contents

Quick start

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' 

Status

Maven Central Javadoc Build Status Quality Gate Status Coverage Hits-of-Code checkstyle PMD MIT License

Usage

The library consists of three big parts.

Measurement

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.

Monads

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

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:

  1. Receives some string value from getStringValue() method.
  2. Reverses string.
  3. Converts string to integer.
  4. Checks if number is positive.
  5. 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.

Collections

The "Collections" part consists of two subparts: "Immutable collections" and "Mutable containers".

Immutable collections

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 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]
Mutable containers

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.

Authors

java-useful-utils's People

Contributors

simonharmonicminor avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Forkers

evads duongli45

java-useful-utils's Issues

Replace Maven usage with Gradle

The master branch should be deployed to maven central automatically each time a new pull request is merged. The easies way to achieve it is to use com.bmuschko:gradle-nexus-plugin:2.3.1 plugin.

Try: replace get() method with orElseThrow()

Try.get method might be confusing because the signature does not tell that it can throw EmptyContainerException. It is better to provide users orElseThrow() variation with zero arguments.

Implement UnmodifiableIterator to return from ImmutableCollection.iterator()

All immutable collections encapsulate java native mutable collections. So, when we type something like

ImmutableArrayList<String> list = ...;
Iterator<String> iterator = list.iterator();

this iterator actually belongs to java.util.ArrayList. So, that means that iterator.remove() may actually delete an element from the list. The solution is to implement UnmodifiableIterator and force immutable collections to return it

Remove Serializable

Those classes should not implement Serializable interface

  • ImmutableHashMap
  • ImmutableArrayList
  • ImmutableHashSet
  • ImmutableTreeMap
  • ImmutableTreeSet
  • PairImpl

Java platform serializability is hard to maintain. More than that, it is not as significant as it used to be.

Define AbstractImmutableCollection class

AbstractImmutableCollection must implement ImmutableCollection interface and any parent collection has to extend this class. AbstractImmutableCollection must declare equals and hashCode methods as abstract

Integrate Checkstyle

Checkstyle is a static analyzer for java. In order to keep the quality on a high level, it should be integrated with the project.

This config and checkstyle version 8.42 should be used.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.