JAVA8特性

1.lambda表达式

Lambda表达式(也称为闭包)是Java 8中最大和最令人期待的语言改变.它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理.最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成,例如:

Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来:

Arrays.asList( "a", "b", "d" ).forEach( e -> {
    System.out.print( e );
    System.out.print( e );
} );

用来给数组排序的Comparator接口,在Java8里面,我们可以把它视为一个函数,用lambda表示式简化如下的操作:

String[] str = "aa、bb、cc、dd".split("、");
Arrays.sort(str, (s1, s2) -> {
    return s1.toLowerCase().compareTo(s2.toLowerCase());
});

Lambda表达式可以引用类成员和局部变量(会将这些变量隐式得转换成final的),例如下列代码块的效果完全相同:

String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach( 
    ( String e ) -> System.out.print( e + separator ) );

final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach( 
    ( String e ) -> System.out.print( e + separator ) );

Lambda表达式有返回值,返回值的类型也由编译器推理得出.如果Lambda表达式中的语句块只有一行,则可以不用使用return语句,下列代码片段效果相同:

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );

Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念.函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式. java.lang.Runnablejava.util.concurrent.Callable是函数式接口的最佳例子.在实践中,函数式接口非常脆弱: 只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败.为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义:

@FunctionalInterface
public interface Functional {
    void method();
}

不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的.

@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();

    default void defaultMethod() {            
    }        
}

2.接口的默认方法和静态方法

Java 8使用两个新概念扩展了接口的含义: 默认方法和静态方法.

默认方法使得开发者可以在 不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法.

默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要.接口提供的默认方法会被接口的实现类继承或者覆写.

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";
    }
}

Defaulable接口使用关键字default定义了一个默认方法notRequired().DefaultableImpl类实现了这个接口,同时默认继承了这个接口中的默认方法;OverridableImpl类也实现了这个接口,但覆写了该接口的默认方法,并提供了一个不同的实现.

Java 8带来的另一个有趣的特性是在接口中可以定义静态方法,例子代码如下:

private interface DefaulableFactory {
    // Interfaces now allow static methods
    static Defaulable create( Supplier< Defaulable > supplier ) {
        return supplier.get();
    }
}

使用场景:

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

##输出
Default implementation
Overridden implementation

由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高.默认方法允许在不打破现有继承体系的基础上改进接口.该特性在官方库中的应用是: 给java.util.Collection接口添加新方法,如stream()parallelStream()forEach()removeIf()等等.

注意:尽管默认方法有这么多好处,但在实际开发中应该谨慎使用: 在复杂的继承体系中,默认方法可能引起歧义和编译错误.

3.重复注解

在Java 8中使用@Repeatable注解定义重复注解.

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

正如我们所见,这里的Filter类使用@Repeatable(Filters.class)注解修饰,而Filters是存放Filter注解的容器,编译器尽量对开发者屏蔽这些细节.这样,Filterable接口可以用两个Filter注解注释.

另外,反射API提供了一个新的方法: getAnnotationsByType(),可以返回某个类型的重复注解,例如Filterable.class.getAnnoation(Filters.class)将返回两个Filter实例,输出到控制台的内容如下所示:

filter1
filter2

4.Java编译器的新特性:参数名

为了在运行时获得Java程序中方法的参数名称,老一辈的Java程序员必须使用不同方法. Java 8终于将这个特性规范化,在语言层面(使用反射API和Parameter.getName()方法)和字节码层面(使用新的javac编译器以及-parameters参数)提供支持.

package com.javacodegeeks.java8.parameter.names;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class ParameterNames {
    public static void main(String[] args) throws Exception {
        Method method = ParameterNames.class.getMethod( "main", String[].class );
        for( final Parameter parameter: method.getParameters() ) {
            System.out.println( "Parameter: " + parameter.getName() );
        }
    }
}

在Java 8中这个特性是默认关闭的,因此如果不带-parameters参数编译上述代码并运行,则会输出如下结果:

Parameter: arg0

如果带-parameters参数,则会输出如下结果(正确的结果):

Parameter: args

如果你使用Maven进行项目管理,则可以在maven-compiler-plugin编译器的配置项中配置-parameters参数:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <configuration>
        <compilerArgument>-parameters</compilerArgument>
        <source>1.8</source>
        <target>1.8</target>
    </configuration>
