JavaThe Most Important New Features & Changes in Java 8

The Most Important New Features & Changes in Java 8

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Introduction (Business Case)

Four years ago, after Oracle’s acquisition of Sun, back in 2010, there were a lot of questions and discussions about what is going to happen next with the Java platform and the language itself. Back then, some of the development was already done for the next release of Java, which was going to be the Java SE7, but some other things originally planned for Java SE7 were going to take longer. Soon after that, Mark Reinhold, Chief Architect of the Java Platform Group, announced that there were two possible plans for the release of Java SE7. Part one, which is Java SE7 as currently defined till the middle of 2012 and then part two, which is the Java SE8 release that has the features we couldn’t get in Java SE7 plus some other things as well.

Java SE8 is defined as the JSR 337 and includes all of the features that are going into the Java platform, the Java language, the Java APIs, the JVM definitions and so on. Despite the fact that the launching of Java SE8 is very close, the specification is not quite final and there might be some minor changes until the final release, but surely not significant changes.

The core part of the specification includes:

  • New functionality
    • JSR 308: Annotations on types
    • JSR 310: Date and Time API
    • JSR 335: Lambda expressions
  • Updated functionality
    • JSR 114: JDBC Rowsets
    • JSR 160: JMX Remote API
    • JSR 173: Streaming API for XML
    • JSR 199: Java Compiler API
    • JSR 206: Java API for XML Processing
    • JSR 221: JDBC 4.0
    • JSR 269: Pluggable Annotation-Processing API

Changes to the Language Syntax

In order for the Java programming language to be able to keep up with modern programming languages like Scala or C# and to leverage programming on multiple cores, to make it easier to distribute processing of collections over multiple threads, unlike other versions of Java released, Java SE8 comes with some significant additions to the language syntax. Moreover, it introduces to Java programmers the idea of closures and functional programming into the language.

Lambda Expressions

If you have done functional programming or if you are familiar with the idea of closures, lambda expressions are not going to be something new. In mathematics and computing generally, a lambda expression is a function. In Java, a lambda expression provides a way of creating an anonymous function, which actually introduces a new type in Java, the anonymous function type.

A lambda expression is like a method, it provides a list of formal parameters and a body (which can be an expression or a block of code) expressed in terms of those parameters. When can we use them? Whenever you would use an anonymous inner class with a single abstract method type in your code, that is a class with just an abstract method in it, you can replace that with a lambda expression. This provides a way of doing much more functional programming style in Java.

The basic syntax of a lambda is either:

         parameters) ->expression  or  (parameters) ->{ statements; }

Listed below are some lambda examples:

       () -> 5                                  // takes no value and returns 5
       x -> 2 * x                               // takes a number and returns the result of doubling it 
       (x, y) -> x – y                          // takes two numbers and returns their difference
       (int x, int y) -> x + y                  // takes two integers and returns their sum
       (String s) -> System.out.print(s)        // takes a string and prints it to console without returning anything

A more practical example is in a graphical user interface application. Because in a GUI application event handler interfaces are functional interfaces and tend to have only one method; when we want to process events, such as keyboards actions, mouse actions, we can replace the anonymous class with a lambda expression, like below:

 // Using anonymous innerclass
btn.setOnAction(new EventHandler<ActionEvent>() {
     @Override
public void handle(ActionEvent event) {
      System.out.println("Hello World!");
     }
 });

Using the new lambda expression syntax, we can replace the above code and simplify it to this:

  // Using lambda expression
btn.setOnAction(event -> System.out.println("Hello World!"));
            

We can also iterate through Collections with lambda. Let us suppose we have the following list:

List<Car> cars = new ArrayList<>();
cars.add(new Car("Audi X",2005));
cars.add(new Car("BMW Y",2013));
cars.add(new Car("Ford Z",2005));

Prior to Java SE8, we would use something like this to loop over the list:

for(Car c : cars) {
	if(c.productionYear > 2007)
System.out.println(c.name);
}

Now, using lambda expression and functional operations, the above code can be simplified into this:

cars.stream().filter((c) -> (c.productionYear > 2007)).forEach((c) -> {
System.out.println(c.name);
});

There is a lot more to lambda expressions and we will also mention them in further sections of this article, but this gives us a basic idea.

