Java 8 key features
Release
date - 18th March 2014
Key features are given below,
1. forEach()
method in Iterable interface
2. default and
static methods in Interfaces
3. Functional
Interfaces and Lambda Expressions
4. Java Stream API
for Bulk Data Operations on Collections
5. Java Time API
6. Collection API
improvements
7. Concurrency API
improvements
8. Java IO
improvements
9. Miscellaneous
Core API improvements.
Let’s have a brief
look on these Java 8 features. I will provide some code snippets for better
understanding, so if you want to run programs in Java 8, you will have to setup
Java 8 environment by following steps.
·
Download JDK8 and install it. Installation is
simple like other java versions. JDK installation is required to write, compile
and run the program in Java.
·
Current Eclipse IDE doesn’t support Java8, so you will have to
download it from efxclipse.org Eclipse for Java 8. There are different
versions for Mac OS, Windows and Linux systems with stable builds, so download
the latest one for most features.
·
I just checked today (15-Apr-2014) and Eclipse Kepler 4.3.2 SR2
package can be used for Java 8. You need to download it first and then install
“Java 8 support for Eclipse Kepler SR2″ plugin from Eclipse Marketplace. I have
tried this and it seems to be working fine.
1. forEach() method in
Iterable interface
Whenever we need to traverse through a Collection, we need to
create an Iterator whose whole purpose is to iterate over and then we have
business logic in a loop for each of the elements in the Collection. We might
get ConcurrentModificationException if iterator is not
used properly.
Java 8 has introduced forEach method
in java.lang.Iterable interface so
that while writing code we focus on business logic only. forEach method takes java.util.function.Consumer object as
argument, so it helps in having our business logic at a separate location that
we can reuse. Let’s see forEach usage with simple example.
Java8ForEachExample.java
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
package com.journaldev.java8.foreach;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.lang.Integer;
public class Java8ForEachExample {
public static void
main(String[] args) {
//creating
sample Collection
List
myList = new ArrayList();
for(int i=0; i<10 i="" mylist.add="" span="">10>
//traversing
using Iterator
Iterator
it = myList.iterator();
while(it.hasNext()){
Integer
i = it.next();
System.out.println("Iterator
Value::"+i);
}
//traversing
through forEach method of Iterable with anonymous class
myList.forEach(new Consumer() {
public void
accept(Integer t) {
System.out.println("forEach
anonymous class Value::"+t);
}
});
//traversing
with Consumer interface implementation
MyConsumer
action = new MyConsumer();
myList.forEach(action);
}
}
//Consumer implementation that can be reused
class MyConsumer implements
Consumer{
public void
accept(Integer t) {
System.out.println("Consumer
impl Value::"+t);
}
}
|
The number of lines
might increase but forEach method helps in having the logic for iteration and
business logic at separate place resulting in higher separation of concern and
cleaner code.
2. default and static
methods in Interfaces
If you read forEach method details carefully, you will notice
that it’s defined in Iterable interface but we know that interfaces can’t have
method body. From Java 8, interfaces are enhanced to have method with
implementation. We can use ‘default‘ and ‘static‘ keyword to create interfaces with method
implementation. forEach method implementation in Iterable interface is:
1
2
3
4
5
6
|
default void forEach(Consumer T>
action) {
Objects.requireNonNull(action);
for
(T t : this) {
action.accept(t);
}
}
|
We know that Java doesn’t provide multiple inheritance in Classes because it leads
to Diamond Problem. So how it will be handled with
interfaces now, since interfaces are now similar to abstract classes. The
solution is that compiler will throw exception in this scenario and we will
have to provide implementation logic in the class implementing the interfaces.
Interface1.java
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package com.journaldev.java8.defaultmethod;
@FunctionalInterface
public interface Interface1 {
void
method1(String str);
default void
log(String str){
System.out.println("I1
logging::"+str);
}
static void
print(String str){
System.out.println("Printing
"+str);
}
//trying to override Object method
gives compile time error as
//"A default method cannot
override a method from java.lang.Object"
// default String toString(){
// return "i1";
// }
}
|
Interface2.java
|
1
2
3
4
5
6
7
8
9
10
11
12
|
package com.journaldev.java8.defaultmethod;
@FunctionalInterface
public interface Interface2 {
void
method2();
default void
log(String str){
System.out.println("I2
logging::"+str);
}
}
|
Notice that both
the interfaces have a common method log() with implementation logic.
MyClass.java
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package com.journaldev.java8.defaultmethod;
public class MyClass implements
Interface1, Interface2 {
@Override
public void
method2() {
}
@Override
public void
method1(String str) {
}
//MyClass won't compile without
having it's own log() implementation
@Override
public void
log(String str){
System.out.println("MyClass
logging::"+str);
Interface1.print("abc");
}
}
|
As you can see that Interface1 has static method implementation that is used in MyClass.log() method implementation. Java 8 uses default and static methods
heavily in Collection API and
default methods are added so that our code remains backward compatible.
If any class in the
hierarchy has a method with same signature, then default methods become
irrelevant. Since any class implementing an interface already has Object as
superclass, if we have equals(), hashCode() default methods in interface, it
will become irrelevant. Thats why for better clarity, interfaces are not allowed
to have Object class default methods.
Why we
need Default Methods?
Why
would one want to add methods into Interfaces? We’ll it is because interfaces
are too tightly coupled with their implementation classes. i.e. it is not
possible to add a method in interface without breaking the implementor class.
Once you add a method in interface, all its implemented classes must declare
method body of this new method.
Since Java 8, things started getting ugly. A new feature Lambda was introduce which is cool. However
it is not possible to use this feature in existing Java libraries such as
java.util package. If you add a single method in interface List
, it
breaks everything. You need to add its implementation in every class that
implements List
interface. Imagine in real world how many
custom classes would change.
So for backward compatibility, Java 8 cleverly added Default Methods.
Virtual
Extension Methods
It added a new concept Virtual extension methods,
or as they are often called defender methods,
can now be added to interfaces providing a default implementation of the
declared behavior. So existing interfaces can be augmented without compromising
backward compatibility by adding extension methods to the interface, whose
declaration would contain instructions for finding the default implementation
in the event that implementors do not provide a method body. A key
characteristic of extension methods is that they are virtual methods just like
other interface methods, but provide a default implementation in the event that
the implementing class does not provide a method body.
Consider
following example:
interface
Person
{
//adds a java 8 default method
default void
sayHello()
{
System.out.println("Hello
there!");
}
}
class
Sam
implements Person
{
}
public
class
Main
{
public static
void
main(String
[] args) {
Sam
sam = new Sam();
//calling
sayHello method calls the method
//defined
in interface
sam.sayHello();
}
}
|
Output:
In above code we added a defender method sayHello()
in Person
interface. So it was ok for classSam
to avoid declaring this methods body.
What about
Multiple Inheritance?
Adding
method definitions in interfaces can add ambiguity in multiple inheritance.
isn’t it? Well, it does. However Java 8 handle this issue at Compile type.
Consider below example:
interface
Person
{
default void
sayHello()
{
System.out.println("Hello");
}
}
interface
Male
{
default void
sayHello()
{
System.out.println("Hi");
}
}
class
Sam
implements Person,
Male {
}
|
In this
example we have same defender method sayHello in both interfaces Person and
Male. Class Sam implements these interfaces. So which version of sayHello will
be inherited? We’ll if you try to compile this code in Java 8, it will give
following error.
class Sam inherits unrelated defaults for sayHello() from types Person and Male
class Sam implements Person, Male { ^ 1 error
So that solves multiple inheritance problem. You cannot
implement multiple interfaces having same signature of Java 8 default methods (without overriding explicitly in
child class).
We can solve the above problem by overriding sayHello method in
class Sam
.
interface
Person
{
default void
sayHello()
{
System.out.println("Hello");
}
}
interface
Male
{
default void
sayHello()
{
System.out.println("Hi");
}
}
class
Sam
implements Person,
Male {
//override the sayHello to
resolve ambiguity
void sayHello()
{
}
}
|
It is also possible to explicitly call method from child class
to parent interface. Consider in above example you want to call sayHello
method from Male interface when Sam.sayHello
is called. You can use super keyword to
explicitly call the appropriate method.
class
Sam
implements Person,
Male {
//override the sayHello to
resolve ambiguity
void sayHello()
{
Male.super.sayHello();
}
}
|
3. Functional Interfaces and
Lambda Expressions
If you notice above interfaces code, you will notice
@FunctionalInterface annotation. Functional interfaces are new concept
introduced in Java 8. An interface with exactly one abstract method becomes
Functional Interface. We don’t need to use @FunctionalInterface annotation to
mark an interface as Functional Interface. @FunctionalInterface annotation is a
facility to avoid accidental addition of abstract methods in the functional
interfaces. You can think of it like @Override annotation and it’s best practice to use
it. java.lang.Runnable with single
abstract method run() is a great example of functional interface.
One of the major benefits of functional interface is the
possibility to use lambda expressions to
instantiate them. We can instantiate an interface with anonymous class but the code looks bulky.
1
2
3
4
5
|
Runnable r = new
Runnable(){
@Override
public void
run() {
System.out.println("My
Runnable");
}};
|
Since functional
interfaces have only one method, lambda expressions can easily provide the
method implementation. We just need to provide method arguments and business
logic. For example, we can write above implementation using lambda expression
as:
1
2
3
|
Runnable r1 = () -> {
System.out.println("My
Runnable");
};
|
If you have single
statement in method implementation, we don’t need curly braces also. For
example above Interface1 anonymous class can be instantiated using lambda as follows:
1
2
3
|
Interface1 i1 = (s) -> System.out.println(s);
i1.method1("abc");
|
So lambda
expressions are means to create anonymous classes of functional interfaces
easily. There are no runtime benefits of using lambda expressions, so I will
use it cautiously because I don’t mind writing few extra lines of code.
A new package java.util.function has been added with bunch of functional interfaces
to provide target types for lambda expressions and method references. Lambda
expressions are a huge topic, I will write a separate article on that in
future.
Why Java needs Lambda Expressions?
Since
its beginning, the Java language hasn’t evolved much if you ignore some of the
features like Annotations, Generics etc. Mostly during its life Java always
remained Object first language. After working with functional language like
JavaScript, it becomes clear to one how Java enforce its strict object-oriented
nature and strict typed on the source code. You see Functions are not important
for Java. On their own they cannot live in Java world.
Functions
are first class citizens in a functional programming language. They exists on
their own. You can assign them to a variable and pass them as arguments to
other functions. JavaScript is one of the best example of an FP language. There
are some good articles here and here that clearly describes the benefits of
JavaScript as a functional language. A functional language provides very
powerful feature called Closure that has quite a few advantages over
traditional way of writing applications. A closure is a function or reference
to a function together with a referencing environment — a table storing a
reference to each of the non-local variables of that function. Closest thing
that Java can provide to Closure is Lambda expressions. There is significant
difference between a Closure and Lambda expression, but at least Lambda
expression provides a good alternative to Closure.
In his quite sarcastic and funny blog post, Steve
Yegge describes how Java world is strictly about Nouns. If you haven’t read his
blog, go first read it. It’s funny, its interesting and it describe the exact
reason why Java had to add Lambda expressions.
Lambda
expression adds that missing link of functional programming to Java. Lambda
expression let us have functions as first class citizen. Although this is not
100% correct, we will shortly see how Lambda expressions are not closures but
they are as much close as we can get to closures. In languages that support
first class functions, the type of the lambda expression would be a function;
but in Java, the lambda expressions are represented as objects, and so they
must be bound to a particular object type known as a functional interface. We
will see in detail what Functional interface are.
Here is a nicely written article by Mario Fusco on Why we need Lambda
Expression in Java. He explains why a modern programming language must have
feature like closures.
Introduction to
Lambda Expressions
A
lambda expression is an anonymous function (not 100% true for Java but lets
assume it for time being). Simply put, it’s a method without a declaration,
i.e., access modifier, return value declaration, and name.
It’s a
shorthand that allows you to write a method in the same place you are going to
use it. Especially useful in places where a method is being used only once, and
the method definition is short. It saves you the effort of declaring and
writing a separate method to the containing class.
Lambda expressions in Java is usual written using syntax(argument) -> (body)
. For
example:
(arg1,
arg2...) -> { body }
(type1
arg1, type2 arg2...) -> { body }
|
Following
are some examples of Lambda expressions.
(int
a,
int b)
-> { return a + b; }
()
-> System.out.println("Hello World");
(String
s) -> { System.out.println(s); }
()
-> 42
()
-> { return 3.1415
};
|
Structure of
Lambda Expressions
Let’s
check the structure of lambda expressions.
·
A lambda expression can have zero, one or more parameters.
·
The type of the parameters can be explicitly declared or it can
be inferred from the context. e.g. (int a)
is same
as just (a)
·
Parameters are enclosed in parentheses and separated by commas.
e.g. (a, b)
or (int a, int
b)
or (String a, int b, float c)
·
Empty parentheses are used to represent an empty set of
parameters. e.g. () -> 42
·
When there is a single parameter, if its type is inferred, it is
not mandatory to use parentheses. e.g. a -> return a*a
·
The body of the lambda expressions can contain zero, one or more
statements.
·
If body of lambda expression has single statement curly brackets
are not mandatory and the return type of the anonymous function is the same as
that of the body expression.
·
When there is more than one statement in body than these must be
enclosed in curly brackets (a code block) and the return type of the anonymous
function is the same as the type of the value returned within the code block,
or void if nothing is returned.
What are
Functional Interfaces
In
Java, a Marker interface is an interface with no methods or fields declaration.
In simple words, marker interface is an empty interface. Similarly, a
Functional Interface is an interface with just one abstract method declared in
it.
java.lang.Runnable
is an example of a Functional Interface.
There is only one method void run()
declared in Runnable
interface. Similarly ActionListener interface is also a Functional Interface.
We use Anonymous inner classes to instantiate objects of functional interface.
With Lambda expressions, this can be simplified.
Each
lambda expression can be implicitly assigned to one of the interface called
Functional interface. For example we can create Runnable interface’s reference
from lambda expression like below:
Runnable
r = () -> System.out.println("hello world");
|
This
type of conversion is automatically taken care by compiler when we dont specify
the functional interface. For example:
new
Thread(
() -> System.out.println("hello
world")
).start();
|
So in above code, compiler automatically deduced that lambda expression
can be casted to Runnable interface from Thread class’s constructor signature public
Thread(Runnable r) { }
.
Few
examples of lambda expressions and their functional interface:
Consumer
c = (int x)
-> { System.out.println(x) };
BiConsumer<Integer,
String> b = (Integer x, String y) -> System.out.println(x + " :
" +
y);
Predicateng>
p = (String s) -> { s == null
|
};
@FunctionalInterface is a new interface added in Java 8 to
indicate that an interface type declaration is intended to be a functional
interface as defined by the Java Language Specification. Java 8 also declared
number of Functional Interfaces that can be used by Lambda expressions.
@FunctionalInterface can be used for compiler level errors when the interface
you have annotated is not a valid Functional Interface.
Following
is an example of custom defined Functional interface.
@FunctionalInterface
public
interface
WorkerInterface
{
pu blic v oid
doSomeWork();
}
|
As its
definition says, Functional Interfaces can have only one abstract method. If
you try to add one more abstract method in it, it throws compile time error.
For example:
@FunctionalInterface
public
interface
WorkerInterface
{
pu
blic v oid
doSomeWork();
public void doS
omeMoreWork();
}
|
Error:
Unexpected
@FunctionalInterface annotation
@FunctionalInterface
^ WorkerInterface is not a functional interface multiple
non-overriding
abstract methods found in interface WorkerInterface 1 error
|
Once
the Functional interface is defined, we can simply use it in our API and take
advantage of Lambda expressions. For example:
//define
a functional interface
@FunctionalInterfac
e
pu
blic
interface
WorkerInterface
{
pu
blic v oid
doSomeWork();
}
public
clas s
WorkerInterfaceTest
{
public static
void
execute(WorkerInterface
work
er) {
worker.doSomeWork();
}
public static
void
main(Str ing
[] args) {
//invoke
doSomeWork using Annonymous class
execute(n
ew Worke
rInterf
ace()
{
@Override
public
void
doSomeWork()
{
System.out.println("Worker
invoked using Anonymous class");
}
});
//invoke
doSomeWork using Lambda expression
exec ute(
() -> System.out.println("Worker invoked using Lambda expression")
)
;
}
}
|
Output:
Worker
|
invoked using Anonymous class
Worker
invoked using Lambda expression
|
Here we
created our own Functional interface and used to with lambda expressions.
execute() method can now take lambda expressions as argument.
Examples of
Lambda Expression
Best
way of learning about Lambda expressions is by examples. Following are few
examples:
Thread
can be initialized like following
:
ld
way:
new
Thread(new
Runnable()
{
@Override
public void
run()
{
System.out.println("Hello
from thread");
}
}).start();
//New
way:
new
Thread(
() -> System.out.println("Hello
from thread")
).start();
|
The
event handling can be done with Java 8 using lambda expression. Following code
we show both old and new way of adding ActionListener to a UI component.
//Old
way:
button.addActionListener(new
ActionListener()
{
@Override
public void
actionPerformed(ActionEvent
e) {
System.out.println("The
button was clicked using old fashion code!");
}
});
//New
way:
button.addActionListener(
(e) -> {
System.out.println("The
button was clicked. From lambda expressions !");
});
|
Simple
code to print all elements of given array. Note there is one more way of using
lambda expression. In below example we use the usual way of creating lambda
expression using arrow syntax and also we used a brand new double colon (::)
operator that Java 8 has to convert a normal method into lambda expression.
//Old
way:
List
list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer
n: list) {
System.out.println(n);
}
//New
way:
List
list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n
-> System.out.println(n));
//or
we can use :: double colon operator in Java 8
list.forEach(System.out::println);
|
In this
example we use Predicate functional interface to create a test and print the
elements that pass the test. This way you can provide the logic using lambda
expression and do something based on it.
import
java.util.Arrays;
import
java.util.List;
import
java.util.function.Predicate;
public
class
Main
{
public static
void
main(String
[] a) {
List
list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
System.out.println("Print
all numbers:");
evaluate(list,
(n)->true);
System.out.println("Print
no numbers:");
evaluate(list,
(n)->false);
System.out.println("Print
even numbers:");
evaluate(list,
(n)-> n%2 ==
0 );
System.out.println("Print
odd numbers:");
evaluate(list,
(n)-> n%2 ==
1 );
System.out.println("Print
numbers greater than 5:");
evaluate(list,
(n)-> n > 5 );
}
public static
void
evaluate(List
list, Predicate predicate) {
for(Integer
n: list) {
if(predicate.test(n))
{
System.out.println(n
+ " ");
}
}
}
}
|
Output:
Print
all numbers: 1 2 3 4 5 6 7
Print
no numbers:
Print
even numbers: 2 4 6
Print
odd numbers: 1 3 5 7
Print
numbers greater than 5: 6 7
|
Some wizardry using Lambda expression to print square of each
element of a list. Notice we used .stream() method to convert regular list into
a steam. Java 8 added some awesome Stream APIs.java.util.stream.Stream interface comes with tons of useful
methods which can be used along with lambda expression to do some voodoo. We
passed a lambda expression x -> x*x
to map() method which applies this to all
elements of the stream. After that we use forEach to print the all elements of
list.
//Old
way:
List
list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer
n : list) {
int x
= n * n;
System.out.println(x);
}
//New
way:
List
list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x)
-> x*x).forEach(System.out::println);
|
Given a
list, sum the square of each element from this list. See how Lambda expression
can be used to achieve this in a single statement. This is also a starters
example on MapReduce. We used map() to square each element and then reduce() to
reduce all elements into single number.
//Old
way:
List
list = Arrays.asList(1,2,3,4,5,6,7);
int
sum
= 0;
for(Integer
n : list) {
int x
= n * n;
sum = sum + x;
}
System.out.println(sum);
//New
way:
List
list = Arrays.asList(1,2,3,4,5,6,7);
int
sum
= list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();
System.out.println(sum);
|
Difference
between Lambda Expression and Anonymous class
One key difference between using Anonymous class and Lambda
expression is the use of this
keyword. For anonymous class
‘this’ keyword resolves to anonymous class, whereas for lambda expression
‘this’ keyword resolves to enclosing class where lambda is written.
Another difference between lambda expression and anonymous class
is in the way these two are compiled. Java compiler compiles lambda expressions
and convert them into private method of the class. It uses invokedynamic
instruction that was added in Java 7 to
bind this method dynamically. Tal Weiss has written a good blog on how Java compiles the lambda
expressions into bytecode.
4. Java Stream API for Bulk
Data Operations on Collections
A new java.util.stream has been added in Java 8 to perform
filter/map/reduce like operations with the collection. Stream API will allow
sequential as well as parallel execution. This is one of the best feature for
me because I work a lot with Collections and usually with Big Data, we need to
filter out them based on some conditions.
Collection interface has been extended with stream() and parallelStream() default
methods to get the Stream for sequential and parallel execution. Let’s see
their usage with simple example.
StreamExample.java
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package com.journaldev.java8.stream;
import java.util.ArrayList;
import java.util.List;
impo
rt java.util.stream.Stre
am;
p
ublic class StreamExample {
public static void
main(String[] args) {
List
myList = new ArrayList<>();
for(int i=0; i<100 i="" mylist.add="" span="">100>
//sequential
stream
Stream
sequentialStream = myList.stream();
//parallel
stream
Stream
parallelStream = myList.parallelStream();
//using
lambda with Stream API, filter example
Stream
highNums = parallelStream.filter(p -> p > 90);
//using
lambda in forEach
highNums.forEach(p
-> System.out.println("High Nums parallel="+p));
Stream
highNumsSeq = sequentialStream.filter(p -> p > 90);
highNumsSeq.forEach(p
-> System.out.println("High Nums sequential="+p));
}
}
|
If you will run
above example code, you will get output like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
High Nums parallel=91
High Nums parallel=96
High Nums parallel=93
High Nums parallel=98
High Nums parallel=94
High Nums parallel=95
High Nums parallel=97
High Nums parallel=92
High Nums parallel=99
High Nums sequential=91
High Nums sequential=92
High Nums sequential=93
High Nums sequential=94
High Nums sequential=95
High Nums sequential=96
High Nums sequential=97
High Nums sequential=98
High Nums sequential=99
|
Notice that parallel processing values are not in order, so
parallel processing will be very helpful while working with huge collections.
Covering everything about Stream API is not possible in this post, you can read
everything about Stream API at Java
8 Stream API Example Tutorial.
It has always been hard to work with Date, Time and Time Zones
in java. There was no standard approach or API in java for date and time in
Java. One of the nice addition in Java 8 is the java.timepackage that will
streamline the process of working with time in java.
Just by looking at Java Time API packages, I can sense that it
will be very easy to use. It has some sub-packages java.time.format that provides
classes to print and parse dates and times and java.time.zoneprovides support
for time-zones and their rules.
The new Time API prefers enums over integer constants for months
and days of the week. One of the useful class is DateTimeFormatter for
converting datetime objects to strings.
6. Collection API improvements
We have already
seen forEach() method and Stream API for collections. Some new methods added in
Collection API are:
·
Iterator default method forEachRemaining(Consumer action) to perform
the given action for each remaining element until all elements have been
processed or the action throws an exception.
·
Collection default method removeIf(Predicate filter) to remove all of the elements
of this collection that satisfy the given predicate.
·
Collection spliterator() method returning Spliterator instance that can be
used to traverse elements sequentially or parallel.
·
Map replaceAll(), compute(), merge() methods.
7. Concurrency API
improvements
Some important
concurrent API enhancements are:
·
ConcurrentHashMap compute(), forEach(), forEachEntry(), forEachKey(),
forEachValue(), merge(), reduce() and search() methods.
·
CompletableFuture that may be explicitly completed (setting its value
and status).
·
Executors newWorkStealingPool() method to create a
work-stealing thread pool using all available processors as its target
parallelism level.
Some IO
improvements kno
wn to me are:
·
Files.list(Path dir) that returns a lazily populated
Stream, the elements of which are the entries in the directory.
·
Files.lines(Path path) that reads all lines from a
file as a Stream.
·
Files.find() that returns a Stream that is lazily populated with Path
by searching for files in a file tree rooted at a given starting file.
·
BufferedReader.lines() that return a Stream, the
elements of which are lines read from this BufferedReader.
9. Miscellaneous Core API
improvements
Some misc API
improvements that might come handy are:
·
ThreadLocal static method withInitial(Supplier supplier)
to create instance easily.
·
Comparator interface has been extended with a lot
of default and static methods for natural ordering, reverse order etc.
·
min(), max() and sum() methods in Integer, Long and Double
wrapper classes.
·
logicalAnd(), logicalOr() and logicalXor() methods in Boolean
class.
·
ZipFile.stream() method to get an ordered Stream over
the ZIP file entries. Entries appear in the Stream in the order they appear in
the central directory of the ZIP file.
·
Several utility methods in Math class.
Do let me know more :)