BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News JAX London 2015 Round-Up

JAX London 2015 Round-Up

This item in japanese

Bookmarks

JAX London 2015, which took place from 12th to 15th October at the Business Design Centre in London, United Kingdom, gathered many of the experts in the areas of Java, Microservices and other modern development practices. Although the topics were varied, there seemed to be a focus towards a better understanding of the technologies popularised over the last couple of years. This suggested both that these technologies are maturing, and that users are learning to use them more effectively.

In the Java front, a number of important innovations were added when version 8 became publicly available in March 2014, most notably streams and lambdas, but also others. Now that the development community have been using these new features for a while, the speakers at JAX London were in a position to understand how to use them and how not to abuse them.

Streams

Streams allow developers to write code in a more functional style, up to the point that many for- and while- loops are being replaced by streams. Thanks to streams, the following for-loop

int total = 0;
for(int i = 0; i < 100; i++) {
    total += i;
}

could be rewritten as:

int total = IntStream.range(0, 100).reduce(0, (a, b) -> a + b);

Angelika Langer showed some of the performance implications of doing this kind of substitutions, comparing for-loops with their sequential stream equivalent, and then sequential streams with parallel streams. The comparison between for-loops and their sequential stream counterpart showed that streams are, at best, as efficient as the for-loop, and oftentimes worse. The main reason is that the iteration cost itself in a for-loop is lower than that of a sequential stream; if the operation (or operations) being executed at each iteration is computationally heavy, then the cost of this will eclipse the cost of the iteration itself, and for-loops and streams will show similar performance. However, if the operation being executed at each iteration is lightweight, then the cost of the iteration will gain importance, and for-loops will be noticeably faster.

The comparison with parallel streams yielded some surprises. The above iteration could be rewritten in the following way to try and make use of multiple threads:

int total = IntStream.range(0, 100).parallel().reduce(0, (a, b) -> a + b);

However, this doesn’t always provide better performance. On one side, one needs to take into account that there will be some overhead in splitting the stream into sub streams that can be assigned to the different threads, and then in gathering the partial results from each thread together into one common result; depending on how large the stream is and how heavy the operation in each iteration, this overhead may not be worthy (Doug Lea, main author of the concurrency utilities in Java, recommends at least 10,000 items for lightweight operations).

On the other hand, and perhaps more interestingly, the underlying collection can affect how costly it is to split the stream into parallel sub streams: in an exercise based on finding the largest element in a LinkedList collection with 500,000 items, Angelika showed how the parallel computation was 65% slower than the sequential one, due to the fact that LinkedList doesn’t provide a cost-effective way to access random elements, and therefore the LinkedList had to be transversed multiple times in order to build the sub streams.

In a different talk, Peter Lawrey also talked about the use of streams, but this time from a code-style point of view. Peter explained how streams provided the developer a way to write code in a functional way as opposed to the traditional imperative way (something Richard Warburton and Raoul-Gabriel Urma also discussed in their talk about Pragmatical Functional Refactoring). However, Peter also warned about the risks of mixing functional and imperative styles: for instance, if one had a list of strings and wanted to remove all items shorter than five characters, one may be tempted to write the following:

list.stream().filter(s -> s.length() < 5).forEach(s -> list.remove(s));

In this case, we are using functional style to iterate and filter the list, but imperative style to perform the actual removal of the item; this code would result in a ConcurrentModificationException, for the list cannot be modified while it is being iterated. Instead, Peter recommends creating a new list with the elements to be kept:

list = list.stream().filter(s -> s.length() >= 5).collect(Collectors.toList());

Or creating a list of the elements to be removed and then remove them in a different step:

List<String> toRemove = list.stream().filter(s -> s.length() < 5).collect(Collectors.toList());
list.removeAll(toRemove);

Or simply iterating through the list in a purely imperative way:

int i = 0;

while(i < list.size()) {
    if(list.get(i).length() < 5)
        list.remove(list.get(i));
    else
        i++;
}

Lambdas

Peter Lawrey exposed both interesting and confusing ways to use lambdas. On the interesting side, he showed how lambdas could be used to easily create different factory objects without having to create a class for each of them; for instance, assuming the following interface for a StringFactory:

public interface StringFactory {
    public String build();
}

Different implementations of the factory can easily be created with lambdas or even with method references in the following way:

StringFactory helloWorldStringFactory = () -> "Hello World!";

StringFactory emptyStringFactory = String::new;