Extension Methods

Lambdas support in Java 8 is the result of a sustained and appreciated effort, because it seems that most developers are very excited to start using them. For a better understanding of this effort, let’s take a closer look to one of the changes dictated by lambdas.

Well, the first lesson of using lambdas probably starts with a simple example, like applying a lambda to each item in a collection:

List<?> mylist = ...
mylist.forEach(...); // lambda ...

This is very interesting, because the forEach method doesn’t exist yet, and the solution will be to insert this method into an existing interface (probably it fits well in the Collection interface). But, this is not acceptable without breaking backwards compatibility, because we can’t add methods to an interface in the current implementation. So, we have the lambdas, but we can’t use them!

The salvation lies in a new concept: virtual extension methods (or, known as defender methods). The concept says that we can add methods to interfaces and provide a default implementation. More precisely, Java 8 interfaces allow us to declare new methods and provide a default implementation directly in the interfaces. I know what you’re thinking: “probably it works, but I don’t think I’m going to use this feature very soon!”. Well, … it’s true that is not a day by day technique, and you will probably forget it soon, but keep in mind that it was meant as a step forward for lambdas support and compatibility issues!

Even if it’s not so cool, let me provide a simple example:

  • define a new interface, named MyInterface and implement a default method, named myMethod:
public interface MyInterface {
    
    default void myMethod(){
       System.out.println("I'm a method implemented into an interface ... Who are you ?");
    }
}
  • define a new class and implement the interface, MyInterface (don’t worry, no errors occur):
public class MyClass implements MyInterface {
    //...
}
  • Use this class:
...
MyClass myClass = new MyClass();
myClass.myMethod();
...

Obviously, the output is:

I'm a method implemented into an interface ... Who are you ?

Things seem to be pretty straightforward, but think of this: What happens if you have two interfaces that define a default method with the same signature, and both interfaces are implemented by the same class? Well, as you probably sense already, this will not be successfully compiled. For example, let’s put this scenario in code lines:

  • interface, MyInterface_1:
public interface MyInterface_1 {
     default void myMethod(){
       System.out.println("I'm a  method in interface 1 ...");
    }
}
  • interface, MyInterface_2:
public interface MyInterface_2 {
     default void myMethod(){
       System.out.println("I'm a  method in interface 2 ...");
    }
}
  • the class that implements both interfaces, MyClass:
public class MyClass implements MyInterface_1, MyInterface_2 {
 //...           
}
  • attempt to use MyClass:
...
MyClass myClass = new MyClass();
myClass.myMethod();
...

Compiler error:

class example2.MyClass inherits unrelated defaults for myMethod() from types example2.MyInterface_1 and example2.MyInterface_2

The solution is simple; you need to override the default method, like this:

public class MyClass implements MyInterface_1, MyInterface_2 {
    
    //overriding the default method to avoid inheritance errors
    @Override
    public void myMethod(){
         System.out.println("I'm a overridden method ...");
    }
    
}

Well, it’s true that this solution breaks down the initial implementation. So, you also can call the initial implementation, like this (for interface 1 here):

@Override
public void myMethod(){
 MyInterface_1.super.myMethod();
}

There are a few more aspects to discuss, but we have many other features to present, therefore we end this section by coming back to the forEach method and present its default implementation:

@FunctionalInterface
public interface
Iterable<T> {
    Iterator<T>
iterator();
 
    default void forEach(Consumer<?
super T> action) {
       Objects.requireNonNull(action);
       for (T t : this) {
              action.accept(t);
       }
    }
}

The java.util.function.Consumer parameter enables us to pass in a lambda or a method reference. Now you should have no problem to understand it and raise more questions.

Annotations on Java Types

Annotations are a familiar subject and we all used them, especially in the Java EE environment, but we could only apply annotations on type declarations (classes, methods or variable definitions). Starting with Java SE8, we can apply annotations also to any type use, which means that anywhere we use a type we can add an annotation to that type. The idea of this addition is to be able to detect more errors at compile-time versus runtime, with a little help of the compiler and type checkers.

