Wednesday, July 28, 2021

10 JDK 7 Features to Revisit, Before You Welcome Java 8 - Examples

It's been almost a month since Java 8 is released and I am sure all of you are exploring new features of JDK 8. But, before you completely delve into Java 8, it’s time to revisit some of the cool features introduced on Java 7. If you remember, Java 6 was nothing on the feature, it was all about JVM changes and performance, but JDK 7 did introduce some cool features which improved the developer's day to day task. Why I am writing this post now? Why I am talking about Java 1. 7, when everybody is talking about Java 8? Well I think, not all Java developers are familiar with changes introduced in JDK 7, and what time can be better to revisit an earlier version than before welcoming a new version.

I don't see automatic resource management used by developers in daily life, even after IDE's has got the content assist for that. Though I see programmers using String in Switch and Diamond operator for type inference, again there is very little known about the fork-join framework,  catching multiple exceptions in one catch block or using underscore on numeric literals.

So I took this opportunity to write a summary sort of post to revise these convenient changes and adopt them into our daily programming life. There are a couple of good changes on NIO and new File API, and lots of other at the API level, which is also worth looking at. I am sure combined with Java 8 lambda expression, these features will result in a much better and cleaner code.


10 Best JDK 7 Features Every Java developer should learn 

Here are my favorite features from Java 7 release which every Java developer should learn:

1) Type inference

Before JDK 1.7 introduce a new operator <<, known as diamond operator to making type inference available for constructors as well. Prior to Java 7, type inference is only available for methods, and Joshua Bloch has rightly predicted in Effective Java 2nd Edition, it’s now available for constructor as well.



Prior JDK 7, you type more to specify types on both left and right hand side of object creation expression, but now it only needed on left hand side, as shown in below example.

Prior JDK 7
Map<String, List<String>> employeeRecords =  new HashMap<String, List<String>>();
List<Integer> primes = new ArrayList<Integer>();

In JDK 7
Map<String, List<String>> employeeRecords =  new HashMap<>();
List<Integer> primes = new ArrayList<>();

So you have to type less in Java 7, while working with Collections, where we heavily use Generics. See here for more detailed information on diamond operator in Java.


2) String in Switch

Before JDK 7, only integral types can be used as selector for switch-case statement. In JDK 7, you can use a String object as the selector. For example,
String state = "NEW";

switch (day) {
   case "NEW": System.out.println("Order is in NEW state"); break;
   case "CANCELED": System.out.println("Order is Cancelled"); break;
   case "REPLACE": System.out.println("Order is replaced successfully"); break;
   case "FILLED": System.out.println("Order is filled"); break;
   default: System.out.println("Invalid");

}
equals() and hashcode() method from java.lang.String is used in comparison, which is case-sensitive. Benefit of using String in switch is that, Java compiler can generate more efficient code than using nested if-then-else statement. See here for more detailed information of how to use String on Switch case statement.


3) Automatic Resource Management

Before JDK 7, we need to use a finally block, to ensure that a resource is closed regardless of whether the try statement completes normally or abruptly, for example while reading files and streams, we need to close them into finally block, which result in lots of boiler plate and messy code, as shown below :
public static void main(String args[]) {
        FileInputStream fin = null;
        BufferedReader br = null;
        try {
            fin = new FileInputStream("info.xml");
            br = new BufferedReader(new InputStreamReader(fin));
            if (br.ready()) {
                String line1 = br.readLine();
                System.out.println(line1);
            }
        } catch (FileNotFoundException ex) {
            System.out.println("Info.xml is not found");
        } catch (IOException ex) {
            System.out.println("Can't read the file");
        } finally {
            try {
                if (fin != null) fin.close();
                if (br != null) br.close();
            } catch (IOException ie) {
                System.out.println("Failed to close files");
            }
        }
    }

Look at this code, how many lines of boiler codes?

Now in Java 7, you can use try-with-resources feature to automatically close resources, which implements AutoClosable and Closeable interface e.g. Streams, Files, Socket handles database connections, etc.

JDK 7 introduces a try-with-resources statement, which ensures that each of the resources in try(resources) is closed at the end of the statement by calling close() method of AutoClosable. Now same example in Java 7 will look like below, a much concise and cleaner code :

public static void main(String args[]) {
       try (FileInputStream fin = new FileInputStream("info.xml");
  BufferedReader br = new BufferedReader(new InputStreamReader(fin));) {
  if (br.ready()) {
   String line1 = br.readLine();
   System.out.println(line1);
  }
 } catch (FileNotFoundException ex) {
  System.out.println("Info.xml is not found");
 } catch (IOException ex) {
  System.out.println("Can't read the file");
 }
}
Since Java is taking care of closing opened resources including files and streams, may be no more leaking of file descriptors and probably an end to file descriptor error. Even JDBC 4.1 is retrofitted as AutoClosable too.

4) Fork Join Framework

