A memo on how to use the Google MµG library, a utility library for Java 8.
dependencies {
compile 'com.google.mug:mug:1.12'
}
BiStream
Stream
with key and value.
BiStream
Use the static method of BiStream
.
biStream()
var input = Stream.of( "foo", "bar", "buz" );
var result = BiStream.biStream( input )
.mapValues( e -> e.toUpperCase() )
.toMap();
assertThat( result ).containsExactly( "foo", "FOO", "bar", "BAR", "buz", "BUZ" );
Wrap a regular Stream
. When wondering what to use, it seems that it is to call an operation like mapKeys ()
afterwards.
var input = Stream.of( "foo", "bar", "buz" );
var result = BiStream.biStream( input, Function.identity(), e -> e.toUpperCase() )
.toMap();
assertThat( result ).containsExactly( "foo", "FOO", "bar", "BAR", "buz", "BUZ" );
A version that takes a mapping function as an argument.
from()
var input = Map.of( "foo", "123", "bar", "456", "buz", "789" );
var result = BiStream.from( input )
.map( ( i, v ) -> i + ":" + v )
.collect( toList() );
assertThat( result ).containsExactly( "foo:123", "bar:456", "buz:789" );
Create a BiStream
with the map key and value as a pair.
indexed()
var input = Stream.of( "foo", "bar", "buz" );
var result = BiStream.indexed( input )
.map( ( i, v ) -> i + ":" + v )
.collect( toList() );
assertThat( result ).containsExactly( "0:foo", "1:bar", "2:buz" );
Add an index starting from 0. This is nice because it's built into the standard APIs of most programming languages.
The return type is BiStream <Integer, V>
. Primitive types are not supported as expected.
neighbors()
var input = Stream.of( "foo", "bar", "buz" );
var result = BiStream.neighbors( input )
.map( ( i, v ) -> i + ":" + v )
.collect( toList() );
assertThat( result ).containsExactly( "foo:bar", "bar:buz" );
Pair two adjacent values.
of()
var result = BiStream.of( "foo", "123", "bar", "456" )
.map( ( i, v ) -> i + ":" + v )
.collect( toList() );
assertThat( result ).containsExactly( "foo:123", "bar:456" );
Similar to the JDK standard ʻof ()`.
zip()
var input1 = Stream.of( "foo", "bar", "buz" );
var input2 = Stream.of( "hoge", "piyo", "fuga" );
var result = BiStream.zip( input1, input2 )
.map( ( t, u ) -> t + ":" + u )
.collect( toList() );
assertThat( result ).containsExactly( "foo:hoge", "bar:piyo", "buz:fuga" );
Combine the two Stream
s. I always made this myself. To be honest, the level I want you to add to the standard library.
However, note that if the size of Stream
is different, the remainder will be ignored according to the smaller one.
var input1 = Stream.of( "foo", "bar", "buz" );
var input2 = Stream.of( "hoge", "piyo" );
var result = BiStream.zip( input1, input2 )
.map( ( t, u ) -> t + ":" + u )
.collect( toList() );
assertThat( result ).containsExactly( "foo:hoge", "bar:piyo" );
BiStream
Stream
operations, operations for keys and values such asfilterKeys ()
andmapValues ()
have been added.append()
var result = BiStream.of( "foo", "123" ).append( "bar", "456" )
.map( ( t, u ) -> t + ":" + u )
.collect( toList() );
assertThat( result ).containsExactly( "foo:123", "bar:456" );
Add a value after it.
inverse()
var result = BiStream.of( "foo", "123", "bar", "456" )
.inverse()
.map( ( t, u ) -> t + ":" + u )
.collect( toList() );
assertThat( result ).containsExactly( "123:foo", "456:bar" );
Exchange values for keys. I want you to add it to the standard Stream
...
MoreStream
Provides extensions not found in the JDK or Guava.
generate()
var result = MoreStreams.generate( 1, n -> n >= 9 ? Stream.empty() : Stream.of( n + 1 ) )
.collect( toList() );
assertThat( result ).containsExactly( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
A method for generating a * finite * stream. Stop generation when an empty stream is returned. Is there any use for it?
flatten()
var input = Stream.of(
Stream.of( "foo", "bar", "buz" ),
Stream.of( "hoge", "piyo", "fuga" )
);
var result = MoreStreams.flatten( input )
.collect( toList() );
assertThat( result ).containsExactly( "foo", "bar", "buz", "hoge", "piyo", "fuga" );
The same process should be possible with flatMap
, but there is this method because it cannot handle infinite streams due to a Java bug. It seems that it was fixed in JDK 10.
dice()
var input = Stream.of( "foo", "bar", "buz", "hoge", "piyo" );
var result = MoreStreams.dice( input, 2 )
.collect( toList() );
assertThat( result.size() ).isEqualTo( 3 );
var list1 = result.get( 0 );
assertThat( list1 ).containsExactly( "foo", "bar" );
var list2 = result.get( 1 );
assertThat( list2 ).containsExactly( "buz", "hoge" );
var list3 = result.get( 2 );
assertThat( list3 ).containsExactly( "piyo" );
List Stream
s by specified size and put them together in a newStream <List>
.
For sequential streams, it is guaranteed that a list of the specified size will be returned except at the end of the stream. Not guaranteed for parallel streams.
By the way, I learned for the first time that such an operation is called dice
. How is it different from window
?
iterateOnce()
var input = Stream.of( "foo", "bar", "buz" );
for ( var e : MoreStreams.iterateOnce( input ) ) {
assertThat( e ).isAnyOf( "foo", "bar", "buz" );
}
A method for iterating a stream with a for
statement. Maybe I won't use it for the rest of my life.
iterateThrough()
expect(() -> {
MoreStreams.iterateThrough( Stream.of( "foo" ), e -> {
throw new Exception( "Checked Exception!" );
});
}).throwsException( e -> {
assertThat( e ).hasMessageThat().isEqualTo( "Checked Exception!" );
});
It's basically the same as forEach ()
, except that you can throw a checked exception.
Retryer
A process for retrying a process that may fail. I think it's convenient, but most of the processes that require retries are provided with a retry function in the first place, so it's surprisingly useless.
Retryer
., ʻifReturns ()
, etc.Delay
. In most cases, you can use ʻexponentialBackoff ()` to double the retry interval.retryBlockingly ()
, retry ()
, retryAsync ()
.var result = new Retryer()
.upon( RuntimeException.class, Retryer.Delay.ofMillis( 100 ).exponentialBackoff( 2, 2 ) )
.retryBlockingly( this::mayFail );
assertThat( result ).isEqualTo( "success" );
CompletionStage<String> future = new Retryer()
.upon( RuntimeException.class, Retryer.Delay.ofMillis( 100 ).exponentialBackoff( 2, 2 ) )
.retry( this::mayFail, Executors.newSingleThreadScheduledExecutor() );
future.thenAccept( result -> {
assertThat( result ).isEqualTo( "success" );
});
To be honest, the asynchronous API is subtle.
Maybe
A class for wrapping processing that may raise an exception. Something like a version of java.util.Optional
with the ability to handle exceptions. Is Try
relatively close in Scala?
The main purpose seems to be to handle checked exceptions in streams easily.
maybe()
The basic method for creating a Maybe
instance. Accepts various functional interfaces.
var result = IntStream.range( 1, 10 ).boxed()
.map( Maybe.maybe( e -> {
if ( e <= 5 ) {
return e;
} else {
throw new Exception();
}
} ) )
.map( m -> m.orElse( e -> Integer.valueOf( 0 ) ) )
.collect( toList() );
assertThat( result ).containsExactly( 1, 2, 3, 4, 5, 0, 0, 0, 0 );
The value that caused the exception in ʻorElse ()is converted to
0`.
expect(() -> {
Maybe.maybe( () -> { throw new RuntimeException( "Unchecked!" );} );
}).throwsException( e -> {
assertThat( e ).hasMessageThat().isEqualTo( "Unchecked!" );
});
Unchecked exceptions are rethrown as is. Caution. It's not that it can be used for anything other than streams, but due to this limitation, I think it's better to use a dedicated library.
byValue()
var result = IntStream.range( 1, 10 ).boxed()
.map( Maybe.maybe( e -> {
if ( e <= 5 ) {
return e;
} else {
throw new Exception();
}
} ) )
.filter( Maybe.byValue( n -> n % 2 == 0 ) )
.map( m -> m.orElse( e -> Integer.valueOf( 0 ) ) )
.collect( toList() );
assertThat( result ).containsExactly( 2, 4, 0, 0, 0, 0 );
Since filter ()
will receive Maybe
, there is a method to unwrap it. As you can see, if an exception occurs, it moves on to the subsequent processing.
catching()
var result = IntStream.range( 1, 10 ).boxed()
.map( Maybe.maybe( e -> {
if ( e <= 5 ) {
return e;
} else {
throw new Exception();
}
} ) )
.flatMap( m -> m.catching( e -> {} ) )
.collect( toList() );
assertThat( result ).containsExactly( 1, 2, 3, 4, 5 );
Use when you just want to ignore the value.
Funnel
It's quite a niche to use, but it seems convenient if you get hooked.
,
Stream`) to another valueFunnel
var funnel = new Funnel<String>();
var batch = funnel.<String>through( data -> {
assertThat( data ).containsExactly( "batch1", "batch2" );
return data.stream().map( String::toUpperCase ).collect( toList() );
} );
var input = List.of( "local1", "batch1", "local2", "batch2" );
for ( var i : input ) {
if ( i.startsWith( "local" ) ) {
funnel.add( i );
} else {
batch.accept( i );
}
}
var result = funnel.run();
assertThat( result ).containsExactly( "local1", "BATCH1", "local2", "BATCH2" );
Funnel
through ()
method.Funnel.add ()
for values to be processed individually.Funnel.Batch.accept ()
.Funnel.run ()
.Note that ʻaccept ()` has an overload that takes a function that converts the result as an argument.
var funnel = new Funnel<String>();
var batch = funnel.<String>through( data -> List.of() );
batch.accept( "batch1" );
expect(() -> {
funnel.run();
}).throwsException( e -> {
assertThat( e ).isInstanceOf( IllegalStateException.class );
});
Obviously, if the size of the batch return list is different from the input, you will get an error.
var funnel = new Funnel<String>();
var batch = funnel.<String>through( data -> List.of() );
batch.accept( "batch1" );
expect(() -> {
funnel.run();
}).throwsException( e -> {
assertThat( e ).isInstanceOf( IllegalStateException.class );
});
The order depends on the order of batch processing. It is necessary to ensure that the process returns in the same order as the input order.
var funnel = new Funnel<String>();
var batch = funnel.<String>through( data -> {
var newList = new ArrayList<String>( data );
Collections.reverse( newList );
return newList;
} );
batch.accept( "batch1" );
batch.accept( "batch2" );
var result = funnel.run();
assertThat( result ).containsExactly( "batch2", "batch1" );
Parallelizer
As the name suggests, parallelize processing.
var input = Stream.of( "foo", "bar", "buz" );
var result = Collections.synchronizedList( new ArrayList<String>() );
new Parallelizer( executor, 3 )
.parallelize( input, result::add );
assertThat( result ).containsExactly( "foo", "bar", "buz" );
Parallelizer
. The arguments are ʻExecutorService to execute the process and the number of tasks that can be executed at the same time. Is it basically the same as the number of threads in ʻExecutor Service
?parallelize ()
or parallelizeUninterruptibly ()
. The first argument is Stream
or ʻIterator`, which is the input data, and the second argument is the processing to be performed.The process is blocked until it finishes. If an exception occurs in the middle, subsequent processing is interrupted.
According to the README
Parallel streams are for CPU-bound tasks. JDK has built-in magic to optimally use the available cores. Parallelizer is for IO-bound tasks.
It seems that it should be used for file input / output, external API call, etc.
BiStream
and MoreStreams
are convenientRecommended Posts