Unfortunately, Java SE8 doesn’t come with a type checking framework, but it does allow us to use a type checking framework that is implemented as one or pluggable modules that are used in conjunction with the Java compiler. An example of such framework is The Checker Framework created by the University of Washington, which allows developers to detect and prevent errors in Java programs. You need to download the core libraries and add them to your project first. For an example to this, we need to define two objects first:

@Nullable Object obj = null;         // might be null @NonNull Object obj2;                 // never null

Now, we need to force an error and see what the checking framework tells us:

obj.toString();      

In this case, the runtime java.lang.NullPointerException error is replaced by a compile error. To actually see what the compiler tells us, we need to run the code with the -processor checkers.nullness.NullnessChecker parameters. For a NetBeans user, go to Project properties – Compiling – Additional Compiler Options, write the parameters there and uncheck the compile on save option because NetBeans ignores the additional parameters when this option is selected. The output will be:

error: dereference of possibly-null reference obj
obj.toString();
1 error

To run another test, just write:

obj2 = obj;

 The output will be:

error: incompatible types in assignment.
       obj2 = obj;
       found   : @FBCBottom @Nullable Object
       required: @UnknownInitialization @NonNull Object
1 error

Because we can detect more errors at compile-time versus runtime, the entire development process is faster, if a compile-time error occurs, web applications run faster because there is no need for deployment, and also, by using a checking framework we avoid situations when the application flow can’t detect an error.

Generalized Target-Type Inference

Java SE8 aims to provide some improvements to the existing type-argument inference support that will significantly reduce the need for explicit type-arguments in generic method calls. Assuming we have the below example:

class List<E> {
              static<Z> List<Z> nil() {..}
              static<Z> List<Z> cons(Z head, List<Z> tail) {..}
              E head() {..}
}

Instead of writing the below code:

List.cons(42, List.<Integer>nil());
String s = List.<String>nil().head();

We will be able to write this:

List.cons(42, List.nil());
Strings = List.nil().head();

Pretty cool; it surely seems to be a more writable code without explicit type-arguments.

Access to Parameter Names at Runtime

Another addition in Java SE8 is the possibility to access parameter names at runtime. Prior to Java SE8, we had access to the types of the parameters that are being passed, but we couldn’t get the names and in certain situations it is useful to have a name-type pair associated with your parameters. For example, we have the below class:

public class Car {
 
private final String name;
private final String color;
private final String brand;
 
public Car(String name, String color, String brand) {
        this.name = name;
        this.color = color;
        this.brand = brand;
    }
 
}

In Java SE8, to get the names parameters for that class we could simply use:

Parameter[] carsParams;
try {
		carsParams = Car.class.getConstructor(String.class, String.class, 
		String.class).getParameters();
 
		for (Parameter param : carsParams) {
				if (param.isNamePresent()) {
				System.out.println("Parameter name : " + param.getName());
          }
		}

   } catch (NoSuchMethodException | SecurityException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
   }

To see the output, we need to run the code with the -parameters parameter. For a NetBeans user, go to Project properties – Compiling – Additional Compiler Options, write the -parameters there and uncheck the compile on save option because NetBeans ignores the additional parameters when this option is selected.

The output will be:

Parameter name : name
Parameter name : color
Parameter name : brand          

Changes to Core Libraries

Enhance Core Libraries with Lambdas

Lambda expressions are not just a syntactic sugar approach, lambdas actually improves the performance on Java code. For the language to be able to take advantage of lambdas, all of the general APIs in Java had to be modernized. What do I mean by that? Well, in Java SE7, there are over 4000 standard classes and for all of these classes, plus the new ones introduced in Java SE8, whenever an anonymous inner class was used, it was replaced with a lambda expression. This enhanced the performance of the standard core libraries.

Concurrency Updates

In terms of concurrency, Java SE8 has made changes to scalable update variables. Maintaining a single variable and updating it by possibly many threads is a common scalability problem. In such situations, when you have a number of threads, all trying to access and update the same variable at the same time, especially if your code does a lot of writes to that variable but less reads, we can get problems of contention between those threads when trying to access that single variable.

Now, in Java SE8, we can use the DoubleAccumulator and the DoubleAdder new classes that allow us to have multiple threads updating the same variable, but in fact, they update their own copy of that variable. The trick happens when you have to read the variable. When you have to do a read, the system will pause all threads, figure out what the value is, update a single instance that the read is associated with and the give the correct value.