StringFactory inputStreamFactory = () -> {
    try {
        return (new BufferedReader(new InputStreamReader(System.in))).readLine();
    } catch (IOException e) {
        return null;
    }
};

Similarly, Stephen Colebourne showed how lambdas can be used to write more compact unit tests around cases where an exception is to be expected, and he even provides a TestHelper class that allows statements like these to be written:

TestHelper.assertThrows(() -> object.methodToTest(), ExpectedException.class);
TestHelper.assertThrows(() -> object.anotherMethodToTest(), AnotherExpectedException.class);

On the confusing side, Peter showed how abusing the more compact expressions of lambdas can lead to highly illegible code, challenging the audience to infer the resulting type of the following expression:

a -> b -> b >- a;

This turned out to be a function that accepted a parameter and returned another function, which again accepted a parameter, and then compared the two of them. Incidentally, this was effectively equivalent to the much more obvious expression:

(a, b) -> b > -a;

Peter went on to indicate how type inference in lambdas can create even more confusing situations when exceptions are involved. As an example, the following block wouldn’t compile due to an unhandled IOException in Files.lines():

ExecutorService ex = Executors.newCachedThreadPool();
ex.submit(() -> {
    Files.lines(Paths.get("data.txt")).forEach(System.out::println);
});

However, adding a “return null;” clause like follows would make the problem go away:

ExecutorService ex = Executors.newCachedThreadPool();
ex.submit(() -> {
    Files.lines(Paths.get("data.txt")).forEach(System.out::println);
    return null;
});

The explanation comes from the fact that adding a return clause makes the compiler infer a different type, and the different types have different signatures. In the first example, the type of the lambda is inferred to be Runnable, which has the following signature:

public abstract void run();

While in the second case the type of the lambda is inferred to be Callable<V>, with the following signature:

V call() throws Exception;

In the case of Runnable, the signature doesn’t include a “throws” clause, and therefore the exception through by Files.lines() needs to be handled. However, in the case of Callable the signature does include a throws Exception clause, which means the IOException can be escalated to the caller transparently.

Perhaps to avoid hidden side-effects like this, both Stephen and Peter suggested that some lambdas are better written explicitly as a named class, where the returned types and signatures are explicitly written.

Interfaces

Stephen Colebourne wanted to bring attention to the new characteristics of interfaces, which he considered a little-known but greatly important change in Java 8. As of the latest version, interfaces can now include both static methods and default implementation for new methods.

As Stephen explained, static methods can be used to prevent the need for a factory class, and combining this with a clever use of the package scope, it can provide a better encapsulation of implementation classes. Let’s consider the case where the implementation details of a class are to be abstracted away through an interface; assuming both interface and class are in the same package, they could be written in the following way:

public interface MyInterface {
    public static MyInterface createInstance() {
        return new MyInterfaceImpl();
    }

    /* Declaration of methods */
}
class MyInterfaceImpl implements MyInterface {
    /* Definition of methods */
}

Note that since the class hasn’t been declared as public, it is not accessible from outside the package. This way, attempting to create an object of MyInterfaceImpl, or simply attempting to make a reference to it, would result in a compilation error due to scope, which forces the developer to operate always through the interface.

On the other hand, default methods allow interfaces to be extended without breaking existing implementations, a traditional problem of previous versions. In fact, one of the reasons why default methods (previously referred to as “defender methods” and “virtual extension methods”) were added to Java 8, was to add new lambda-enabled methods to Collection interfaces without breaking existing implementations, as explained in Interface evolution via virtual extension methods by Brian Goetz, Oracle’s Java Language Architect. Default methods give developers much more flexibility to let their interfaces evolve, reason for which Stephen ventured that abstract classes may one day disappear.

There were many other examples that indicated that the development community is looking at current technologies with a more analytical eye. Chris Bailey compared Java and JavaScript, but instead of using the traditional qualitative approach (contrast of features) he focused on a quantitative approach, showing a number of different metrics and benchmarks and being careful to highlight where a result could have different interpretations.

In the methodologies front, Daniel Bryant and Steve Poole showcased some of the new challenges that DevOps and the Cloud bring to developers. Chris Richardson presented a “pattern language” to try and compare different microservices strategies from an objective point of view. Lyndsay Prewer demonstrated that, although microservices bring a number of benefits, monolithic strategies can still bring success. For a full list of sessions, the program of JAX London 2015 can still be consulted.

Rate this Article

Adoption
Style

BT