Showing posts with label method reference. Show all posts
Showing posts with label method reference. Show all posts

Friday, 4 May 2018

Creating a method reference on a null reference does not throw NullPointerException

We ran into a problem that the Unit tests ran perfectly on my local machine, but the same Unit tests would break in the continuous delivery pipeline.

The problem occurred when creating a method reference. Like so:

When running the test included above, the test failed with:

java.lang.AssertionError: Expected exception: java.lang.NullPointerException

After some research we found out that the difference between the two is the Eclipse compiler used by the IntelliJ IDE vs. the openjdk installed in the continuous delivery pipeline.

We found out that it is illegal to use a method reference on a null reference. Quoting from [1]:

First, if the method reference expression begins with an ExpressionName or a Primary, this subexpression is evaluated. If the subexpression evaluates to null, a NullPointerException is raised, and the method reference expression completes abruptly.

At first glance, this is a bit weird. After all, we want to have a method reference, in order to call it at a later time, which in fact may never occur. So why not have the NullPointerException when an attempt is made to actually call the method?

IntelliJ

IntelliJ comes equipped automatically with the Eclipse Java Compiler (ECJ), and as such it takes some effort to find out which version is installed along with the IDE.

Jar file ecj-4.6.1.jar was include in directory .local/share/JetBrains/Toolbox/apps/IDEA-U/ch-1/181.4445.78/lib/ecj-4.6.1.jar.

In IntelliJ it is possible to provide the path to the ecj jar to use, see [2].

Setting the path to the newly downloaded jar file ~/Downloads/ecj-4.7.3a.jar solved my problem.

It's been a long time since I encountered a bug in the compiler3, but other people have noticed it too4 and then it gets fixed.

Note

Bear in mind that, if we did not use a method reference, but an ordinary lambda, that this problem would not have occurred (immediately).

This means that, if you replace a Lambda with a method reference you may be introducing a NullPointerException earlier in the code without realising it5.

Bear this in mind.

References

[1] JLS 8 - 15.13.3. Run-Time Evaluation of Method References
https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13.3
[2] IntelliJ - Specifying compilation settings
https://www.jetbrains.com/help/idea/specifying-compilation-settings.html
[3] Eclipse - Bug Report
https://bugs.eclipse.org/bugs/show_bug.cgi?id=521182
[4] StackOverflow - Creating a method reference on a null reference does not throw an exception
https://stackoverflow.com/questions/37681625/creating-a-method-reference-on-a-null-reference-does-not-throw-an-exception
[5] StackOverflow - java.lang.NullPointerException is thrown using a method reference but not a lambda
https://stackoverflow.com/questions/37413106/java-lang-nullpointerexception-is-thrown-using-a-method-reference-but-not-a-lamb/37413546

Friday, 16 February 2018

Turning a Stream into an Iterable

Recently I wanted to know how to change a stream into an Iterable. The method in the API that I need to call expects an Iterable.

I found the following solution in [1]:

public class RealEstateEvaluator
{
  public void evaluate(Session session, Iterable<House> iterable)
  {
    // do stuff
  }
}

public void realEstateEvaluation()
{
  Stream<House> housenumbersStream = 
    housenumbers.stream()
      .map(realEstateService::getHouse);   

  new RealEstateEvaluator().evaluate(getSession(), housenumbersStream::iterator);
}

However, [1] did mention that the result was unreadable. It is confusing that a method reference that provides an Iterator, perhaps accidentally, fulfills the contract for Iterable2.

That's right. Iterable is an interface3, which has three methods, two of which have default implementations. Which means it has one method (iterator()), which means it can be used as a Lambda expression.

I prefer:

public void evaluate()
{
  List<House> houses = 
    housenumbers.stream()
      .map(realEstateService::getHouse)
      .collect(Collectors.toList();   

  new RealEstateEvaluator().evaluate(getSession(), houses);
}

It is a lot more readable. And I am a big believer in the Principle of Least Surprise.

All Collections are Iterables, by the way.

Update 2018-02-23: after reading and evaluating the comment below, I still think using the method reference makes it harder to read, but is much safer to use. Perhaps getting used to using stream::iterator is just a question of time.

References

[1] LambdaFAQ - How do I turn a stream into an iterable
http://www.lambdafaq.org/how-do-i-turn-a-stream-into-an-iterable/
[2] StackOverflow - Why does stream not implement iterable
https://stackoverflow.com/questions/20129762/why-does-streamt-not-implement-iterablet
[3] Oracle JavaDoc - Iterable
https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html?is-external=true

Thursday, 6 July 2017

Lambdas, New IO, and parsing textfiles in a hurry.

Okay, so I needed to do some parsing of a file containing URLs (which I "wget"-ted) and moving the retrieved files to proper locations.

I decided to write a quick Java program to do this instead of messing around with scripting languages or a Linux bash shell.

It worked very well, and I am rather pleased with the result and Java 8.

It contains the following "new/newer/not-very-old" stuff:
  • a lambda
  • a stream (of Strings)
  • a method reference (used as a lambda)
  • the java.nio.file package (New IO)
One small note: lambdas implement an interface. In this case the forEach requires a lambda that implements the Consumer interface. The Consumer interface does not specify an IOException. Therefore, I am required to catch it here and rethrow it unchecked.

References

[1] Java SE 8 - Official Javadoc
https://docs.oracle.com/javase/8/docs/api/