For ConcurrentHashMaps, Java SE8 comes with methods to compute value for keys when they are not present, as well as improved support for scanning and possibly evicting entries and a better support for maps with a large number of elements. There are also new features that improve the functionality and performance for ForkJoinPools and allow them to be used more effectively.

Bulk Data Operations for Collections

Java SE8 added two packages related to bulk data operations for Collections, the java.util.function package, and the java.util.stream. A stream is like an iterator, but with a lot of extra functionality. Now, when you want to process a collection, you can use streams.

Another good thing about streams is that they were implemented in such a way that they can be used either serial, or either parallel, and this is where the power of streams with lambdas shows up. Because of this, developers can stop writing java code specifically serial or specifically parallel and write code that can simply be either by just changing the way streams are created. Also, streams use lazy evaluation and they are not actually reading all the data and methods like getFirst()can end the stream. A few examples of streams and lambdas are shown below:

List<String> players = new ArrayList<>(
Arrays.asList("Rafael Nadal", "Novak Djokovic", "David Ferrer",
                        "Andy Murray", "Juan Martin Del Potro", "Roger Federer"));

// Looping before Java SE8
for (String o : players) {
System.out.println(o);
        }

// Loop using functional operation
players.stream().forEach((o) -> {
System.out.println(o);
        });

        // Loop using member reference
players.stream().forEach(System.out::println);

        // Loop using explicit parameter types
players.stream().forEach((String o) -> {
System.out.println(o);
        });

        // Loop using anonymous innerclass
players.stream().forEach(new Consumer<String>() {
            @Override
public void accept(String o) {
System.out.println(o);
}
        });

The new java.util.stream package will allow developers to use various types of operations and comes with many new and useful methods, like findFirst, findAny, min, max, count, collect, reduce, filter, map, limit, substream and so on; it surely deserves a look.

players.stream().filter((o) -> (o.contains(“Rafa”) || o.contains(“Novak”))) .forEach(System.out::println);

Moreover, Java SE8 comes with a few updates on the java.util.Collections package:

  • java.util.Collections.emtpySortedSet() returns an immutable empty sorted set similar to the existing Collections.emptyList(), Collections.emptyMap(), and Collections.emptySet().
  • java.util.Collections.emtpyNavigableSet() returns an immutable empty navigable set similar to the existing Collections.emptyList(), Collections.emptyMap(), and Collections.emptySet().

Multiple methods for checked/empty/unmodifiable wrappers for the core collection interfaces java.util.SortedSet, java.util.SortedMap, java.util.NavigableMap, and java.util.NavigableSet were added:

  • java.util.Collections.checkedNavigableMap()
  • java.util.Collections.checkedNavigableSet()
  • java.util.Collections.emptyNavigableMap()
  • java.util.Collections.emptyNavigableSet()
  • java.util.Collections.emptySortedSet()
  • java.util.Collections.synchronizedNavigableMap()
  • java.util.Collections.synchronizedNavigableSet()
  • java.util.Collections.unmodifiableNavigableMap()
  • java.util.Collections.unmodifiableNavigableSet()

Parallel Array Sorting

Since Java SE7 has added the Fork/Join framework for lightweight data parallelism, developers around the world have implemented their own algorithms in order to fulfill common parallel tasks like sorting. In order to help developers, Java SE8 has added some more utility methods to the java.util.Arrays class, which now has a parallelSort() method based on multiple signatures for different primitives.

Based on the idea of Doug Lea’s parallel-array implementation and built on top of the Fork/Join framework, the parallelSort() method is designed to improve performance with over 30% on sequential sorts for dual-core systems. A minus to this parallel sort is the fact that you need a memory working space at the same size as the array to be sorted. This means that if you are trying to sort a very big array, it is being held in memory, and then you need the same space in memory again in order to hold the working data as it is sorted. A few examples of parallel sorting are shown below:

Integer[] array = {44,84,2,68,9,0,32,8,35,86,33,25,55,5,66, ...};

       // Sort the first 10 positions ascending
Arrays.parallelSort(array, 0, 10);

       // Sort the last 10 positions descending
