#Java #interview #Java 8 #Functional Programming
Interview Questions for Java 8
- What new features were introduced in Java 8 and JDK 8?
- What is a “lambda”? What is the structure and unique characteristics of using a lambda expression?
- What variables can lambda expressions access?
- How can you sort a list of strings using a lambda expression?
- What is a “method reference”?
- What types of method references do you know?
-
Explain the expression
System.out::println
. - What are “functional interfaces”?
-
What are the purposes of functional interfaces
Function<T,R>
,DoubleFunction<R>
,IntFunction<R>
, andLongFunction<R>
? -
What are the purposes of functional interfaces
UnaryOperator<T>
,DoubleUnaryOperator
,IntUnaryOperator
, andLongUnaryOperator
? -
What are the purposes of functional interfaces
BinaryOperator<T>
,DoubleBinaryOperator
,IntBinaryOperator
, andLongBinaryOperator
? -
What are the purposes of functional interfaces
Predicate<T>
,DoublePredicate
,IntPredicate
, andLongPredicate
? -
What are the purposes of functional interfaces
Consumer<T>
,DoubleConsumer
,IntConsumer
, andLongConsumer
? -
What are the purposes of functional interfaces
Supplier<T>
,BooleanSupplier
,DoubleSupplier
,IntSupplier
, andLongSupplier
? -
What is the purpose of the functional interface
BiConsumer<T,U>
? -
What is the purpose of the functional interface
BiFunction<T,U,R>
? -
What is the purpose of the functional interface
BiPredicate<T,U>
? -
What are the functional interfaces of the type
_To_Function
useful for? -
What are the purposes of functional interfaces
ToDoubleBiFunction<T,U>
,ToIntBiFunction<T,U>
, andToLongBiFunction<T,U>
? -
What are the purposes of functional interfaces
ToDoubleFunction<T>
,ToIntFunction<T>
, andToLongFunction<T>
? -
What are the purposes of functional interfaces
ObjDoubleConsumer<T>
,ObjIntConsumer<T>
, andObjLongConsumer<T>
? -
What is
StringJoiner
? -
What are
default
methods in an interface? -
How do you call a
default
method of an interface within a class that implements the interface? -
What is a
static
method in an interface? -
How do you call a
static
method in an interface? -
What is
Optional
? -
What is
Stream
? - What are the existing ways to create a stream?
-
What is the difference between
Collection
andStream
? -
What is the purpose of the
collect()
method in streams? -
What is the purpose of the
forEach()
andforEachOrdered()
methods in streams? -
What are the purposes of
map()
andmapToInt()
,mapToDouble()
,mapToLong()
methods in streams? -
What is the purpose of the
filter()
method in streams? -
What is the purpose of the
limit()
method in streams? -
What is the purpose of the
sorted()
method in streams? -
What are the purposes of
flatMap()
,flatMapToInt()
,flatMapToDouble()
,flatMapToLong()
methods in streams? - Discuss parallel processing in Java 8.
- What terminal methods for streams do you know?
- What intermediate methods for streams do you know?
-
How can you print 10 random numbers using
forEach()
? -
How can you print unique squares of numbers using the
map()
method? -
How can you print the count of empty strings using the
filter()
method? - How can you print 10 random numbers in ascending order?
- How can you find the maximum number in a set?
- How can you find the minimum number in a set?
- How can you get the sum of all numbers in a set?
- How can you get the average of all numbers?
- What additional methods for working with associative arrays (maps) were introduced in Java 8?
-
What is
LocalDateTime
? -
What is
ZonedDateTime
? - How can you get the current date using the Date Time API from Java 8?
- How can you add 1 week, 1 month, 1 year, or 10 years to the current date using the Date Time API?
- How can you get the next Tuesday using the Date Time API?
- How can you get the second Saturday of the current month using the Date Time API?
- How can you get the current time with millisecond precision using the Date Time API?
- How can you get the current time in local time with millisecond precision using the Date Time API?
- How can you define a repeatable annotation?
-
What is
Nashorn
? -
What is
jjs
? - What class was introduced in Java 8 for encoding/decoding data?
- How can you create a Base64 encoder and decoder?
What new features were introduced in Java 8 and JDK 8?
- Default interface methods;
- Lambda expressions;
- Functional interfaces;
- Method and constructor references;
- Repeating annotations;
- Type annotations;
- Reflection for method parameters;
- Stream API for working with collections;
- Parallel sorting of arrays;
- New API for working with date and time;
- New JavaScript engine Nashorn;
- Several new classes for thread-safe operations;
- New API for
Calendar
andLocale
; - Support for Unicode 6.2.0;
- Standard class for working with Base64;
- Support for unsigned arithmetic;
- Improved performance of
java.lang.String(byte[], *)
constructor andjava.lang.String.getBytes()
method; - A new implementation of
AccessController.doPrivileged
allowing a subset of privileges to be set without needing to check all other access levels; - Password-based algorithms became more resilient;
- Support for SSL/TLS Server Name Indication (SNI) in JSSE Server;
- Improved key storage (KeyStore);
- Added SHA-224 algorithm;
- The JDBC - ODBC bridge was removed;
- The PermGen space was removed, and the way metadata for classes is stored was changed;
- The ability to create profiles for the Java SE platform that includes not the entire platform but only part of it;
- Tooling:
- Added
jjs
utility for using JavaScript Nashorn; - The
java
command can run JavaFX applications; - Added
jdeps
utility for analyzing .class files.
- Added
What is a “lambda”? What is the structure and unique characteristics of using a lambda expression?
A lambda is a set of instructions that can be separated into a standalone variable and then called multiple times in different parts of the program.
The core of a lambda expression consists of a lambda operator, represented by the arrow ->
. This operator separates the lambda expression into two parts: the left part contains the parameter list of the expression, while the right part provides the body of the lambda expression, where all actions are executed.
A lambda expression doesn’t execute by itself; it forms an implementation of a method defined in a functional interface. Importantly, the functional interface must contain only one abstract method with no implementation.
interface Operationable {
int calculate(int x, int y);
}
public static void main(String[] args) {
Operationable operation = (x, y) -> x + y;
int result = operation.calculate(10, 20);
System.out.println(result); //30
}
In effect, lambda expressions are a sort of shorthand for the anonymous inner classes previously used in Java.
-
Deferred execution of lambda expressions - defined once in one part of the program, called as necessary, any number of times, and in any part of the program.
-
Lambda expression parameters must be of the same type as the parameters of the method in the functional interface:
operation = (int x, int y) -> x + y;
//When defining the lambda expression itself, the type of parameters may be omitted:
(x, y) -> x + y;
//If the method does not take any parameters, empty parentheses are written, for example:
() -> 30 + 20;
//If the method accepts only one parameter, parentheses can be omitted:
n -> n * n;
- Terminal lambda expressions do not have to return any value.
interface Printable {
void print(String s);
}
public static void main(String[] args) {
Printable printer = s -> System.out.println(s);
printer.print("Hello, world");
}
- Block lambda expressions are enclosed in curly braces. Block lambda expressions can utilize inner nested blocks, loops,
if
,switch
constructs, create variables, and so forth. If a block lambda expression is expected to return a value, thereturn
operator must be explicitly used:
Operationable operation = (int x, int y) -> {
if (y == 0) {
return 0;
}
else {
return x / y;
}
};
- Passing a lambda expression as a parameter to a method:
interface Condition {
boolean isAppropriate(int n);
}
private static int sum(int[] numbers, Condition condition) {
int result = 0;
for (int i : numbers) {
if (condition.isAppropriate(i)) {
result += i;
}
}
return result;
}
public static void main(String[] args) {
System.out.println(sum(new int[] {0, 1, 0, 3, 0, 5, 0, 7, 0, 9}, (n) -> n != 0));
}
What variables can lambda expressions access?
The access to variables in the outer scope from a lambda expression is very similar to access from anonymous objects. You can refer to:
- Immutable (effectively final - not necessarily marked as
final
) local variables; - Class fields;
- Static variables.
Access to default methods in the implementing functional interface is forbidden within the lambda expression.
How can you sort a list of strings using a lambda expression?
public static List<String> sort(List<String> list){
Collections.sort(list, (a, b) -> a.compareTo(b));
return list;
}
What is a “method reference”?
If an existing method within a class already does everything that is needed, you can use the mechanism of method reference to directly pass this method. Such a reference is passed in the form of:
ClassName::staticMethodName
for a static method;objectName::instanceMethodName
for an instance method;ClassName::new
for a constructor.
The result will be exactly the same as defining a lambda expression that calls that method.
private interface Measurable {
public int length(String string);
}
public static void main(String[] args) {
Measurable a = String::length;
System.out.println(a.length("abc"));
}
Method references are potentially more efficient than using lambda expressions. Additionally, they provide the compiler with better information about the type, and whenever there is an option to choose between using a method reference and using a lambda expression, method reference should always be preferred.
What types of method references do you know?
- to static methods;
- to instance methods;
- to constructors.
Explain the expression System.out::println
.
This expression illustrates the mechanism of instance method reference: passing a reference to the println()
method of the static field out
from the System
class.
What are “functional interfaces”?
A functional interface is an interface that defines only one abstract method.
To explicitly define an interface as functional, the annotation @FunctionalInterface
is added, working like @Override
. It denotes intention and prevents defining a second abstract method in the interface.
An interface can include any number of default
methods and still remain a functional interface since default
methods are not abstract.
What are the purposes of functional interfaces Function<T,R>
, DoubleFunction<R>
, IntFunction<R>
, and LongFunction<R>
?
Function<T, R>
is an interface used to implement a function that takes an instance of class T
as input and returns an instance of class R
.
Default methods can be used to build call chains (compose
, andThen
).
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123"); // "123"
DoubleFunction<R>
- a function that takesDouble
as input and returns an instance of classR
;IntFunction<R>
- a function that takesInteger
as input and returns an instance of classR
;LongFunction<R>
- a function that takesLong
as input and returns an instance of classR
.
What are the purposes of functional interfaces UnaryOperator<T>
, DoubleUnaryOperator
, IntUnaryOperator
, and LongUnaryOperator
?
UnaryOperator<T>
(Unary Operator) takes an object of type T
as a parameter, performs operations on it, and returns the result as an object of type T
:
UnaryOperator<Integer> operator = x -> x * x;
System.out.println(operator.apply(5)); // 25
DoubleUnaryOperator
- unary operator takingDouble
as input;IntUnaryOperator
- unary operator takingInteger
as input;LongUnaryOperator
- unary operator takingLong
as input.
What are the purposes of functional interfaces BinaryOperator<T>
, DoubleBinaryOperator
, IntBinaryOperator
, and LongBinaryOperator
?
BinaryOperator<T>
(Binary Operator) is an interface used to implement a function that takes two instances of class T
as input and returns an instance of class T
.
BinaryOperator<Integer> operator = (a, b) -> a + b;
System.out.println(operator.apply(1, 2)); // 3
DoubleBinaryOperator
- binary operator takingDouble
as input;IntBinaryOperator
- binary operator takingInteger
as input;LongBinaryOperator
- binary operator takingLong
as input.
What are the purposes of functional interfaces Predicate<T>
, DoublePredicate
, IntPredicate
, and LongPredicate
?
Predicate<T>
(Predicate) is an interface used to implement a function that takes an instance of class T
as input and returns a value of type boolean
.
The interface contains various default methods allowing for building complex conditions (and
, or
, negate
).
Predicate<String> predicate = (s) -> s.length() > 0;
predicate.test("foo"); // true
predicate.negate().test("foo"); // false
DoublePredicate
- predicate takingDouble
as input;IntPredicate
- predicate takingInteger
as input;LongPredicate
- predicate takingLong
as input.
What are the purposes of functional interfaces Consumer<T>
, DoubleConsumer
, IntConsumer
, and LongConsumer
?
Consumer<T>
(Consumer) is an interface used to implement a function that takes an instance of class T
, performs an action with it, and returns nothing.
Consumer<String> hello = (name) -> System.out.println("Hello, " + name);
hello.accept("world");
DoubleConsumer
- consumer takingDouble
as input;IntConsumer
- consumer takingInteger
as input;LongConsumer
- consumer takingLong
as input.
What are the purposes of functional interfaces Supplier<T>
, BooleanSupplier
, DoubleSupplier
, IntSupplier
, and LongSupplier
?
Supplier<T>
(Supplier) is an interface used to implement a function that takes no input but returns a result of class T
;
Supplier<LocalDateTime> now = LocalDateTime::now;
now.get();
DoubleSupplier
- supplier returningDouble
;IntSupplier
- supplier returningInteger
;LongSupplier
- supplier returningLong
.
What is the purpose of the functional interface BiConsumer<T,U>
?
BiConsumer<T,U>
represents an operation that accepts two arguments of classes T
and U
, performs an action with them, and returns nothing.
What is the purpose of the functional interface BiFunction<T,U,R>
?
BiFunction<T,U,R>
represents an operation that takes two arguments of classes T
and U
and returns a result of class R
.
What is the purpose of the functional interface BiPredicate<T,U>
?
BiPredicate<T,U>
represents an operation that takes two arguments of classes T
and U
and returns a result of type boolean
.
What are the functional interfaces of the type _To_Function
useful for?
DoubleToIntFunction
- operation that takes an argument of classDouble
and returns a result of typeInteger
;DoubleToLongFunction
- operation that takes an argument of classDouble
and returns a result of typeLong
;IntToDoubleFunction
- operation that takes an argument of classInteger
and returns a result of typeDouble
;IntToLongFunction
- operation that takes an argument of classInteger
and returns a result of typeLong
;LongToDoubleFunction
- operation that takes an argument of classLong
and returns a result of typeDouble
;LongToIntFunction
- operation that takes an argument of classLong
and returns a result of typeInteger
.
What are the purposes of functional interfaces ToDoubleBiFunction<T,U>
, ToIntBiFunction<T,U>
, and ToLongBiFunction<T,U>
?
ToDoubleBiFunction<T,U>
- operation that takes two arguments of classesT
andU
and returns a result of typeDouble
;ToLongBiFunction<T,U>
- operation that takes two arguments of classesT
andU
and returns a result of typeLong
;ToIntBiFunction<T,U>
- operation that takes two arguments of classesT
andU
and returns a result of typeInteger
.
What are the purposes of functional interfaces ToDoubleFunction<T>
, ToIntFunction<T>
, and ToLongFunction<T>
?
ToDoubleFunction<T>
- operation that takes an argument of classT
and returns a result of typeDouble
;ToLongFunction<T>
- operation that takes an argument of classT
and returns a result of typeLong
;ToIntFunction<T>
- operation that takes an argument of classT
and returns a result of typeInteger
.
What are the purposes of functional interfaces ObjDoubleConsumer<T>
, ObjIntConsumer<T>
, and ObjLongConsumer<T>
?
ObjDoubleConsumer<T>
- operation that takes two arguments of classesT
andDouble
, performs an action with them, and returns nothing;ObjLongConsumer<T>
- operation that takes two arguments of classesT
andLong
, performs an action with them, and returns nothing;ObjIntConsumer<T>
- operation that takes two arguments of classesT
andInteger
, performs an action with them, and returns nothing.
What is StringJoiner
?
The StringJoiner
class is used to create a sequence of strings, separated by a specified delimiter, with the ability to attach a prefix and suffix to the resulting string:
StringJoiner joiner = new StringJoiner(".", "prefix-", "-suffix");
for (String s : "Hello the brave world".split(" ")) {
joiner.add(s);
}
System.out.println(joiner); //prefix-Hello.the.brave.world-suffix
What are default
methods in an interface?
Java 8 allows adding non-abstract method implementations in an interface using the default
keyword:
interface Example {
int process(int a);
default void show() {
System.out.println("default show()");
}
}
- If a class implements an interface, it may, but is not obliged to, implement the
default
methods already implemented in the interface. The class inherits the default implementation. - If a class implements multiple interfaces that have the same default method, the class must implement the method with the matching signature itself. This is similar when one interface has a default method, and in another, the same method is abstract - no default implementation is inherited by the class.
- A default method cannot override a method from the
java.lang.Object
class. - They help implement interfaces without fearing disrupting the functionality of other classes.
- They can avoid the creation of utility classes since all necessary methods can be represented within the interfaces themselves.
- They allow classes to choose which method to override.
- One of the main reasons for implementing default methods is to enable collections in Java 8 to use lambda expressions.
How do you call a default
method of an interface within a class that implements the interface?
Using the super
keyword along with the interface name:
interface Paper {
default void show() {
System.out.println("default show()");
}
}
class Licence implements Paper {
public void show() {
Paper.super.show();
}
}
What is a static
method in an interface?
Static methods in interfaces are similar to default methods, except they cannot be overridden in the classes that implement the interface.
- Static methods in an interface are part of the interface and cannot be overridden for objects of the implementation class;
- Methods from the
java.lang.Object
class cannot be overridden as static methods; - Static methods in an interface are used to provide helper methods, such as null checks, sorting collections, etc.
How do you call a static
method in an interface?
Using the interface name:
interface Paper {
static void show() {
System.out.println("static show()");
}
}
class Licence {
public void showPaper() {
Paper.show();
}
}
What is Optional
?
An optional value in Optional
is a container for an object that may or may not contain a null
value. This wrapper is a convenient way to prevent NullPointerException
, as it has some higher-order functions that eliminate the need for repetitive if-null/notNull
checks:
Optional<String> optional = Optional.of("hello");
optional.isPresent(); // true
optional.ifPresent(s -> System.out.println(s.length())); // 5
optional.get(); // "hello"
optional.orElse("ops..."); // "hello"
What is Stream
?
The java.util.Stream
interface represents a sequence of elements that you can perform various operations on.
Stream operations are either intermediate or terminal. Terminal operations return a result of a specific type, while intermediate operations return the same stream. Therefore, you can build chains of several operations on the same stream.
A stream can have any number of calls to intermediate operations and ends with a terminal operation. All intermediate operations execute lazily, meaning that until a terminal operation is called, no actions really take place (similar to creating a Thread
or Runnable
object without calling start()
).
Streams are created based on various sources, such as classes from java.util.Collection
.
Associative arrays (maps), for example, HashMap
, are not supported.
Stream operations can be performed sequentially or in parallel.
Streams cannot be reused. Once a terminal operation has been called, the stream closes.
In addition to universal object-oriented streams, there are specific types of streams for dealing with primitive data types int
, long
, and double
: IntStream
, LongStream
, and DoubleStream
. These primitive streams operate similarly to regular object streams but have the following distinctions:
- They use specialized lambda expressions, e.g.,
IntFunction
orIntPredicate
instead ofFunction
andPredicate
; - They support additional terminal operations like
sum()
,average()
,mapToObj()
.
What are the existing ways to create a stream?
- From a collection:
Stream<String> fromCollection = Arrays.asList("x", "y", "z").stream();
- From specific values:
Stream<String> fromValues = Stream.of("x", "y", "z");
- From an array:
Stream<String> fromArray = Arrays.stream(new String[]{"x", "y", "z"});
- From a file (each line in the file becomes a separate element in the stream):
Stream<String> fromFile = Files.lines(Paths.get("input.txt"));
- From a string:
IntStream fromString = "0123456789".chars();
- Using
Stream.builder()
:
Stream<String> fromBuilder = Stream.builder().add("z").add("y").add("z").build();
- Using
Stream.iterate()
(infinite):
Stream<Integer> fromIterate = Stream.iterate(1, n -> n + 1);
- Using
Stream.generate()
(infinite):
Stream<String> fromGenerate = Stream.generate(() -> "0");
What is the difference between Collection
and Stream
?
Collections allow you to work with elements individually, while streams do not, but instead provide the ability to perform functions over data as a whole.
It’s also important to note the fundamental concept: a Collection
primarily represents a Data Structure. For instance, a Set
not only stores elements but embodies the idea of a collection with unique elements, whereas a Stream
is primarily an abstraction necessary for implementing a pipeline of computations, hence the results of a pipeline yield certain Data Structures or results of checks/searches, etc.
What is the purpose of the collect()
method in streams?
The collect()
method is a terminal operation that is used to represent the result as a collection or some other data structure.
collect()
takes a Collector<SourceType, AccumulatorType, ResultType>
as input, which comprises four stages: supplier - initializing the accumulator, accumulator - processing each element, combiner - combining two accumulators during parallel execution, [finisher] - an optional method for final processing of the accumulator. In Java 8, several common collectors have been implemented in the Collectors
class:
toList()
,toCollection()
,toSet()
- represent the stream as a list, collection, or set;toConcurrentMap()
,toMap()
- allow for transforming the stream into aMap
;averagingInt()
,averagingDouble()
,averagingLong()
- return the average value;summingInt()
,summingDouble()
,summingLong()
- return the sum;summarizingInt()
,summarizingDouble()
,summarizingLong()
- returnSummaryStatistics
with various aggregate values;partitioningBy()
- splits the collection into two parts based on adherence to a condition and returns them asMap<Boolean, List>
;groupingBy()
- splits the collection into several parts and returnsMap<N, List<T>>
;mapping()
- additional value transformations for complexCollector
-s.
It is also possible to create a custom collector via Collector.of()
:
Collector<String, List<String>, List<String>> toList = Collector.of(
ArrayList::new,
List::add,
(l1, l2) -> { l1.addAll(l2); return l1; }
);
What is the purpose of the forEach()
and forEachOrdered()
methods in streams?
- The
forEach()
method applies a function to each object of the stream, without guaranteeing order during parallel execution; - The
forEachOrdered()
method applies a function to each object of the stream while maintaining the order of elements.
What are the purposes of map()
and mapToInt()
, mapToDouble()
, mapToLong()
methods in streams?
The map()
method is an intermediate operation that transforms each element of the stream in a specified way.
mapToInt()
, mapToDouble()
, mapToLong()
are analogous to map()
but return the corresponding numeric stream (i.e., a stream composed of numeric primitives):
Stream
.of("12", "22", "4", "444", "123")
.mapToInt(Integer::parseInt)
.toArray(); //[12, 22, 4, 444, 123]
What is the purpose of the filter()
method in streams?
The filter()
method is an intermediate operation that takes a predicate which filters out all elements, returning only those that conform to the condition.
What is the purpose of the limit()
method in streams?
The limit()
method is an intermediate operation that allows for limiting the selection to a specified number of leading elements.
What is the purpose of the sorted()
method in streams?
The sorted()
method is an intermediate operation that allows for sorting values either in natural order or by specifying a Comparator
.
The order of elements in the original collection remains unchanged - sorted()
merely creates a sorted representation of it.
What are the purposes of flatMap()
, flatMapToInt()
, flatMapToDouble()
, flatMapToLong()
methods in streams?
The flatMap()
method is similar to map
, but it can create multiple objects from a single element. Therefore, each object will be transformed into zero, one, or multiple other objects supported by the stream. The most straightforward use case of this operation is transforming container elements with functions that return containers.
Stream
.of("H e l l o", "w o r l d !")
.flatMap((p) -> Arrays.stream(p.split(" ")))
.toArray(String[]::new);//["H", "e", "l", "l", "o", "w", "o", "r", "l", "d", "!"]
flatMapToInt()
, flatMapToDouble()
, flatMapToLong()
are analogous to flatMap()
, returning the corresponding numeric stream.
Discuss parallel processing in Java 8.
Streams can be sequential or parallel. Operations on sequential streams are executed in a single processor thread, while parallel streams utilize multiple processor threads. Parallel streams use a common ForkJoinPool
accessible via the static ForkJoinPool.commonPool()
method. If the environment is not multi-core, the stream will execute sequentially. Essentially, using parallel streams means that data in the streams will be divided into parts, each part processed on a separate processor core, and then those parts are merged back together, and terminal operations are performed on them.
You can also create a parallel stream from a collection using the parallelStream()
method of the Collection
interface.
To make a regular sequential stream parallel, call the parallel()
method on the Stream
object. The isParallel()
method allows you to check whether the stream is parallel.
Using the parallel()
and sequential()
methods enables you to determine which operations can be parallelized and which should remain sequential. It is also possible to convert any sequential stream into a parallel one and vice versa:
collection
.stream()
.peek(...) // this operation is sequential
.parallel()
.map(...) // this operation can run in parallel,
.sequential()
.reduce(...) // this operation is sequential again
Typically, elements are passed to the stream in the order in which they are defined in the source data. When working with parallel streams, the system retains the sequence of elements. An exception is the forEach()
method, which may output elements in an arbitrary order. To maintain the sequence, the forEachOrdered()
method must be employed.
Criteria that can influence performance in parallel streams include:
- Size of data - the larger the data, the more challenging it will be to first separate the data and then combine it.
- Number of processor cores. Theoretically, the more cores on the machine, the faster the program will run. If only one core is present, there’s no point in using parallel streams.
- Simpler data structures will allow operations to proceed faster with streams. For instance, data from
ArrayList
is easy to use because its structure assumes the sequence of unrelated data. In contrast, aLinkedList
is not an optimal option since, in a sequential list, all elements are linked to previous/subsequent ones. Hence, such data is difficult to parallelize. - Operations over primitive types will be performed faster than over class objects.
- It is highly inadvisable to use parallel streams for long operations (like network connections) since all parallel streams work with a single ForkJoinPool; long operations may halt the work of all parallel streams in the JVM due to a lack of available threads in the pool. Therefore, parallel streams should only be used for shorter operations, where timing is in milliseconds, but not for those that may take seconds or even minutes.
- Retaining order in parallel streams increases costs during execution. If order is unimportant, you have the chance to disable order retention and thus increase performance using the intermediate operation
unordered()
:
collection.parallelStream()
.sorted()
.unordered()
.collect(Collectors.toList());
What terminal methods for streams do you know?
findFirst()
returns the first element;findAny()
returns any suitable element;collect()
presents results in the form of collections and other data structures;count()
returns the number of elements;anyMatch()
returnstrue
if the condition is met for at least one element;noneMatch()
returnstrue
if the condition is not met for any element;allMatch()
returnstrue
if the condition is met for all elements;min()
returns the minimum element usingComparator
as the condition;max()
returns the maximum element usingComparator
as the condition;forEach()
applies a function to each object (the order is not guaranteed during parallel execution);forEachOrdered()
applies a function to each object while maintaining element order;toArray()
returns an array of values;reduce()
allows for executing aggregate functions and returning a single result.
For numeric streams, additional methods are available:
sum()
returns the sum of all numbers;average()
returns the average of all numbers.
What intermediate methods for streams do you know?
filter()
filters entries, returning only those entries that adhere to the condition;skip()
allows you to skip a specified number of elements at the beginning;distinct()
returns a stream without duplicates (based onequals()
);map()
transforms each element;peek()
returns the same stream, applying a function to each element;limit()
allows you to restrict the selection to a specified number of leading elements;sorted()
allows you to sort values either in natural order or by specifying aComparator
;mapToInt()
,mapToDouble()
,mapToLong()
- analogs ofmap()
returning streams of numeric primitives;flatMap()
,flatMapToInt()
,flatMapToDouble()
,flatMapToLong()
- similar tomap()
, but can create multiple objects from a single element.
For numeric streams, an additional method mapToObj()
is available, which transforms a numeric stream back into an object stream.
How can you print 10 random numbers using forEach()
?
(new Random())
.ints()
.limit(10)
.forEach(System.out::println);
How can you print unique squares of numbers using the map()
method?
Stream
.of(1, 2, 3, 2, 1)
.map(s -> s * s)
.distinct()
.forEach(System.out::println);
How can you print the count of empty strings using the filter()
method?
System.out.println(
Stream
.of("Hello", "", ", ", "world", "!")
.filter(String::isEmpty)
.count());
How can you print 10 random numbers in ascending order?
(new Random())
.ints()
.limit(10)
.sorted()
.forEach(System.out::println);
How can you find the maximum number in a set?
Stream
.of(5, 3, 4, 55, 2)
.mapToInt(a -> a)
.max()
.getAsInt(); //55
How can you find the minimum number in a set?
Stream
.of(5, 3, 4, 55, 2)
.mapToInt(a -> a)
.min()
.getAsInt(); //2
How can you get the sum of all numbers in a set?
Stream
.of(5, 3, 4, 55, 2)
.mapToInt()
.sum(); //69
How can you get the average of all numbers?
Stream
.of(5, 3, 4, 55, 2)
.mapToInt(a -> a)
.average()
.getAsDouble(); //13.8
What additional methods for working with associative arrays (maps) were introduced in Java 8?
putIfAbsent()
adds a key-value pair only if the key was absent:
map.putIfAbsent("a", "Aa");
forEach()
takes a function that performs actions on each element:
map.forEach((k, v) -> System.out.println(v));
compute()
creates or updates the current value based on calculations (it can use both the key and the current value):
map.compute("a", (k, v) -> String.valueOf(k).concat(v)); //["a", "aAa"]
computeIfPresent()
if the key exists, updates the current value based on calculations (it can use both the key and the current value):
map.computeIfPresent("a", (k, v) -> k.concat(v));
computeIfAbsent()
if the key is absent, it creates it with a value that is computed (it can use the key):
map.computeIfAbsent("a", k -> "A".concat(k)); //["a","Aa"]
getOrDefault()
returns the specified default value if the key is absent:
map.getOrDefault("a", "not found");
merge()
takes a key, value, and a function that combines the passed and current values. If there’s no value under the specified key, it writes the passed value.
map.merge("a", "z", (value, newValue) -> value.concat(newValue)); //["a","Aaz"]
What is LocalDateTime
?
LocalDateTime
combines LocaleDate
and LocalTime
, containing both date and time in the ISO-8601 calendar system without being bound to a time zone. The time is stored with precision up to nanoseconds. It contains many convenient methods, such as plusMinutes, plusHours, isAfter, toSecondOfDay, etc.
What is ZonedDateTime
?
java.time.ZonedDateTime
is analogous to java.util.Calendar
, a class with the most complete range of information about the time context in the ISO-8601 calendar system. It includes a time zone, hence all operations with time shifts conducted by this class consider it.
How can you get the current date using the Date Time API from Java 8?
LocalDate.now();
How can you add 1 week, 1 month, 1 year, or 10 years to the current date using the Date Time API?
LocalDate.now().plusWeeks(1);
LocalDate.now().plusMonths(1);
LocalDate.now().plusYears(1);
LocalDate.now().plus(1, ChronoUnit.DECADES);
How can you get the next Tuesday using the Date Time API?
LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.TUESDAY));
How can you get the second Saturday of the current month using the Date Time API?
LocalDate
.of(LocalDate.now().getYear(), LocalDate.now().getMonth(), 1)
.with(TemporalAdjusters.nextOrSame(DayOfWeek.SATURDAY))
.with(TemporalAdjusters.next(DayOfWeek.SATURDAY));
How can you get the current time with millisecond precision using the Date Time API?
new Date().toInstant();
How can you get the current time in local time with millisecond precision using the Date Time API?
LocalDateTime.ofInstant(new Date().toInstant(), ZoneId.systemDefault());
How can you define a repeatable annotation?
To define a repeatable annotation, you need to create a container annotation for a list of repeatable annotations and designate the repeatable meta-annotation with @Repeatable
:
@interface Schedulers
{
Scheduler[] value();
}
@Repeatable(Schedulers.class)
@interface Scheduler
{
String birthday() default "Jan 8 1935";
}
What is Nashorn
?
Nashorn is a JavaScript engine developed in Java by Oracle. Its purpose is to allow the embedding of JavaScript code within Java applications. Compared to Rhino, maintained by the Mozilla Foundation, Nashorn offers 2 to 10 times higher performance since it compiles code and directly hands over bytecode to the Java Virtual Machine in memory. Nashorn can compile JavaScript code and generate Java classes, which are loaded via a special class loader. It is also possible to call Java code directly from JavaScript.
What is jjs
?
jjs
is a command-line utility that allows you to run JavaScript programs directly from the console.
What class was introduced in Java 8 for encoding/decoding data?
Base64
is a thread-safe class that implements a data encoder and decoder using the base64 encoding scheme according to RFC 4648 and RFC 2045.
Base64 contains 6 main methods:
getEncoder()
/getDecoder()
- returns the base64 encoder/decoder that corresponds to the RFC 4648 standard;
getUrlEncoder()
/getUrlDecoder()
- returns a URL-safe base64 encoder/decoder, corresponding to the RFC 4648 standard;
getMimeEncoder()
/getMimeDecoder()
- returns a MIME encoder/decoder that corresponds to the RFC 2045 standard.
How can you create a Base64 encoder and decoder?
// Encode
String b64 = Base64.getEncoder().encodeToString("input".getBytes("utf-8")); //aW5wdXQ==
// Decode
new String(Base64.getDecoder().decode("aW5wdXQ=="), "utf-8"); //input