java8script
v1.1.1
Published
Implementing Java 8 features like optionals and streams in javascript (typescript)
Downloads
24
Readme
java8script
install through npm https://www.npmjs.com/package/java8script
Implementation of a Java 8 style Stream library in Javascript/Typescript. the implementation tries to follow as closely to the Stream interface in Java 8 as possible.
Also includes an Optional class that works the same as the Java8 optional
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html.
- Methods (more to come!)
Stream
A stream is a sequence of elements with possibly unlimited length and a sequence of 0 or more operations to be undertaken on the elements. Streams computations are lazy and only take place when necessary, rather than at the time they are declared.
The operations being undertaken on the elements of a stream can be thought of like a pipeline elements enter the start of the pipeline, and various processors, or nodes along the pipeline take elements, act on them and possibly pass outputs on to the rest of the pipeline.
a Stream pipeline consists of two types of operations:
Intermediate Operations: intermediate operations are lazy, they are not envoked until a terminal operation is created, they generally transform or remove elements in some way. intermediate operations come in 3 flavours, stateless, stateful, and short-circuiting stateless operations do not depend on previous results, and can be completely lazily computed, processing one element at a time on an is-needed basis. stateful operations on the other hand need access to previous elements of a pipeline in order to carry out a calculation, and because of this need to collect and process some or all elements of a stream before the rest of the pipeline can proceed. short-circuiting operations act as a guard or door, they stop elements from passing them in the pipeline, and have the benifit of turning a infinite stream into a finite one.
Terminal Operations: terminal operations are the final operation on a pipeline, they complete the circuit so to speak, when a terminal node is envoked all the processing of a stream takes place. example. terminal operations can also be short circuiting, in that they can cut an infinite stream of elements down to a finite stream.
Caution: short circuiting operations are only effective on a stateless pipeline, or one where each
stateful operations are first proceeded by a short circuiting one, otherwise an infinte loop can still happen.
for example consider an infinite stream S. S.findFirst()
will correctly short circuit and return the first item of the
stream. S.sorted().findFirst()
. on the other hand will infinitly loop as sorted()
tries to greedily consume elements
before proceeding. this could be remedied by first limiting the streams output. S.limit(10).sorted().findFirst()
Examples
Assume you have a large list of Employee
objects containing such data as name, salary, gender, jobTitle, yearsOfExperience, listOfPromotions.
Find list of all female developers
Stream.of(fullEmployeeList)
.filter(e => e.jobTitle === "DEVELOPER") // keeps only employees where jobtitle === "DEVELOPER"
.filter(e => e.gender === "FEMALE")
.toArray(); //terminal action, its not until this call that the stream is processed.
return the top 5 salaries in descending order
Stream.of(fullEmployeeList)
.map(emp => emp.salary) //now we have a stream of salaries
.sorted((salary1, salary2) => Comparator.default()(salary2, salary1)) //now we have a descending stream of salaries
.limit(5)
.toArray();
Note: because this stream contains a stateful operation sorted
it will need to process every value
find the total years of experience of all the people named "Matt" in the list
Stream.of(fullEmployeeList)
.filter(e => e.name === "Matt")
.map(e => e.yearsOfExperience)
.reduce((y1, y2) => y1 + y2);
find first promotion for a manager whose current salary is over 65,000
Stream.of(fullEmployeeList)
.filter(e => e.jobTitle === "MANAGER")
.filter(e => e.salary > 65000)
.map(e => e.listOfPromotions)
.flatMap(Stream.of) //alternatively there is .flatMapList(list => list)
.findFirst();
Note: because this is a fully stateless stream (no stateful intermediate operations) this stream will be lazily processed, only processing a minimum number of elements to produce a result. Meaning that not every employee will go through the filters/mapping functions. For example if the first employee in the list was a manager with over 65k salary, they would be the only element to be processed.
Create a list of the first 100 even numbers
Stream.iterate(2, (n) => n+2)
.limit(100)
.toArray();
Note: it is important when using functions like iterate that you properly terminate the stream with a short circuiting operation, as otherwise and infinite loop will occur.
Methods
Creates a new stream from the given source array
Stream.of<T>(source: T[]): Stream<T>;
Creates a new stream from the given source values
Stream.ofValues<T>(...values: T[]): Stream<T>;
creates a stream of a single element with the given source value;
Stream.ofValue<T>(value: T): Stream<T>;
creates an empty Stream
Stream.empty<T>(): Stream<T>;
generates a infinite stream where elements are generated by the given supplier.
Stream.generate<T>(supplier: Supplier<T>): Stream<T>;
creates an infinte stream of values by incrementally applying getNext to the last item in the stream, so you have a stream like: seed, getNext(seed), getNext(getNext(seed)), etc
Stream.iterate<T>(seed: T, getNext: Function<T, T>): Stream<T>;
creates a new stream consisting of all the values of s1, followed by all the values of s2
Stream.concat<T>(s1: Stream<T>, s2: Stream<T>): Stream<T>;
returns a stream of numbers starting at startInclusive, and going to up to but not including endExculsive in increments of 1, if a step is passed in, the increments of 1 will be changed to increments of size step, negative steps will be treated as positive.
IF the start is greater than the end, the default step will be -1 and any positive step values will be treated as negative i.e. 5 => -5, -5 => -5
an empty stream will be returned if start and end are the same
Stream.range(startInclusive: number, endExclusive: number, step?: number): Stream<number>;
returns a stream of numbers starting at startInclusive, and going to up to and including endInclusive in increments of 1, if a step is passed in, the increments of 1 will be changed to increments of size step
IF the start is greater than the end, the default step will be -1 and any positive step values will be treated as negative i.e. 5 => -5, -5 => -5
an empty stream will be returned if start and end are the same
Stream.rangeClosed(startInclusive: number, endInclusive: number, step?: number): Stream<number>;
Terminal Operation - Short Circuting: returns true if all items in the stream match the given predicate, if any item returns false, return false if the stream is empty, return true, the predicate is never evaluated.
allMatch(predicate: Predicate<T>): boolean;
Terminal Operation - Short Circuting: returns true if any 1 item in the stream match the given predicate, if any item returns true, return true, else false.
anyMatch(predicate: Predicate<T>): boolean;
Terminal Operation: returns the count of all the elements of the stream.
count(): number;
Terminal Operation: applies a mutable reduction operation to the elements in the collection using the given items, use of the combiner is not garenteed
customCollect<R>(supplier: Supplier<R>, accumulator: BiConsumer<R, T>, combiner: BiConsumer<R, R>): R;
Terminal Operation: applies a mutable reduction operation to the elements in the collection using the given collector
collect<R, A>(collector: Collector<T, A, R>): R;
Intermediate Operation - Stateful: return a distinct stream of elements according to the given equality function, if an equality function is not supplied, the BiPredicate.defaultEquality() function is used. This function is stateful because it needs to keep track of previous elements, but does not need access to the full stream before proceeding
distinct(equalsFunction?: BiPredicate<T, T>): Stream<T>;
Intermediate Operation: returns a stream whose elements are those from the current stream that match the given predicate function. Keep all elements who match the given predicate.
filter(predicate: Predicate<T>): Stream<T>;
Terminal Operation: Short Circuiting: Returns an optional describing the first element of the stream, if the stream is empty, return an empty Optional.
findFirst(): Optional<T>;
Terminal Operation: Short Circuiting: Returns an optional describing the an element of the stream, if the stream is empty, return an empty Optional.
findAny(): Optional<T>;
Intermediate Operation: A one to many mapping Function, returns a stream whos elements consist of the elements of all the output streams of the Function function.
flatMap<U>(Function: Function<T, Stream<U>>): Stream<U>;
Intermediate Operation: A one to many mapping Function, returns a stream whos elements consist of the elements of all the output lists of the Function function. same idea as flatMap but with standard arrays
flatMapList<U>(Function: Function<T, U[]>): Stream<U>;
Intermediate Operation: similar idea to flatMap or flatMapList, takes in a Function function that returns a optional, and returns a stream of actual values of the optional results that include a value, functionally equivelant to stream.map(Function).filter(o => o.isPresent()).map(o => o.get())
flatMapOptional<U>(Function: Function<T, Optional<U>>): Stream<U>;
Terminal Operation: applies a given consumer to each entity in the stream. elements are processed in sequental order
forEachOrdered(consumer: Consumer<T>): void;
Terminal Operation: applies a given consumer to each entity in the stream. ordering is not garenteed
forEach(consumer: Consumer<T>): void;
Intermediate Operation - Short Circuiting: returns a stream that consists of less than or equal to maxSize elements will create finite stream out of infinite stream.
limit(maxSize: number): Stream<T>;
Intermediate Operation: Returns a stream consisting of the results of applying the given function to the elements of this stream.
map<U>(Function: Function<T, U>): Stream<U>;
Terminal Operation: returns the largest element in the stream if the stream is not empty otherwise return Optional.empty() If a comparator is supplied to the function, it is used to find the largest value in the stream, if no comparator is supplied, a default comparator using the > and < operators is used.
max(comparator?: Comparator<T>): Optional<T>;
Terminal Operation: returns the smallest element in the stream if the stream is not empty otherwise return Optional.empty() If a comparator is supplied to the function, it is used to find the smallest value in the stream, if no comparator is supplied, a default comparator using the > and < operators is used.
min(comparator?: Comparator<T>): Optional<T>;
Terminal Operation - Short Circuting: returns true if no items in the stream match the given predicate, if any item predicate returns true, return false if the stream is empty, return true, the predicate is never evaluated
noneMatch(predicate: Predicate<T>): boolean;
Intermediate Operation: applies the given consumer to each item in the pipeline as an intermediate operation This function is mainly ment for debugging operations of a pipeline. Care should be taken that the values of the stream are not altered within the consumer, it should be a stateless and non altering function otherwise problems can be caused down the pipeline
peek(consumer: Consumer<T>): Stream<T>;
Terminal Operation: applies a reduction on the elements of the stream using the given accumulator function. returns an Optional describing the result if the stream have values. Optionally, an initial value can be specified, if the stream is empty, an optional describing the initial value will be returned.
reduce(accumulator: BiFunction<T>, initialValue?: T): Optional<T>;
returns a StreamIterator of the current stream, allowing easier step by step data retrieval from the stream
streamIterator(): StreamIterator<T>;
Intermediate Operation: Returns a stream consisting of all the value after discarding the first n values. If a negative number is passed in, no values are skipped.
skip(n: number): Stream<T>;
Intermediate Operation - Stateful: If comparator is passed in, it is used to sort the values in the stream, otherwise the default Comparator.default() comparator is used. and values are sorted in ascending order
sorted(comparator?: Comparator<T>): Stream<T>;
Terminal Operation: returns the Stream as an array of elements.
toArray(): T[];
StreamIterator
A Stream pipeline is designed to act on all its elements (or until a termination is reached) as a batch or group, because of this a stream iterator exists be be able to manually pull items out of a stream, processing them one at a time.
A StreamInterator is a basic Iterator interface consisting of three methods hasNext
to check if a next value exists, getNext
to get the next value in the iterator, and tryAdvance
a method where a Consumer
is passed in, and will be used to act on the next element in the iterator (if one exists).
Methods
Returns true if there is another value available in the iterator, false otherwise
hasNext(): boolean;
Returns a value bearing Optional
of the next value in the given iterator,
if there is no next value an empty Optional
will be returned.
getNext(): Optional<T>;
Takes a value consuming function and, if a next value exists within the iterator applies the consumer to the value and returns true. If no value exists the consumer function will not be called and false will be returned.
tryAdvance(consumer: Consumer<T>): boolean;
Optional
A container object which may or may not contain a non-null value. If a value is present, isPresent()
will return true and get()
will return the value.
Additional methods that depend on the presence or absence of a contained value are provided, such as orElse()
(return a default value if value not present) and ifPresent()
(execute a block of code if the value is present).
Methods
Returns an Optional with the specified present non-null value. Throws 'NullPointerException' if the value does not exist Use ofNullable when the value might not be present;
static of<U>(value: U): Optional<U>;
Returns an Optional describing the specified value, if non-null, otherwise returns an empty Optional.
static ofNullable = <U>(value?: U): Optional<U> => new Optional(value);
returns an empty Optional instance.
static empty<U>(): Optional<U>;
Return true if there is a value present, otherwise false.
isPresent(): boolean;
If a value is present in this Optional, returns the value, otherwise throws "NoSuchElementException".
get(): T;
If a value is present, and the value matches the given predicate, return an Optional describing the value, otherwise return an empty Optional.
filter(predicate: Predicate<T>): Optional<T>;
If a value is present, invoke the specified consumer with the value, otherwise do nothing.
ifPresent(consumer: Consumer<T>): void;
If a value is present, apply the provided Function function to it, and if the result is non-null, return an Optional describing the result. Otherwise return an empty Optional.
map<V>(Function: Function<T, V>): Optional<V>;
If a value is present, apply the provided Optional-bearing mapping function to it, return that result, otherwise return an empty Optional.
flatMap<V>(Function: Function<T, Optional<V>>): Optional<V>;
Return the value if present, otherwise return other.
orElse(other: T): T;
Return the value if present, otherwise invoke other and return the result of that invocation. if result of supplier is null, throw "NullPointerException"
orElseGet(supplier: Supplier<T>): T;
Return the contained value, if present, otherwise throw an error to be created by the provided supplier.
orElseThrow(exceptionSupplier: Supplier<Error>): T;
Map
Implementation of the java Map interface.
A Map is a object that maps keys to values, unlike regular JS objects, the keys of a Map can be any type of object or value (whereas JS objects can only have string on number keys). The implementation provided in this library is a HashMap
. (See Map.of
, or Map.empty
)
Entry
The Entry<K, V>
object encapsulates a key value pair used within a Map
. The interface contains 3 visible functions:
getValue(): V
: which returns the value stored in the Entry
getKey(): K
: returns the key stored in the entry
Entry.of(key: K, value: V): Entry<K, V>
creates a new entry with the given key and value
Methods
Map.empty<K, V>(): Map<K, V>
Map.of<K, V>(k1?: K, v1?: V, k2?: K, v2?: V, k3?: K, v3?: V, k4?: K, v4?: V, k5?: K, v5?: V): Map<K, V>
clear(): void;
get(key: K): V | null;
getOrDefault(key: K, defaultVal: V): V;
getOptional(key: K): Optional<V>;
put(key: K, value: V): V | null;
putIfAbsent(key: K, value: V): V | null;
putAll(map: Map<K, V>): void;
containsKey(key: K): boolean;
containsValue(value: V, equalityTest?: BiPredicate<V, V>): boolean;
keySet(): K[];
values(): V[];
entrySet(): Entry<K, V>[];
keyStream(): Stream<K>;
valueStream(): Stream<V>;
entryStream(): Stream<Entry<K, V>>;
forEach(consumer: BiConsumer<K, V>): void;
isEmpty(): boolean;
remove(key: K): V | null;
merge(key: K, value: V, remappingFunction: BiFunction<V>): V | null;
NumberSummaryStatistics
A state object for collecting statistics such as count, min, max, sum, and average. about a list of numbers
Methods
NumberSummaryStatistics.create(): NumberSummaryStatistics
accept(n: number): void
combine(other: NumberSummaryStatistics): void
getAverage(): number
getMin(): number
getMax(): number
getCount(): number
getSum(): number
Collectors
Firstly is the Collector
interface. A Collector is an abstraction of all the pieces needed to perform a mutable reduction operation. A mutable reduction operation is similar in concept to a normal reduction like Stream.reduce
above or array.reduce
, except that a mutable reduction uses an intermediate mutable container to collect values into before optionally performing a final transformation in the intermediate result. This can provide numerious computational efficencies over a default reduction as there is less copying of results, rather a building up of a single mutable result container.
a simple example might be concatinating a stream of arrays into a single output array
streamOfArrays.reduce((a1, a2) -> a1.concat(a2));
This will work, but it involves a lot of creation of new arrays, whereas a mutable reduction would create a single initial array (as this is a mutable container) and add all the arrays of the stream into it, rather than creating a new array after every array is added.
A Collector contains four types of of methods to do its job:
supplier()
: provides a Supplier
function that provides a new mutable container.
accumulator()
: provides a BiConsumer
function that takes in a mutable container and a value, and adds the value to the container.
combiner()
: provides a BiFunction
, that takes two mutable result containers and returns a the combined result.
finisher()
: provides a Function
that takes in a mutable result container and provides the final value.
NOTE: Performing a reduction operation with a Collector should produce a result equivalent to:
const container: C = collectors.supplier()();
for(let d in someData) {
collector.accumulator()(container, d);
}
return collector.finisher()(container);
Collectors
therefore is a grouping of useful and predefined collectors that can be used for all manner of things such as summing values, combining strings, pulling values into a array, etc.
Methods
Returns a Collector
that accumulates the input elements into a new array.
Collectors.toArray(): Collector<T, T[], T[]>;
Alias of toArray
: Returns a Collector
that accumulates the input elements into a new array.
Collectors.toList(): Collector<T, T[], T[]>;
Returns a Collector that concatenates the input elements into a String, in encounter order.
Collectors.joining(): Collector<string, _, string>
Returns a Collector that concatenates the input elements, separated by the specified delimiter, in encounter order.
delimiter
- the delimiter to be used between each element
Collectors.joining(delimiter: string): Collector<string, _, string>;
Returns a Collector that concatenates the input elements, separated by the specified delimiter, with the specified prefix and suffix, in encounter order.
delimiter
- the delimiter to be used between each element
prefix
- the sequence of characters to be used at the beginning of the joined result
suffix
- the sequence of characters to be used at the end of the joined result
Collectors.joining(delimiter: string, prefix: string, suffix: string): Collector<string, _, string>;
Returns a Collector
that produces the arithmetic mean of a number-valued function applied to the input elements.
mapper
: Function function to transform input elements into a number
Collectors.averagingNumber<I>(mapper: Function<I, number>): Collector<I, _, number>;
Returns a Collector
that produces the arithmetic mean of input numbers.
Collectors.averaging(): Collector<number, _, number>;
Returns a new Collector
, adapting the given downstream collector to incude an extra finishing transformation on the downstreams result.
downstream
: Initial collector to be applied, before applying the finisher
finisher
: function used to transform the results of the downstream collector
Collectors.collectingAndThen<I, M, A, O>(downStream: Collector<I, M, A>, finisher: Function<A, O>): Collector<I, M, O>
Returns a Collector accepting elements of type I that counts the number of input elements. If no elements are present, the result is 0.
Collectors.counting<I>(): Collector<I, _, number>
Returns a Collector
implementing a "group by" operation on input elements of type T,
grouping elements according to a classification function, and returning the results in a Map.
The classification function maps elements to some key type K. The collector produces a
Map<K, T[]> whose keys are the values resulting from applying the classification function to the input elements,
and whose corresponding values are arrays containing the input elements which map to the associated key under the classification function.
classifier
- the classifier function mapping input elements to keys
Collectors.groupingBy<T, K>(classifier: Function<T, K>): Collector<T, _, Map<K, T[]>>
Returns a Collector
implementing a cascaded "group by" operation on input elements of type T,
grouping elements according to a classification function, and then performing a reduction operation
on the values associated with a given key using the specified downstream Collector
.
The classification function maps elements to some key type K. The downstream collector operates
on elements of type T and produces a result of type D. The resulting collector produces a Map<K, D>.
classifier
- a classifier function mapping input elements to keys
downstream
- a Collector
implementing the downstream reduction
Collectors.groupingBy<T, K, A, D>(classifier: Function<T, K>, downstream: Collector<T, A, D>): Collector<T,_, Map<K, D>>;
Returns a Collector that will return the max value as determined by the default comparator
See, Comparator.default()
; the max value is returned in an optional, or empty if no value
was found.
Comparator.maxBy(): Collector<I, _, Optional<I>>
Returns a Collector that will return the max value as determined by the given comparator
the max value is returned in an Optional
an empty Optional
if no value was found.
comparator
- a Comparator
for comparing elements of type I
Collectrs.maxBy<I>(comparator: Comparator<I>): Collector<I, _, Optional<I>>;
Returns a Collector that will return the min value as determined by the given comparator
the min value is returned in an Optional
an empty Optional
if no value was found.
comparator
- a Comparator
for comparing elements of type I
Collectrs.minBy<I>(comparator: Comparator<I>): Collector<I, _, Optional<I>>;
Returns a Collector that will return the min value as determined by the default comparator
See, Comparator.default()
; the min value is returned in an Optional
, or empty if no value
was found.
Comparator.minBy(): Collector<I, _, Optional<I>>
Adapts a Collector accepting elements of type II to one accepting elements of type I by applying a mapping function to each input element before accumulation.
mapper
- a function to be applied to the input elements
downstream
- a collector which will accept mapped values
Collectors.mapping<I, II, A, R>(mapper: Function<I, II>, downstream: Collector<II, A, R>): Collector<I, A, R>
Returns a Collector
which partitions the input elements according to a Predicate
, and organizes them into a Map<Boolean, T[]>.
predicate
- a Predicate
used for classifying input elements
Collectors.partitioningBy<T, A, D>(predicate: Predicate<T>): Collector<T, _, Map<boolean, T[]>>
Returns a Collector
which partitions the input elements according to a Predicate
, reduces the values in each partition according to another Collector
, and organizes them into a Map<Boolean, D> whose values are the result of the downstream reduction.
predicate
- a Predicate
used for classifying input elements
downstream
- a Collector
implementing the downstream reduction
Collectors.partitioningBy<T, A, D>(predicate: Predicate<T>, downStream: Collector<T, A, D>): Collector<T, Map<boolean, T[]>, Map<boolean, D>>;
Returns a Collector
which performs a reduction of its input elements under a specified BinaryOperator
. The result is described as an Optional
.
reducer
- a BinaryOperator used to reduce the input elements
Collectors.reducing<I>(reducer: BiFunction<I>): Collector<I, I[], Optional<I>>
Returns a Collector
which performs a reduction of its input elements under a specified BinaryOperator
using the provided identity.
reducer
- a BinaryOperator
used to reduce the input elements
identity
- the identity value for the reduction (also, the value that is returned when there are no input elements)
Collectors.reducing<I>(reducer: BiFunction<I>, identity: I): Collector<I, I[], Optional<I>>
Returns a Collector
which performs a reduction of its input elements under a specified BinaryOperator
using the provided identity.
identity
- the identity value for the reduction (also, the value that is returned when there are no input elements)
mapper
- a mapping function to apply to each input value
reducer
- a BinaryOperator used to reduce the mapped values
reducing<I, U>(reducer: BiFunction<U>, identity: U, mapper: Function<I, U>): Collector<I, I[], Optional<U>>;
` ``
---
## Functions Types and Default methods
There are several core function types that are referenced throughout the documentation as well as used within the code itself, some of these functional types have useful static methods attached to them
#### Predicate
A function that takes in a single input of type I and returns a boolean result, used for stream actions like `filter`
#### BiPredicate
A function that takes two inputs of types I and O. and returns a boolean result, used for things like testing equality.
Returns a Bi predicate function uses `===` to test if the inputs are equivelant
```typescript
BiPredicate.defaultEquality()
Consumer
A function that takes in a value of type I, but does not return anything.
Returns a Consumer that logs the input to the console and nothing else.
Consumer.logger()
Returns a Consumer takes in an input and does nothing with it.
Consumer.sink()
BiConsumer
A function that takes two inputs of type I and U, but does not return any value.
Function
the most generic or basic function type, simply a function that takes in an input of type I and returns an output of type O.
Returns a Function that simply returns the input value
Function.identity()
Returns a Function takes in an input and logs the value, returning the same value;
Function.logger()
Supplier
A function that takes no inputs, but returns a value of type O.
BiFunction
A function that takes two inputs, both of type I and also returns a value of type T.
Comparator
A function that takes in two values of type T, and compares them for order. Returns a negative number, zero, or a positive number if the first argument is less than, equal to, or greater than the second.
Returns a Comparator that compares the given values with the < and > operators, returns -1 if first input is less that second input, +1 if first input is greater, and 0 if they are equal.
Comparator.default()