Arrays.parallelSort(array, array.length - 10, array.length, Collections.reverseOrder());

       // Sort array ascending
Arrays.parallelSort(array);

       // Sort array descending
Arrays.parallelSort(array, Collections.reverseOrder());

Date and Time API

Java SE8 will have a completely new Date and Time API that will contain classes like LocalDate, LocalTime, LocalDateTime, ZoneDateTime and so on. The API will use as the default calendar the standard defined in ISO-8601, which is a calendar based on the Gregorian calendar and globally used as the default standard for representing date and time.

Moreover, the new Date and Time API will use the Unicode Common Locale Data Repository (CLDR), with support for the world’s languages and with the world’s largest collection of locale data available, and also the Time-Zone Database (TZDB) that provides information about every time zone change globally since 1970.Enough being said, let us see a few examples:

LocalTime localTime = LocalTime.now();
System.out.println("Local time now: " + localTime);

LocalTime newTime = localTime.plusMinutes(13);
System.out.println("Local new time: " + newTime);

       LocalDate today = LocalDate.now();
System.out.println("Today: " + today);

LocalDate yesterday = today.minusDays(1);
System.out.println("Yesterday: " + yesterday);

LocalDateTime localDateTime = yesterday.atTime(8, 0);
System.out.println("Yesterday at 8 o'clock: " + localDateTime);

LocalDateTime earlyMorning = LocalDate.of(2014, Month.FEBRUARY, 10).atStartOfDay();
System.out.println("Early morning of today: " + earlyMorning);

The output will be:

Local time now: 07:07:59.864
Local new time: 07:20:59.864
Today: 2014-02-10
Yesterday: 2014-02-09
Yesterday at 8 o'clock:
2014-02-09T08:00
Early morning of today:
2014-02-10T00:00

To see a more complex example, we will try to find out how long a flight from Bucharest to London will be:

ZoneId Bucharest = ZoneId.of("Europe/Bucharest");
ZoneId London = ZoneId.of("Europe/London");

LocalDate date = LocalDate.of(2014, Month.FEBRUARY, 14);
       LocalTime takeoff = LocalTime.of(13, 20);
       LocalTime landing = LocalTime.of(15, 10);
       Duration flightTime = Duration.between(
ZonedDateTime.of(date, takeoff, Bucharest), 
ZonedDateTime.of(date, landing, London));

System.out.println("Flight time: " + flightTime.toHours() + 
                " hours and " + flightTime.toMinutes() % 60 + " minutes");

The output will be:

Flight time: 3 hours and 50 minutes

New Methods to I/O and NIO

Java SE8 also comes with some additions to the I/O and NIO packages, as listed below:

  • java.nio.file.Files.lines(Path, Charset) method has been added and it will read lines of a text as a Stream.
  • java.nio.file.Files.lines(Path) method has been added in order to be used withoud method references, it uses UTF-8 as the default charset for encoding and decoding.
  • java.nio.file.Files.newBufferedReader(Path), java.nio.file.Files.newBufferedWriter(Path,OpenOption...), java.nio.file.Files.readAllLines(Path), and java.nio.file.Files.write(Path,Iterable,OpenOption...) methods have been added.

Base64 Encoding and Decoding

Finally, Java SE8 will now have a standard way of dealing with base64 encoding and decoding and developers can stop using non-public APIs to do that. Methods line encode, encodeToString, decode and wrap are now available. We can see an example below:

String asB64;
	byte[] asBytes = Base64.getDecoder().decode("SmF2YTggQmFzZTY0IEVuY29kaW5nICYgRGVjb2Rpbmc=");
	try {
	// Encode
  asB64 = Base64.getEncoder().encodeToString("Java8 Base64 Encoding & Decoding".getBytes("utf-8"));
		System.out.println(asB64);

	// Decode
		System.out.println(new String(asBytes, "utf-8"));
	 } catch (UnsupportedEncodingException ex) {
		Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
	 }
       

The output will be:

SmF2YTggQmFzZTY0IEVuY29kaW5nICYgRGVjb2Rpbmc=
Java8 Base64 Encoding & Decoding

Changes to Internationalization Support