The fork/join framework is an implementation of the ExecutorService interface that allows you to take advantage of multiple processors available in modern servers. It is designed for work that can be broken into smaller pieces recursively.

The goal is to use all the available processing power to enhance the performance of your application. As with any ExecutorService implementation, the fork/join framework distributes tasks to worker threads in a thread pool.

The fork-join framework is distinct because it uses a work-stealing algorithm, which is very different than producer consumer algorithm. Worker threads that run out of things to do can steal tasks from other threads that are still busy.

The centre of the fork/join framework is the ForkJoinPool class, an extension of the AbstractExecutorService class. ForkJoinPool implements the core work-stealing algorithm and can execute ForkJoinTask processes.

You can wrap code in a ForkJoinTask subclass like RecursiveTask (which can return a result) or RecursiveAction. See here for some more information on fork join framework in Java.


5) Underscore in Numeric literals

In JDK 7, you could insert underscore(s) '_' in between the digits in an numeric literals (integral and floating-point literals) to improve readability. This is especially valuable for people who uses large numbers in source files, may be useful in finance and computing domains. For example,

int billion = 1_000_000_000;  // 10^9
long creditCardNumber =  1234_4567_8901_2345L; //16 digit number
long ssn = 777_99_8888L;
double pi = 3.1415_9265;
float  pif = 3.14_15_92_65f;


You can put underscore at convenient points to make it more readable, for examples for large amounts putting underscore between three digits make sense, and for credit card numbers, which are 16 digit long, putting underscore after 4th digit make sense, as they are printed in cards. By the way remember that you cannot put underscore, just after decimal number or at the beginning or at the end of number. For example, following numeric literals are invalid, because of wrong placement of underscore:

double pi = 3._1415_9265; // underscore just after decimal point
long creditcardNum = 1234_4567_8901_2345_L; //underscore at the end of number
long ssn = _777_99_8888L; //undersocre at the beginning

See my post about how to use underscore on numeric literals for more information and use case.

6) Catching Multiple Exception Type in Single Catch Block

In JDK 7, a single catch block can handle more than one exception types.

For example, before JDK 7, you need two catch blocks to catch two exception types although both perform identical task:

try {

   ......

} catch(ClassNotFoundException ex) {
   ex.printStackTrace();
} catch(SQLException ex) {
   ex.printStackTrace();
}

In JDK 7, you could use one single catch block, with exception types separated by '|'.

try {

   ......

} catch(ClassNotFoundException|SQLException ex) {

   ex.printStackTrace();

}

By the way, just remember that Alternatives in a multi-catch statement cannot be related by subclassing. For example, a multi-catch statement like below will throw compile time error :

try {

   ......

} catch (FileNotFoundException | IOException ex) {

   ex.printStackTrace();

}

Alternatives in a multi-catch statement cannot be related by sub classing, it will throw an error at compile time :
java.io.FileNotFoundException is a subclass of alternative java.io.IOException
        at Test.main(Test.java:18)

see here to learn more about improved exception handling in Java SE 7.


7) Binary Literals with prefix "0b"

In JDK 7, you can express literal values in binary with prefix '0b' (or '0B') for integral types (byte, short, int and long), similar to C/C++ language. Before JDK 7, you can only use octal values (with prefix '0') or hexadecimal values (with prefix '0x' or '0X').

int mask = 0b01010000101;

or even better

int binary = 0B0101_0000_1010_0010_1101_0000_1010_0010;


8) Java NIO 2.0

Java SE 7 introduced java.nio.file package and its related package, java.nio.file.attribute, provide comprehensive support for file I/O, and for accessing the default file system. It also introduced the Path class which allows you to represent any path in the operating system.

New File system API complements the older one and provides several useful method checking, deleting copying, and moving files. for example, now you can check if a file is hidden in Java. You can also create symbolic and hard links from Java code.

The JDK 7 new file API is also capable of searching for files using wild cards. You also get support to watch a directory for changes. I would recommend checking the Java doc of a new file package to learn more about this interesting useful feature.


9) G1 Garbage Collector

JDK 7 introduced a new Garbage Collector known as G1 Garbage Collection, which is a short form of garbage first. G1 garbage collector performs clean-up where there is most garbage. To achieve this it split Java heap memory into multiple regions as opposed to 3 regions in the prior to Java 7 version (new, old and permgen space). It's said that G1 is quite predictable and provides greater throughput for memory-intensive applications.


10) More Precise Rethrowing of Exception

The Java SE 7 compiler performs more precise analysis of re-thrown exceptions than earlier releases of Java SE. This enables you to specify more specific exception types in the throws clause of a method declaration. before JDK 7, re-throwing an exception was treated as throwing the type of the catch parameter. For example, if your try block can throw ParseException as well as IOException.

In order to catch all exceptions and rethrow them, you would have to catch Exception and declare your method as throwing an Exception. This is sort of obscure non-precise throw, because you are throwing a general Exception type (instead of specific ones) and statements calling your method need to catch this general Exception. This will be more clear by seeing the following example of exception handling in code prior to Java 1.7

public void obscure() throws Exception{
    try {
        new FileInputStream("abc.txt").read();
        new SimpleDateFormat("ddMMyyyy").parse("12-03-2014");        
    } catch (Exception ex) {
        System.out.println("Caught exception: " + ex.getMessage());
        throw ex;
    }
}

From JDK 7 onwards you can be more precise while declaring the type of Exception in throws clause of any method. This precision in determining which Exception is thrown from the fact that, If you re-throw an exception from a catch block, you are actually throwing an exception type which:

   1) your try block can throw,
   2) has not handled by any previous catch block, and
   3) is a subtype of one of the Exception declared as catch parameter

This leads to improved checking for re-thrown exceptions. You can be more precise about the exceptions being thrown from the method and you can handle them a lot better at client-side, as shown in the following example :

public void precise() throws ParseException, IOException {
    try {
        new FileInputStream("abc.txt").read();
        new SimpleDateFormat("ddMMyyyy").parse("12-03-2014");        
    } catch (Exception ex) {
        System.out.println("Caught exception: " + ex.getMessage());
        throw ex;
    }
}
The Java SE 7 compiler allows you to specify the exception types ParseException and IOException in the throws clause in the preciese() method declaration because you can re-throw an exception that is a super-type of any of the types declared in the throws, we are throwing java.lang.Exception, which is super class of all checked Exception. Also in some places you will see final keyword with catch parameter, but that is not mandatory any more.

That's all about what you can revise in JDK 7. All these new features of Java 7 are very helpful in your goal towards clean code and developer productivity. With lambda expression introduced in Java 8, this goal to cleaner code in Java has reached another milestone. Let me know, if you think I have left out any useful feature of Java 1.7, which you think should be here.


Related Java 8 Tutorials
If you are interested in learning more about new features of Java 8,here are my earlier articles covering some of the important concepts of Java 8:
  • 20 Examples of Date and Time in Java 8 (tutorial)
  • How to use Stream class in Java 8 (tutorial)
  • How to use filter() method in Java 8 (tutorial)
  • How to use forEach() method in Java 8 (example)
  • How to join String in Java 8 (example)
  • How to convert List to Map in Java 8 (solution)
  • How to use peek() method in Java 8 (example)
  • 5 Books to Learn Java 8 from Scratch (books)
  • How to convert stream to array in Java 8 (tutorial)
  • Java 8 Certification FAQ (guide)
  • Java 8 Mock Exams and Practice Test (test)

Thanks for reading this article so far. If you like this article then please share with your friends and colleagues. If you have any question, doubt, or feedback then please drop a comment and I'll try to answer your question.

P.S.: If you want to learn more about new features in Java 8 then please see the tutorial What's New in Java 8. It explains about all important features of Java 8 e.g. lambda expressions, streams, functional interface, Optionals, new date and time API and other miscelleneous changes.

P.S.: If you love books then you may like Java 7 New features Cookbook from Packet Publication as well. 

17 comments :

Leonardo said...

Nice resume of Java 7 features.

Anonymous said...

Can u post how to use generics with examples... It is a slight confusing topic for me

Anonymous said...

also post the feature of java 6 with exmaples

Unknown said...

String.substring() method could cause memory leak in 16,while in 1.7 it wouldn't.
String. intern() is different in 1.6 and 1.7.
String provide an attitude hash which store the hashcode of Sring in 1.7 because String is immutable and often be used in the Collection genercis as the key whilch need override the equal and hashcode method.
Correct me if I am wrong. Thanks

Lukasz said...

Good stuff but chapter 2) should probably switch using "state" not "day" ;-)

Tio Vagner said...

Nice!

Hi, in the first feature, the diamond operator is <<, is it right?

Unknown said...

Tio, you are right! same question in my mind, when I read it first time!

shiva said...

Nice info.......... can you please post java 1.6 features

Anonymous said...

One more thing in Java8 interface allows default method implemantion with in it and prior to Java8 interface allows only abstract methods.

Swathi said...

Please correct the diamond operator as "<>" which should look like a diamond symbol. It indicates:: You can substitute the parameterized type of the constructor with an empty set of type parameters (<>)

Anonymous said...

I think you have missed the hyperlink for "See here for more detailed information on diamond operator in Java.".

Can you please update this.





javin paul said...

@Anonymous, sure, will update it, thanks for pointing it out.

Unknown said...

Hey Javin, it is almost the time to write the same article for Java9!

David Mayer at https://www.java8certificationquestions.com/

Unknown said...

Nice Explanation with examples......

Ritesh Sail said...

Nice article, Thanks for putting this all together.

SAMPATH said...

Nice.. and thanks for comparing with previous version..

Unknown said...

Good article about Java 7.

Post a Comment