</plugin>

5.Streams

新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中.这是目前为止最大的一次对Java库的完善,以便开发者能够写出更加有效、更加简洁和紧凑的代码.

举个例子:

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

Task类有一个分数(或伪复杂度)的概念,另外还有两种状态: OPEN或者CLOSED.现在假设有一个task集合:

final Collection< Task > tasks = Arrays.asList(
    new Task( Status.OPEN, 5 ),
    new Task( Status.OPEN, 13 ),
    new Task( Status.CLOSED, 8 ) 
);

现在要查询在这个task集合中一共有多少个OPEN状态的点?
在Java 8之前,要解决这个问题,则需要使用foreach循环遍历task集合;但是在Java 8中可以利用steams解决.

// 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 );

运行这个方法的控制台输出是:

Total points: 18

过程: 第一: tasks集合被转换成steam表示;
第二: 在steam上的filter操作会过滤掉所有CLOSED的task;
第三: mapToInt操作基于每个task实例的Task::getPoints方法将task流转换成Integer集合;
最后,通过sum方法计算总和,得出最后的结果.

steam的另一个价值是创造性地支持并行处理(parallel processing).对于上述的tasks集合,我们可以用下面的代码计算所有任务的点数之和:

// Calculate total points of all tasks
final double totalPoints = tasks
   .stream()
   .parallel()
   .map( task -> task.getPoints() ) // or map( Task::getPoints ) 
   .reduce( 0, Integer::sum );

System.out.println( "Total points (all tasks): " + totalPoints );

这里我们使用parallel方法并行处理所有的task,并使用reduce方法计算最终的结果.控制台输出如下:

Total points(all tasks): 26.0

对于一个集合,经常需要根据某些条件对其中的元素分组.利用steam提供的API可以很快完成这类任务,代码如下:

// Group tasks by their status
final Map< Status, List< Task > > map = tasks
    .stream()
    .collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );

控制台的输出如下:

{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}

6.Date/Time

Java 8中新的时间和日期管理API深受Joda-Time影响,并吸收了很多Joda-Time的精华.新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类.新设计的API认真考虑了这些类的不变性(从java.util.Calendar吸取的教训),如果某个实例需要修改,则返回一个新的对象.

Clock类使用时区来返回当前的纳秒时间和日期.Clock可以替代System.currentTimeMillis()TimeZone.getDefault().

// Get the system clock as UTC offset 
final Clock clock = Clock.systemUTC();
System.out.println(clock.instant());
System.out.println(clock.millis());

2014-04-12T15:19:29.282Z
1397315969360

LocalDate仅仅包含ISO-8601日历系统中的日期部分;LocalTime则仅仅包含该日历系统中的时间部分.这两个类的对象都可以使用Clock对象构建得到.

// Get the local date and local time
final LocalDate date = LocalDate.now();
final LocalDate dateFromClock = LocalDate.now( clock );

System.out.println( date );    // 2014-04-12
System.out.println( dateFromClock ); // 2014-04-12

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

System.out.println( time );    // 11:25:54.568
System.out.println( timeFromClock ); // 11:25:54.568

LocalDateTime类包含了LocalDate和LocalTime的信息,但是不包含ISO-8601日历系统中的时区信息.

// Get the local date/time
final LocalDateTime datetime = LocalDateTime.now();
final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );

System.out.println( datetime );    // 2014-04-12T11:37:52.309
System.out.println( datetimeFromClock );    // 2014-04-12T11:37:52.309

如果你需要特定时区的data/time信息,则可以使用ZoneDateTime,它保存有ISO-8601日期系统的日期和时间,而且有时区信息.

// 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" ) );

Duration类,它持有的时间精确到秒和纳秒.这使得我们可以很容易得计算两个日期之间的不同.

// Get duration between two dates
final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );

final Duration duration = Duration.between( from, to );
System.out.println( "Duration in days: " + duration.toDays() );
System.out.println( "Duration in hours: " + duration.toHours() );

这个例子用于计算2014年4月16日和2015年4月16日之间的天数和小时数,输出结果如下:

Duration in days: 365
Duration in hours: 8783

完整的例子