Related to internationalization, a few changes were made in the java.util and java.text packages and in the java.lang.Character class. Moreover, Java has changed the support for Unicode standard Character and String from version 6.0 to 6.2 and added eleven new constants to the java.lang.Character.UnicodeBlock class and seven enums to java.lang.Character.UnicodeScript.

Some of the changes done in the java.util.Calendar package are listed below:

  • A new inner class was added, java.util.Calendar.Builder, to provide support for constructing java.util.Calendar objects with various parameters.
  • The java.util.Calendar.getCalendarType() and java.util.GregorianCalendar.getCalendarType() methods have been added to return the calendar type as defined by the Unicode Locale Data Markup Language.
  • Added support for narrow display names for java.util.Calendar fields of era, month, day of the week, and so on.
  • The getFirstDayOfWeek() and getMinimumDaysInFirstWeek() methods have been added to java.util.spi.CalendarDataProvider, both of them being locale-dependent on country.
  • Added support for Minguo (Taiwan) and Hijrah (Islamic) calendars

In the java.util.Locale package has been added support to:

  • Unicode Common Locale Data Repository which contains XML-based locale data that are agreed upon by major entities in the world.
  • BCP47 locale matching as defined in RFC 4647
  • Clarification of the default locale for each locale-sensitive operation

Changes to Security

Some big improvements have been added in Java SE8 in the security section, which now has a better implementation of SecureRandom API that until now could cause a mix of blocking/non-blocking system calls; two new interfaces have been added to enhance certificate Revocation-Checking API, the CertPathChecker and CertPathParameters interfaces, and also a command line debug option: -Djava.security.debug=certpath.

Java SE8 will also have support for TLS Server Name Indication (SNI) Extension to allow more flexible secure virtual hosting and virtual-machine infrastructure based on SSL/TLS protocols, will provide implementation for NSA Suite B cryptographic algorithms and a new type of network permissions that can grant access in terms of URLs rather than low-level IP addresses.

Changes to the Platform

JavaFX applications can now be launched directly! Prior to Java SE8, in the JavaFX environment, we had the public static void main method, which just calls the launch method on JavaFX applications. To make life a little easier for developers, this part has been integrated directly into the command line and we can call the launch method directly, rather than thru the public static void main.

Java SE8 also introduces the concept of a Java SE Profile, a well-defined subset of the Java SE Platform in order to allow applications that use just a part of the Platform to run on resource-constrained devices. This means that starting with Java SE8, developers don’t need the full JRE in order to run Java applications and we can choose one of the below packages:

  • Compact1 Profile: requires about 10Mb
  • Compact2 Profile: requires about 17Mb
  • Compact3 Profile:requires about 24Mb
  • Full JRE: requires about 140Mb

Each profile includes specific packages; how you choose the suitable profile for you depends on the application requests.

In order to prepare the language and the platform for modularization and getting ready for project Jigsaw, some new features have been added and others have been fixed. You can now use a ServiceLoader rather than proprietary SPI code, a JDK tool that will analyze the application code dependencies between APIs has been added and some APIs that can affect modularization has been deprecated.

Changes to the Virtual Machine

A few months ago, James Laskey presented at JavaOne the Nashorn JavaScript engine implemented fully in Java and integrated into the Java 8 JRE. Based on the Da Vinci Machine (JSR 292) and developed by Oracle, Nashorn will allow developers to embed JavaScript in Java applications and to develop standalone JavaScript applications, will use the existing javax.script API and will have a new command-line tool to run JavaScript, named jjs.

Apparently, since the Rhino JavaScript engine used before was slower than many of the current JavaScript engines available today, Nashorn has been created to be three to five times faster and a lot smaller than Rhino.

Conclusion

In this article, we went through the most important additions introduced in the new Java SE8 release, from syntactic additions on the language itself to improvements on the virtual machine, the purpose of this article was to give us a quick overview to the changes that happened. As we have discovered previously in this article, the introduction of lambda expressions mixed with streams and the new date and time API seems to be the biggest feature from a developer point of view. Nevertheless, the significant improvements over the core libraries, the Collections, the I/O and NIO, and the support for base64 encoding and decoding will make our lives happier. As a note, the improvements done in the security section, even if they were not fully introduced in this article, along with small features not mentioned in this article, deserve special attention.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories