Java 8 new features study notes

Lambda expressions and functional interfaces

  • Lambda expressions (also known as closures) are the biggest and most anticipated language change in Java 8. It allows us to pass functions as parameters to a method, or treat the code itself as data.
  • The simplest lambda expressions can consist of comma-separated parameter lists, -> symbols, and statement blocks, for example:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
//请注意参数e的类型是由编译器推测出来的
  • Lambda may return a value. The type of the return value is also inferred by the compiler.
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

or

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );
  • Java 8 adds a special annotation @FunctionalInterface (all existing interfaces of class libraries in Java 8 are annotated with @FunctionalInterface).
@FunctionalInterface
public interface Functional {
    void method();
}
  • One thing to keep in mind is that default methods and static methods do not affect the contract of functional interfaces and can be used arbitrarily:
@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();

    default void defaultMethod() {            
    }        
}

Default and static methods of interfaces

  • Java 8 extends interface declarations with two new concepts, default methods and static methods.
  • Default methods differ from abstract methods in that abstract methods must require implementation, but default methods do not. Instead, every interface must provide a so-called default implementation, so that all interface implementers will inherit it by default (and can override this default implementation if necessary).
private interface Defaulable {
    // Interfaces now allow default methods, the implementer may or 
    // may not implement (override) them.
    default String notRequired() { 
        return "Default implementation"; 
    }        
}

private static class DefaultableImpl implements Defaulable {
}

private static class OverridableImpl implements Defaulable {
    @Override
    public String notRequired() {
        return "Overridden implementation";
    }
}
  • Another interesting feature brought by Java 8 is that interfaces can declare (and can provide implementations) static methods.
private interface DefaulableFactory {
    // Interfaces now allow static methods
    static Defaulable create( Supplier< Defaulable > supplier ) {
        return supplier.get();
    }
}
  • The following small code snippet glues the default method above with the static method.
public static void main( String[] args ) {
    Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
    System.out.println( defaulable.notRequired() );

    defaulable = DefaulableFactory.create( OverridableImpl::new );
    System.out.println( defaulable.notRequired() );
}

method reference

  • Method references provide a very useful syntax for directly referencing methods or constructors of existing Java classes or objects (instances). Used in conjunction with lambdas, method references can make language constructs more compact and concise, reducing redundant code.
  • eg:
public static class Car {
    public static Car create( final Supplier< Car > supplier ) {
        return supplier.get();
    }              

    public static void collide( final Car car ) {
        System.out.println( "Collided " + car.toString() );
    }

    public void follow( final Car another ) {
        System.out.println( "Following the " + another.toString() );
    }

    public void repair() {   
        System.out.println( "Repaired " + this.toString() );
    }
}
  • The first method reference is a constructor reference, whose syntax is Class::new, or more generally Class<T>::new. Note that the constructor has no parameters.
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );
  • The second method reference is a static method reference, and its syntax is Class::static_method. Note that this method accepts a parameter of type Car.
cars.forEach( Car::collide );
  • The third method reference is a method reference to an arbitrary object of a specific class, and its syntax is Class::method. Note that this method has no parameters.
cars.forEach( Car::repair );
  • Finally, the fourth method reference is a method reference for a specific object, and its syntax is instance::method. Note that this method accepts a parameter of type Car
final Car police = Car.create( Car::new );
cars.forEach( police::follow );

Duplicate annotations

  • This feature has become very popular and widely used since Java 5 introduced the annotation mechanism. However, one limitation of using annotations is that the same annotation can only be declared once at the same location, not multiple times. Java 8 breaks this rule and introduces the duplicate annotation mechanism, so that the same annotation can be declared multiple times in the same place.
  • The repeat annotation mechanism itself must be annotated with @Repeatable. In fact, this is not a language-level change, but more of a compiler trick, and the underlying principles remain the same. example:
package com.javacodegeeks.java8.repeatable.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class RepeatingAnnotations {
    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    public @interface Filters {
        Filter[] value();
    }

    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    @Repeatable( Filters.class )
    public @interface Filter {
        String value();
    };

    @Filter( "filter1" )
    @Filter( "filter2" )
    public interface Filterable {        
    }

    public static void main(String[] args) {
        for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
            System.out.println( filter.value() );
        }
    }
}

better type inference

  • The Java 8 compiler has greatly improved in type inference. In many scenarios, the compiler can deduce the data type of a parameter, making the code more concise.
package com.hyman.test;

public class Value< T > {
    public static< T > T defaultValue() { 
        return null; 
    }

    public T getOrDefault( T value, T defaultValue ) {
        return ( value != null ) ? value : defaultValue;
    }
}
  • The following code is an application of the Value type:
package com.hyman.test;

public class TypeInference {
    public static void main(String[] args) {
        final Value< String > value = new Value<>();
        value.getOrDefault( "22", Value.defaultValue() );
    }
}

Broaden the application scenarios of annotations

  • Java 8 broadens the application scenarios of annotations. Now, annotations can be used on almost any element: local variables, interface types, superclasses and interface implementation classes, and even on function exception definitions.
package com.hyman.test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;

public class Annotations {
    @Retention( RetentionPolicy.RUNTIME )
    @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
    public @interface NonEmpty {        
    }

    public static class Holder< @NonEmpty T > extends @NonEmpty Object {
        public void method() throws @NonEmpty Exception {            
        }
    }

    @SuppressWarnings( "unused" )
    public static void main(String[] args) {
        final Holder< String > holder = new @NonEmpty Holder< String >();        
        @NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();        
    }
}

Optional

  • The most common bug in Java applications is the null exception. Optionals class to solve NullPointerException, so as to avoid the source code being polluted by various null checks, so that developers can write cleaner code.
  • Optional is just an easy: hold a value of type T or null. It provides some useful interfaces to avoid explicit null checks
Optional< String > fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );        
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) ); 
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
//如果Optional实例持有一个非空值,则isPresent()方法返回true,否则返回falseorElseGet()方法,Optional实例持有null,则可以接受一个lambda表达式生成的默认值;map()方法可以将现有的Opetional实例的值转换成新的值;orElse()方法与orElseGet()方法类似,但是在持有null的时候返回传入的默认值。
Optional< String > firstName = Optional.of( "Tom" );
System.out.println( "First Name is set? " + firstName.isPresent() );        
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) ); 
System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
System.out.println();

Streams

  • The new Stream API (java.util.stream) brings functional programming of the build environment to the Java library. This is by far the largest improvement to the Java library so that developers can write more efficient, concise, and compact code.
  • Stream API greatly simplifies collection operations
  • The difference between Stream and Collection: Collection is a static in-memory data structure, while Stream is about computation. The former is mainly oriented to memory and stored in memory, while the latter is mainly oriented to CPU and realizes calculation through CPU.
public class Streams  {
    private enum Status {
        OPEN, CLOSED
    };

    private static final class Task {
        private final Status status;
        private final Integer points;

        Task( final Status status, final Integer points ) {
            this.status = status;
            this.points = points;
        }

        public Integer getPoints() {
            return points;
        }

        public Status getStatus() {
            return status;
        }

        @Override
        public String toString() {
            return String.format( "[%s, %d]", status, points );
        }
    }
}
final Collection< Task > tasks = Arrays.asList(
    new Task( Status.OPEN, 5 ),
    new Task( Status.OPEN, 13 ),
    new Task( Status.CLOSED, 8 ) 
);
  • Solved with steams: A list that includes a sequence of elements, and supports sequential and parallel processing.
// Calculate total points of all active tasks using sum()
final long totalPointsOfOpenTasks = tasks
    .stream()
    .filter( task -> task.getStatus() == Status.OPEN )
    .mapToInt( Task::getPoints )
    .sum();

System.out.println( "Total points: " + totalPointsOfOpenTasks );
  • serial and parallel streams
    • There are two kinds of streams, serial and parallel. The operations on serial streams are performed sequentially in one thread, while parallel streams are performed simultaneously on multiple threads. Parallel and serial streams can be switched between each other: the serial stream is returned by stream.sequential(), and the parallel stream is returned by stream.parallel(). Compared with serial streams, parallel streams can greatly improve the execution efficiency of programs.
  • Serial sort:
