In particular, the Java 5 release (JDK 1.5) marked a significant change, and noticeably altered (and improved) the character of typical Java code. When updating old code to more recent versions of Java, it's helpful to use a quick checklist of things to look out for. Here's a list of such items related to the Java 5 release:
Use @Override liberally
The @Override
standard annotation identifies methods that override a superclass method.
It should be used liberally to indicate your intent to override.
It's also used when implementing methods defined by an interface.
Avoid raw types
Raw types should almost always be avoided in favor of parameterized types.
Use for-each loops
The enhanced for loop (also called the for-each loop) should be used whenever available.
It's more compact, concise, and clear.
Replace constants with enumerations
Using public static final
constants to represent sets of related items should be avoided in favor of the enumerations now supported by Java.
In addition, you should consider replacing any "roll-your-own" implementations of type-safe enumerations with the new language construct.
Replace StringBuffer
with StringBuilder
The older StringBuffer
class is thread-safe, while the new StringBuilder
is not.
However, it's almost always used in a context in which thread safety is superfluous.
Hence, the extra cost of synchronization is paid without benefit.
Although the performance improvement in moving from StringBuffer
to StringBuilder
may only be very slight (or perhaps not even measurable), it's generally considered better form to prefer StringBuilder
.
Use sequence parameters when appropriate
Sequence parameters (varargs) let you replace Object[]
parameters (containing 0..N items) appearing at the end of a parameter list with an alternate form more convenient for the caller.
For example,
public static void main(String[] aArgs){}can now be replaced with:
public static void main(String... aArgs){}
Be careful with Comparable
The Comparable
interface has been made generic.
For example,
class Anatomy implements Comparable{ public int compareTo(Object aThat){} }should now be replaced with :
class Anatomy implements Comparable<Anatomy>{ public int compareTo(Anatomy aThat){} }
Example
Here's an example of code written using Java 5 features:
import java.util.*; public final class Office { /** * Use sequence parameter (varargs) for main method. * * Use a sequence parameter whenever array parameter appears at * the END of the parameter list, and represents 0..N items. */ public static void main(String... args){ //Use parameterized type 'List<String>', not the raw type 'List' List<String> employees = Arrays.asList("Tom", "Fiorella", "Pedro"); Office office = new Office(AirConditioning.OFF, employees); System.out.println(office); //prefer the for-each style of loop for(String workingStiff: employees){ System.out.println(workingStiff); } } /** * Preferred : use enumerations, not int or String constants. */ enum AirConditioning {OFF, LOW, MEDIUM, HIGH} /* * Definitely NOT the preferred style : */ public static final int OFF = 1; public static final int LOW = 2; public static final int MEDIUM = 3; public static final int HIGH = 4; Office(AirConditioning airConditioning, List<String> employees){ this.airConditioning = airConditioning; this.employees = employees; //(no defensive copy here) } AirConditioning getAirConditioning(){ return airConditioning; } List<String> getEmployees(){ return employees; } /* * Get used to typing @Override for toString, equals, and hashCode : */ @Override public String toString(){ //..elided } @Override public boolean equals(Object that){ //..elided } @Override public int hashCode(){ //..elided } // PRIVATE // private final List<String> employees; private final AirConditioning airConditioning; }
JDK 7 also has some new tools which improve the character of typical Java code:
Use 'diamond' syntax for generic declarations
Declarations of the form:
List<String> blah = new ArrayList<String>(); Map<String, String> blah = new LinkedHashMap<String, String>();can now be replaced with a more concise syntax:
List<String> blah = new ArrayList<>(); Map<String, String> blah = new LinkedHashMap<>();
Use try-with-resources
The try-with-resources
feature lets you eliminate most finally blocks in your code.
Use Path and Files for basic input/output
The new java.nio
package is a large improvement over the older File API.
Objects
The new Objects
class has simple utility methods for common tasks.
JDK 8 has these highlights:
Interfaces
Interfaces can now define static helper (utility) methods.
Interfaces can now define default
methods.
These methods both define a method in the API, and provide a default implementation for that method.
Default methods aren't abstract.
As a second benefit, default
methods let you add new methods to an existing interface, without breaking any existing implementations.
(That's because the existing implementations will simply inherit the default
implementation of the new method.)
Functional interfaces and functional methods
A functional interface
is simply a new term for something that already exists: an interface having exactly 1 abstract method (called the functional method).
An example would be the Comparable interface.
A number of standard functional interfaces
are pre-defined in the JDK. You may reuse them in your application whenever you please.
For example, a functional method that takes an object of some type T
and returns a boolean can be created using Predicate<T>
.
You don't have to explicitly create a new interface.
A trade-off: the names of things in this package take some getting used to, and can be ambiguous. For example:
Consumer.accept
Supplier.get
Function.apply
The term functional interface has been created because they provide target types for 2 new constructs in the Java programming language: lambda expressions and method references.
Lambda expressions
Lambda expressions
let you pass a short implementation of a functional interface as a parameter to a method.
They offer you the choice of a more compact syntax for doing things that were already possible.
Lambda expressions don't define a new scope.
They see all final
and effectively-final
variables in the scope where they appear.
Method references
Method references
(like Integer::sum
) are compact lambda expressions for any method that has a name.
Streams, pipelines, and aggregate operations
java.util.stream
lets you treat collections and other items in a functional style.
It defines streams.
A stream can be processed with a pipeline of N sequential aggregate operations (intermediate and terminal).
Aggregate operations are also called stream operations.
Dates and times
At long last, replacements for the old date-time classes
have been added.
Comparator
The Comparator
interface
has some new methods, both static
methods and default
methods.
Example
Here's an example of code written using Java 8 features:
import java.time.LocalDateTime; /** A 'functional interface', since there's exactly 1 abstract method.*/ public interface Explosion { /** The 'functional method'. */ void blowUp(); /** A default method, that can be overridden by an implementation. */ default void countdown(int startingWithSeconds){ int count = startingWithSeconds; while (count > 0){ //elided: there's no actual delay here System.out.println(count); } } /** Static methods: helper or utility. */ static int numSecondsLeftInTheCurrentMinute(){ //there's no time zone info in LocalDateTime; //the local/default time zone is implicit here LocalDateTime now = LocalDateTime.now(); return 60 - now.getSecond(); } }
import java.util.Arrays; import java.util.Comparator; import java.util.List; public class ExampleJava8 { public static void main(String... args) { ExampleJava8 thing = new ExampleJava8(); thing.useStreams(); thing.useLambdaExpression(); } /** This prints beatrice and charles. */ void useStreams(){ List<String> names = Arrays.asList("allan", "beatrice", "charles", "diana"); //disadvantage with chaining in general: if there's a NullPointerException, //then it will usually be hard to know which item in the chain is null names .stream() .filter(name -> name.length() > 5) //filter-in the names longer than 5 chars .forEach(e -> log(e)) // print the name ; } /** There are variations in the syntax for lambda expressions. The variations are according to the number of parameters to the method, and the number of lines in the method's implementation. The intent of these variations is to let the code be as short as possible. */ void useLambdaExpression(){ //a 0-param method, with a 1-line impl Runnable runner1line = () -> log("Bang!"); runner1line.run(); //a 0-param method, with an N-line impl Runnable runnerNline = () -> { log("Teenage Fanclub"); log("Alvvays"); log("The Smiths"); }; runnerNline.run(); //a 2-param method, that returns an int Comparator<String> compare = (a, b) -> {return a.length() - b.length();}; //there are other variations not shown here... } private static void log(Object thing){ System.out.println(thing.toString()); } }