Java 8: 10+ features

Oleg
Tsal-Tsalko

Lead Software Engineer
@EPAM
Speaker JavaDay 2014-2015

Java 8: 10+ features


Use Stream API

Stream API – is not a data structure or a collection, it is an abstraction which allows building operations pipeline over sequence of data elements. By using Stream API, you can build a simple and logical chain of operations. A very important feature of Java 8 is that you can use internal parallelisms. For example, if you need to parallelize a particular operation, writing even the simplest code without using the parallel stream would be doomed to writing lots of code using directly ForkJoinPools, ExecutorServices, and other low level concurrency mechanisms. This used to make the process of writing a code more complicated. In addition, Stream API makes it more convenient to use functional programming in Java.

One would ask how can we get a stream? You can do this in numerous ways like: Collection.stream (), IntStream.range (), Stream.of () or Arrays.stream (). Also, by using BufferedReader.lines (), you can get stream of lines directly from file. By using Pattern.splitAsStream (), you can get stream of string pieces’ splitter by regex pattern without any extra work.

Look at the following code snippet. Even without knowing the syntax, every developer can understand what is written – the code is readable well enough.

txns.stream()
 .filter(t ->t.getBuyer().getAge()>=65)
 .map(Txn::getSeller)
 .distinct()
 .sort(comparing(Seller::getName))
 .forEach(s -> System.out.println(s.getName());

The use of functional interfaces in “streams” also simplifies writing and perception of a code. For example, in line .filter (t -.> T.getBuyer () getAge ()> = 65), we use Predicate functional interface for filtering elements of our stream, in line .map (Txn :: getSeller) we basically  map/transform stream of Transactions into stream of Sellers of these Transactions by utilising another functional interface – transforming Function, and in line .forEach (s -> System.out.println (s.getName ()); we apply another functional interface – so called  Consumer, to each element of Stream, which basically simply consume a particular element and does smth with it.

Use Lambdas
Another great Java 8 feature which comes side by side with Streams is Lambdas. You already saw use of Lambdas in the example above.
Lambda – is an anonymous function, which is not linked to any particular class. As with any function, we have input parameters and body operations returning some value.

Possible Lambda function syntax:
– Argument list and a simple body which represents return expression
(Object o) -> o.toString()
Since “Lambda” is an anonymous function, in principle, any method can be represented as a function. With help of «Method reference», you can make a reference to a different method.
– Method reference
Object::toString
For example, let’s have a look at following FileFilter anonymous class implementation, which basically says that FileFilter should consume particular File as an input and check whether it is readable or not. In Java 8 we can write it much simpler and cleaner:

FileFilter x = new FileFilter() {
  public boolean accept(File f) {
  return f.canRead();
       }
};
FileFilter x = (File f) -> f.canRead();
FileFilter x = File::canRead;

With the help of Method reference, you can refer to:
– static method
– object method
– method of enclosing current object in which “Lambda” declared.

Let us have a closer look at another example. On the input of function, apply Person value; we use variables of limited context that appear in the scope. Ideally, your function must not carry out operations on the input parameters, that is, should not take the data from somewhere and process them. If there are input parameters of the function and value return, one should try to avoid external context variables.
Lambdas can use effectively final variables from enclosing context like ‘name’ variable in following example, however use your power carefully, because ideally true functions should operate only on input parameters and not capture other variables from enclosing context which might lead to unpredictable side effect.
– Capture value from enclosing context
(Person p) ->p.getName().equals(name)
In previous example compiler can figure out type of input parameter based on context where this Lambda is used. This called type inference.
– Type inference
p -> p.getName().equals(name)
With Lambdas forget of use of anonymous classes!

Internal iteration over external iteration
For example, look at the following code snippets:

@Test
@PriorJava 8
Public void printAllWords() {
	For (String word : wordlist ) {
		System.out.println(word);
	}
}

 

@Test
@Java 8
Public void printAllWordsInJava 8() {
	wordlist.forEach (System.out: :println);

 

Perhaps, at first glance, the difference in the code is not reasonable. However, there is a significant difference in design approach behind. In first example we have so called external iteration whereas in second example you have so called internal iteration. In second example stream knows how to iterate over underlying collection, you only tell him what to do during iteration other then how to do this.

What’s type of Lambda anyway?
Since Java does not have Function type as first class citizen, Lambda’s type get casted to some Functional interface:
Lambda type gets resolved to some type based on the context.
Runnable r = () -> { doSmth(); };
Here Lambda should match Runnable interface otherwise compiler will show an error.
Callable c = () -> calcResult();
Here Lambda should represent Callable.
Comparator comp = (o1, o2) -> compare(o1, o2);
And this “Lambda” implements custom Comparator.

Influence on existing classes
JDK Library has been evolved as well with regard to these fundamental language changes. Existing interfaces Iterable, Collection, Comparator, etc. have been extended with new methods for Streams support and functional style programming

java.lang.Iterable#forEach() 

 

java.util.Collection#stream() java.util.Collection#parallelStream() 

 

java.util.Comparator#comparing(…) 
java.util.Comparator#thenComparing(…) 
java.util.Comparator#reverse() 

In order to preserve backward compatibility within this breaking change there was introduced another Java 8 feature – Default Methods. Default methods make it possible to modify existing interfaces without breaking existing code.

Use Default methods
We all understand that libraries should evolve otherwise they stagnate. Without default methods being introduced, it would not be possible to implement “Streams” and “Lambdas” features at same extent.
Default methods offer us:
•  Backward compatibility
•  Multiple inheritance behavior in Java.
•  Reduction in boilerplate code

With regard to multiple behavior inheritance, resolution rules are following and quite simple:
•  Class wins over interface (if you have same methods in extended class and in the interface – the class wins).
•  More specific subclass wins (if you have multiple interfaces with the same default methods and one is more specific then another – then more specific interface wins).
•  No 3rd rule (if compiler can’t resolve correct method by itself based on first 2 rules, you should give it a hint).

The next feature – Optional
Annoyed by NPEs?
Should your method return null or throw RuntimeException?
Wondering how to implement SpecialCase pattern?
Inspired by functional programming and want to build a method chain for NULL handling?
<=> You are lucky with Java 8…

Optional is like a holder class which may or may not hold a value.
Here is an example of empty Optional:
Optional fullName = Optional.empty();
We can check if there is a value in the Optional or not:
System.out.println( Full Name is set? + fullName.isPresent())

The great power comes with possibility to chain operation on Optional. For example in an example below we are simply getting a value from Optional holder or in case there is no value – return some sensible defaults using provided Supplier in form of Lambda expression. This is a good example when different Java 8 features complement each other:
System.out.println(“Full Name: + fullName.orElseGet( () -> “[none]” ) );
Here is another example of useful operations chaining over Optional. You take Optional fullName and if it exists transform it to another String otherwise provide default message:
System.out.println( fullName.map( s:-> Hey + s “!” ).orElse( Hey Stranger!” ) );

The use of Date & Time API
•  Replaces old ambiguous mostly deprecated java.util.Date, Calendar, TimeZone, DateFormat classes
•  More fluent/simple/clean API
•  Immutable classes
•  Using Java 8 features including Lambdas
•  Precise separation of concepts

Range of types with clear purpose:
•  LocalDate – a date only
•  LocalTime – a time only
•  LocalDateTime – date with time
•  ZonedDateTime – date with time in time zone
•  Period – date-based amount of time
•  Duration – time-based amount of time
•  And more…
This all works in one calendar system. If you want other calendar systems, you can plug them additionally.
Everyone knows that working with TimeZones is pain, so try to avoid them if you can, otherwise use ZonedDateAndTime!

TimeZone classes in Java 8
In Java 8 there are 4 main classes that support time zones processing:
• ZoneId – replacement for TimeZone class (e.g. “Europe / London”, “Europe / Kiev”)
• ZoneOffset – representing offset from UTC time
• ZoneRules – behind the scenes class, which defines time zone rules
• ZonedDateTime – main date/time class, that is aware of time zones

Use StampedLock
When should we use StampedLock? Whenever we think of using RWLock…
RWLock has serious problems:
– RWLock can cause starvation of readers
– RWLock is pessimistic on reads
StampedLock by default:
– Read lock is optimistic
– Optimistic lock can be converted to pessimistic lock if necessary
– Write locks are always pessimistic

•  Pros
– Has much better performance than Reentrant ReadWriteLock
– Latest versions do not suffer from starvation of writers
•  Cons
– Idioms are more difficult to get right than with ReadWriteLock
– A small difference can make a big difference in performance

For example, with ReadWriteLock your code might look like this:

public void deposit (double amount) {
	lock.writeLock().lock();
try {
	balance = balance + amount;
}
finally {
	lock.writeLock().unlock();
}
}

And with StampedLock pretty much the same:


public double getBalance() {
	long stamp = lock.writeLock();
            try {
                balance = balance + amount;
            }
            finally {
                lock.unlockWrite(stamp);
            }
}

However the main power comes with variety of optimistic lock construction like in following example:

public double getBalance() {
            long stamp = lock.tryOptimisticRead();
            double currentBalance = balance;
            if (!lock.validate(stamp)){
                stamp = lock.readLock();
                currentBalance = balance;
                lock.unlockRead(stamp);
            }
            return currentBalance;
        }

Code looks a bit verbose and more complicated at first glance and indeed in order to get performance win with more fine grained lock control you need to make yourself comfortable with StampedLock…

Concurrent Adders
What can be faster then Atomic classes you will ask? However it appeared to be that in Java 8 you can do even better…
>How would you implement a simple counter in multithreaded environment?
– First naive approach would be to use simple variable also known as dirty counter. Of course it’s no good. You will have multiple issues with this approach: non-atomic operations, caching on different levels, certain modifications might not be visible to other threads.
– You can use synchronized or RWLock, BUT it’s way too slow.
– You can use volatile variable, BUT only if you have 1 writer / updater.
– Before Java 8 AtomicInteger was the right choice to go.
– But in Java 8 there is even better solution – LongAdder, which works faster in case you have lot of concurrent updates and you are interested in final result only.

Improved annotations
We can now use annotations almost everywhere:


public class AnnotationsEverywhere {

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
    public @interface NonEmpty {
    }

    public static class Holder<@NonEmpty T> extends @NonEmpty Object {
        public void method() throws @NonEmpty Exception {
        }
    }

 


@SuppressWarnings("unused")
    public static void main(String[] args) {
        final Holder holder = new @NonEmpty Holder();
        @NonEmpty Collection<@NonEmpty String> strings = new ArrayList<>();
    }

}

 

Static analyzers make heavy use of this…
However, there are even more…
•  Process Termination
•  Strong Random Generation
•  Date .toInstant()
•  Interface static methods
•  Better Type Inference
•  Parameter names support by compiler
•  Parallel Arrays
•  Nashorn CLI JavaScript interpreter
•  Class dependency analyzer

There is no excuse to not to use all that great stuff!