List<String> list = new ArrayList<String>();
for(int i=0;i<1000000;i++){
double d = Math.random()*1000;
list.add(d+"");
}
long start = System.nanoTime();//获取系统开始排序的时间点
int count= (int) ((Stream) list.stream().sequential()).sorted().count();
long end = System.nanoTime();//获取系统结束排序的时间点
long ms = TimeUnit.NANOSECONDS.toMillis(end-start);//得到串行排序所用的时间
System.out.println(ms+”ms”);
  • Parallel sorting:
List<String> list = new ArrayList<String>();
for(int i=0;i<1000000;i++){
double d = Math.random()*1000;
list.add(d+"");
}
long start = System.nanoTime();//获取系统开始排序的时间点
int count = (int)((Stream) list.stream().parallel()).sorted().count();
long end = System.nanoTime();//获取系统结束排序的时间点
long ms = TimeUnit.NANOSECONDS.toMillis(end-start);//得到并行排序所用的时间
System.out.println(ms+”ms”);
串行输出为 1200ms,并行输出为 800ms。可见,并行排序的时间相比较串行排序时间要少不少。
  • Intermediate operation
    • This operation keeps the stream in an intermediate state, allowing further operations. It returns a Stream, allowing more chaining operations. Common intermediate operations are:
      • filter(): filter elements;
      • sorted(): sort the elements;
      • map(): mapping of elements;
      • distinct(): remove duplicate elements;
      • subStream(): Get subStream, etc.
list.stream()
.filter((s) -> s.startsWith("s"))
.forEach(System.out::println);
  • Terminate operation
    • This operation must be the last operation on the stream, and once called, the Stream has reached a terminated state and cannot be used any more. Common termination operations are:
      • forEach(): process each element;
      • toArray(): export elements to an array;
      • findFirst(): returns the first matching element;
      • anyMatch(): Whether there are matching elements, etc.
list.stream() //获取列表的 stream 操作对象
.filter((s) -> s.startsWith("s"))//对这个流做过滤操作
.forEach(System.out::println);

Date/Time

  • Java 8 takes the best of Joda-Time to create a great API for Java with a fresh start. The new java.time contains all the classes for Clock, LocalDate, LocalTime, LocalDateTime, ZonedDateTime and Duration. The historic Date class has a new toInstant() method for converting a Date into a new representation. These new localized time and date APIs greatly simplify the management of date times and localization.
//LocalDate
LocalDate localDate = LocalDate.now(); //获取本地日期
localDate = LocalDate.ofYearDay(2014, 200); // 获得 2014 年的第 200 天 
System.out.println(localDate.toString());//输出:2014-07-19
localDate = LocalDate.of(2014, Month.SEPTEMBER, 10); //2014 年 9 月 10 日 
System.out.println(localDate.toString());//输出:2014-09-10
//LocalTime
LocalTime localTime = LocalTime.now(); //获取当前时间
System.out.println(localTime.toString());//输出当前时间
localTime = LocalTime.of(10, 20, 50);//获得 10:20:50 的时间点
System.out.println(localTime.toString());//输出: 10:20:50
//Clock 时钟
Clock clock = Clock.systemDefaultZone();//获取系统默认时区 (当前瞬时时间 )
long millis = clock.millis();//
  • Clock can replace System.currentTimeMillis() and TimeZone.getDefault().
// Get the system clock as UTC offset 
final Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );
  • LocalDate contains only the date part of the ISO-8601 calendar system; LocalTime contains only the time part of the calendar system. Objects of both classes can be constructed using Clock objects.
// Get the local date and local time
final LocalDate date = LocalDate.now();
final LocalDate dateFromClock = LocalDate.now( clock );

System.out.println( date );
System.out.println( dateFromClock );

// Get the local date and local time
final LocalTime time = LocalTime.now();
final LocalTime timeFromClock = LocalTime.now( clock );

System.out.println( time );
System.out.println( timeFromClock );
  • If you need data/time information for a specific time zone, you can use ZoneDateTime, which holds the date and time of the ISO-8601 date system, and time zone information.
// Get the zoned date/time
final ZonedDateTime zonedDatetime = ZonedDateTime.now();
final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );

System.out.println( zonedDatetime );
System.out.println( zonedDatetimeFromClock );
System.out.println( zonedDatetimeFromZone );

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326000191&siteId=291194637