IntelliJ IDEA
IntelliJ IDEA – the Leading Java and Kotlin IDE, by JetBrains
The Inspection Connection – Issue #1, Migration Translation
The release of JDK 8 has introduced a number of new language features into Java vernacular. In this issue of The Inspection Connection, we will examine eight useful inspections that IntelliJ IDEA provides to help you become more fluent in functional programming on Java 8, migrate your legacy code, detect bugs sooner, and develop with pleasure.
1. Anonymous type can be replaced with lambda
As a Java developer, you are probably familiar with anonymous classes, which declare and instantiate themselves all at once. These classes will either implement an interface or override functionality by extending another class. For years, IntelliJ IDEA has collapsed anonymous classes in order to make them easier to read. For example:
Function<Function, Function> f2 = new Function<Function, Function>() { @Override public Function apply(Function function) { return function.compose(function); } };
Now that Java 8 supports a feature called lambda notation, we can utilize this “folding” practice directly – once the compiler has seen the declaration, it knows the resulting type signature, so why bother with the ceremony of writing everything out? The solution here is very simple: our inputs come first, then a “goes to” or ->, followed by the statement block:
Function<Function, Function> f = (Function function) -> { return function.compose(function); };
So this feature is available in different contexts: as a superficial folding rule, for language levels 7 and below, then as an inspection and intention for projects which have already migrated to Java 8. By default, IntelliJ IDEA will display anonymous classes as lambdas. On Java 8, you will be prompted to refactor them through the Quick Fix intention, as well as via the primary inspection.
2. Statement lambda can be replaced with expression lambda
Now lambda statements can be reduced even further in many cases. Brace yourselves, for braces are optional when the lambda body is a single expression. Java’s language designers have worked hard to help us do more with less – here expression lambdas are one result of their perseverance.
Function<Function, Function> f2 = (Function function) -> { return function.compose(function); };
The above statement can be rewritten using an abbreviated expression lambda, in a similar manner to the way loops and control flow blocks may may opt to forgo enclosing a single-line body. Here is the above statement lambda, rewritten with this inspection:
Function<Function, Function> f2 = (Function function) -> function.compose(function);
This inspection can only be applied when the statement body invokes a single expression or void method. IntelliJ IDEA will detect and replace sites where statement lambdas can be reduced to expression lambdas, and suggest refactoring them with a Quick Fix.
3. Remove redundant types
When using lambda expressions in Java 8, you may choose to omit data types from the parameter list for the sake of brevity. In fact the formal parameter syntax is seldom used, since the declaration necessarily indicates the parameter type. Continuing our prior example:
Function<Function, Function> f2 = (Function function) -> function.compose(function);
Furthermore, parentheses are optional iff there is exactly one parameter. So this inspection will strip down any redundant type declarations, then peel off unnecessary parentheses from the parameter list, leaving us with the following concise expression.
Function<Function, Function> f2 = function -> function.compose(function);
So far, we have gone from a multi-line anonymous class to a single line lambda expression using the Function interface, which represents many of the new language patterns in Java 8. Now we will take the analogy one step further by introducing method references, the most succinct form of lambda expressions.
4. Lambda can be replaced with method reference
In a small but specific set of circumstances based on common usage patterns, lambdas may be replaced by method references, which are semantically equivalent to lambdas, but without any parameters. Instead, the parameter list is inferred based on an expected signature at the call site. As a result we can transform some expression lambdas to their equivalent method reference form, without any additional information.
String[] stringArray = {"IntelliJ IDEA", "AppCode", "CLion", "0xDBE", "Upsource"}; Arrays.sort(stringArray, (s1, s2) -> s1.compareToIgnoreCase(s2));
The above line #2 can be rewritten using the Quick Fix inspection (which is enabled by default), by navigating to the highlighted region within your editor window, then pressing Alt+Enter when the lightbulb appears and selecting “Replace lambda with method reference”. This will result in a much cleaner version we see below.
String[] stringArray = {"IntelliJ IDEA", "AppCode", "CLion", "0xDBE", "Upsource"}; Arrays.sort(stringArray, String::compareToIgnoreCase)
However, even when the method reference is syntactically correct, this refactoring is not always appropriate, due to method overloading semantics in Java. IntelliJ IDEA does not suggest refactoring in these cases. Consider the following scenario:
class Foo { public String bar() { return "foo"; } public static String bar(Foo foo) { return "bar"; } } class Test { public void ambiguousReference() { Stream.of(new Foo()).map(Foo::bar); } }
While line #13 may pass our initial inspection, the method reference is semantically ambiguous, since it is unclear whether the author refers to the static method on line #6 or the arbitrary-object-particular-type instance method on line #2. So we can see how removing context from the call site can discard important lexical information, here, the overloading of Foo::bar
.
5. Lambda parameter hides field (new in 13.1)
Like any field or method, lambda parameters can hide the visibility of homonymous fields in surrounding classes and superclasses. While field hiding may be deliberate, it is more likely an unintended side effect of refactoring that can turn into a serious bug. IntelliJ IDEA takes extra precautions to ensure they are not unintentional.
interface Function<T, R> { R apply(T t); } class X { private String s; void m() { Function<String, String> f = (s) -> null; } }
This inspection is not enabled under the default inspection profile, although it can be added under Settings -> Inspections -> Visibility Issues, by selecting “Lambda parameter hides field” under the default profile, or by using a custom profile, which can be configured to use any combination of these inspections for a custom application.
6. foreach loop can be collapsed with Stream API (new in 13.1)
In functional programming, recursion and composition are generally favored over loops and iteration, so in keeping with the functional design for Java 8, IntelliJ IDEA offers a dedicated inspection to unravel For-Each loops into a more functional style with the new Streams API found in JDK 1.8.
List<String> foo = new ArrayList<>(); for (String s : foo) { if (s != null) { System.out.println(s); } }
Now, we will apply this inspection to achieve the same result with the stream/filter pattern.
List<String> foo = new ArrayList<>(); foo.stream().filter(s -> s != null).forEach(System.out::println);
This inspection will transform the For-Each loop on our behalf, allowing us to rapidly refactor existing code. Notice how the filter condition is pared down to the compact expression lambda, and subsequent print statement is reduced to a method reference.
7. Interface may be annotated @FunctionalInterface (new in 13.1)
Java 8 introduces a new annotation that indicates when an interface type declaration is a functional interface, which are an important concept in Java 8 that have paved the way for lambda expressions. When is an interface a functional interface? You may have guessed the answer already.
interface Interface { void function(); public String toString(); public boolean equals(Object o); }
A functional interface is an interface with exactly one abstract method, excluding those methods overridden from Object. If an interface has more than one abstract method or less than one abstract method, it is no longer a functional interface.
@FunctionalInterface interface Interface { void function(); public String toString(); public boolean equals(Object o); }
Should you forget when you may use the @FunctionalInterface annotation, IntelliJ IDEA will remind you. However, regardless of whether a functional interface is annotated by @FunctionalInterface, it will not be treated any differently by the compiler.
8. Abstract class may be interface (new in 14)
With the introduction of default methods in Java 8, longstanding sanctions on static methods from Interfaces have been relaxed, blurring some old lines between abstract classes and interfaces, and making it easier for us to share boilerplate and helper methods.
abstract class ConvertMe { public static final String S = ""; public void m() {} public static void n() { new ConvertMe() {}; class X extends ConvertMe {} } public class A {} }
In many ways, it will now be simpler to convert abstract classes to interfaces, although this comes with several tradeoffs. There are a few important criteria that distinguish interfaces and abstract classes in Java 8, and these will influence your design choices in this area.
interface ConvertMe { String S = ""; java void m() {} static void n() { new ConvertMe() {}; class X implements ConvertMe {} } class A {} }
For language level 8 and above, IntelliJ IDEA will take now into consideration abstract classes with both static and non-static methods, allowing us to refactor them using the expanded interface definition. This inspection can be enabled under Settings -> Inspections -> Class Structure -> “Abstract class may be interface”. As a reminder, you can run any of these inspections individually by pressing Ctrl/Cmd+Alt+Shift+I.
With this, hopefully you have found some useful inspections and learned a few things about Java 8 along the way. If you are interested in learning more, one way to do this is by spending some time developing your own custom language inspections. Another way is by letting us know, so we can continue to deliver more educational content. Thank you for reading and develop with pleasure!