public static void main(String[] args) {
    // 获取系统时钟
    final Clock clock = Clock.systemUTC();
    System.out.println("当前时间: " + clock.instant());
    System.out.println("当前时间的毫秒值: " + clock.millis());

    // 获取当前日期, 只有年月日
    final LocalDate date = LocalDate.now();
    final LocalDate dateFromClock = LocalDate.now(clock);
    System.out.println("当前日期: " + date);
    System.out.println("当前日期: " + dateFromClock);
    System.out.println("年份: " + date.getYear());
    System.out.println("月份:" + date.getMonthValue());
    System.out.println("月中的第几天: " + date.getDayOfMonth());
    LocalDate nextWeek = date.plus(1, ChronoUnit.WEEKS);
    System.out.println("下周时间: " + nextWeek);

    // 获取当前时间, 时分秒毫秒
    final LocalTime time = LocalTime.now();
    final LocalTime timeFromClock = LocalTime.now(clock);
    System.out.println("当前时间:"+time);
    System.out.println("当前时间:"+timeFromClock);

    // 获取当前的日期+时间
    final LocalDateTime datetime = LocalDateTime.now();
    final LocalDateTime datetimeFromClock = LocalDateTime.now(clock);
    DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
    System.out.println("默认时间:"+datetime);
    System.out.println("默认时间:"+datetimeFromClock);
    System.out.println("格式化时间:"+datetime.format(format));

    // 获取两个时间的间隔
    final LocalDateTime from = LocalDateTime.of(2014, Month.APRIL, 16, 0, 0, 0);
    final LocalDateTime to = LocalDateTime.of(2015, Month.APRIL, 16, 23, 59, 59);
    final Duration duration = Duration.between(from, to);
    System.out.println("两个时间间隔的天数: " + duration.toDays());
    System.out.println("两个时间间隔的小时数: " + duration.toHours());
}

7.Base64

对Base64编码的支持已经被加入到Java 8官方库中,这样不需要使用第三方库就可以进行Base64编码.

import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class Base64s {
    public static void main(String[] args) {
        final String text = "Base64 finally in Java 8!";

        final String encoded = Base64
            .getEncoder()
            .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
        System.out.println( encoded );

        final String decoded = new String( 
            Base64.getDecoder().decode( encoded ),
            StandardCharsets.UTF_8 );
        System.out.println( decoded );
    }
}

// 输出
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!

8.并行数组

Java8版本新增了很多新的方法,用于支持并行数组处理.最重要的方法是parallelSort(),可以显著加快多核机器上的数组排序.

import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;

public class ParallelArrays {
    public static void main( String[] args ) {
        long[] arrayOfLong = new long [ 20000 ];        

        Arrays.parallelSetAll( arrayOfLong, 
            index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach( 
            i -> System.out.print( i + " " ) );
        System.out.println();

        Arrays.parallelSort( arrayOfLong );        
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach( 
            i -> System.out.print( i + " " ) );
        System.out.println();
    }
}

上述这些代码使用parallelSetAll()方法生成20000个随机数,然后使用parallelSort()方法进行排序.这个程序会输出乱序数组和排序数组的前10个元素.

9.Nashorn引擎

jjs是一个基于标准Nashorn引擎的命令行工具,可以接受js源码并执行.例如,我们写一个func.js文件,内容如下:

function f() { 
     return 1; 
}; 

print( f() + 1 );

可以在命令行中执行这个命令: jjs func.js,控制台输出结果是:

2

10.类依赖分析器

jdeps是一个相当棒的命令行工具,它可以展示包层级和类层级的Java类依赖关系,它以.class文件、目录或者Jar文件为输入,然后会把依赖关系输出到控制台.

jdeps org.springframework.core-3.0.5.RELEASE.jar

这个命令会输出很多结果,我们仅看下其中的一部分: 依赖关系按照包分组,如果在classpath上找不到依赖,则显示"not found".

org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
   org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.io                                            
      -> java.lang                                          
      -> java.lang.annotation                               
      -> java.lang.ref                                      
      -> java.lang.reflect                                  
      -> java.util                                          
      -> java.util.concurrent                               
      -> org.apache.commons.logging                         not found
      -> org.springframework.asm                            not found
      -> org.springframework.asm.commons                    not found
   org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.lang                                          
      -> java.lang.annotation                               
      -> java.lang.reflect                                  
      -> java.util

results matching ""

    No results matching ""