Modernize old code

Java has been around since 1995. There is a large amount of code that has been written for older versions of the JDK. Such code will still run, but there are many advantages to modernizing such code, when you get the chance, to take advantage of more modern libraries and language features.

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:

If you use these, then you're forced to use the names of these pre-defined classes and methods. If you define your own interfaces instead, then the names will very likely be much more descriptive.

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());
  }
} 

See Also :
Type Safe Enumerations
Implementing compareTo
Know the core libraries
Overloading can be tricky
Use for each liberally
Use @Override liberally
Avoid raw types
Prefer modern libraries for concurrency