Lambda表达式是整个Java 8体系中最让人期待的特性,它允许我们將函数作为一个方法的参数传递到方法体中或者將一段代码作为数据,这些概念有过函数式编程经验的会比较熟悉,很多基于Java虚拟机平台的语言(Groovy、Scala...)都引入了Lambda表达式。
Lambda表达式的设计研讨,相关社区贡献的巨大的时间和精力,最后进行了相应的取舍,形成了简洁而紧凑的语法结构。在它最简单的形式中,可以使用逗号分割参数列表,使用->符号,例如:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );需要注意的事参数e的类型由编译器来推断。除此之外,你还可以显式的为参数指定一个类型,用小括号括起来,例如:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );为了防止lamdba表达式体过于复杂,可以向Java中函数的定义一样,把它放在方括号中,例如:
Arrays.asList( "a", "b", "d" ).forEach( e -> { System.out.print( e ); System.out.print( e ); } );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表达式。java.lang.Runnable和java.util.concurrent.Callable就是两个很好的函数式接口的例子。在编程实践中,函数式接口是相当容易被破坏的,如果有人在接口的定义中添加了另外一个方法,它將不再是函数式接口而且编译过程可能会失败。为了解决这种易破坏性并且显式的声明该接口属于函数式接口,Java 8新增了一个特殊的注解@FunctionalInterface,我们来看一下一个简单的函数式接口的定义:
@FunctionalInterface public interface Functional { void method(); }有一件事需要注意:default和static方法并不破坏函数式接口的约定,可以声明如下:
@FunctionalInterface public interface FunctionalDefaultMethods { void method(); default void defaultMethod() { } }lambda表达式是Java 8最大的卖点,它潜在的吸引越来越多的开发者使用这个伟大的平台并且提供了先进的纯Java支持的函数式编程的概念。更多细节请参考官方文档。
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声明了一个default方法notRequired()。类DefaultableImpl实现了该接口,保留了notRequired()方法默认的实现,另外一个类OverridableImpl重写了默认的实现。
private interface DefaulableFactory { // Interfaces now allow static methods static Defaulable create( Supplier< Defaulable > supplier ) { return supplier.get(); } }下面一小段代码展示了上例中static方法和default方法的使用:
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 implementationdefault方法在jvm中的实现是非常高效的,并且提供了方法调用的字节码指令。default方法允许对现有的Java接口进行演变而不打断编译过程。在java.util.Collection接口中新增stream(), parallelStream(), forEach(), removeIf(), …方法就是很好的例子。
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() ); } }第一种方法引用类型是构造器引用,使用Class::new语法或针对泛型的Class<T>::new,请注意构造函数没有参数。
final Car car = Car.create( Car::new ); final List< Car > cars = Arrays.asList( car );第二种是静态方法引用,使用Class:static_method语法。需要注意的是该方法接收了一个Car类型的参数。
cars.forEach( Car::collide );第三种是针对任意对象的实例方法引用,使用Class:method语法。请注意该方法没有接收任何参数。
cars.forEach( Car::repair );最后一种是特定类实例的实例方法引用,使用instance::method语法。注意该方法接收一个Car类型的参数。
final Car police = Car.create( Car::new ); cars.forEach( police::follow );运行这些案例,控制台输出:
Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d更多关于方法引用的细节,请参考 官方文档。
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() ); } } }我们可以看到,Filter是使用@Repeatable修饰的注解类。Filters注解是Filter注解的持有者,但是Java编译器试图向开发人员掩饰它的存在。在Filterable接口中定义了两次Filter。
filter1 filter2更多细节请查看 官方文档。
public class Value< T > { public static< T > T defaultValue() { return null; } public T getOrDefault( T value, T defaultValue ) { return ( value != null ) ? value : defaultValue; } }这里使用Value<String>类型。
public class TypeInference { public static void main(String[] args) { final Value< String > value = new Value<>(); value.getOrDefault( "22", Value.defaultValue() ); } }Value.defaultValue()的类型参数不需要提供,由编译器推断得到。在Java 7中,这个例子将不能编译通过,需要更改为Value.<String>defaultValue()。
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<>(); } }ElementType.TYPE_USE和ElementType.TYPE_PARAMETER是描述注解使用环境的两个新的元素。Java注解处理API也发生了一些细微的变化去识别那些新的注解类型。
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() ); } } }如果你使用-parameters参数编译这个类,运行该应用时将会看到:
Parameter: arg0使用-parameters给编译器传递参数,程序运行时將输出不同的结果:
Parameter: args对于有经验的Maven使用人员,为编译器增加-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>在支持Java 8的Eclipse发行版Eclipse Kepler SR2中,如下图所示,勾选相关选项:
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!" ) );isPresent()返回true,如果Optional实例持有非空的值,否则为false。orElseGet()提供一种回调机制,为了防止Optional持有值为null,接收一个函数提供一个默认的值。map()函数转换现有的Optional的值,返回一个新的Optional实例。orElse()方法和orElseGet()类似,但是它接收一个默认的值,程序输出如下:
Full Name is set? false Full Name: [none] Hey Stranger!我们再简单的看一下另外一个例子:
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();程序输出:
First Name is set? true First Name: Tom Hey Tom!更多细节请查看 官方文档。
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中持有Status类型和Integer类型的引用points,然后我们向集合中添加Task对象:
final Collection< Task > tasks = Arrays.asList( new Task( Status.OPEN, 5 ), new Task( Status.OPEN, 13 ), new Task( Status.CLOSED, 8 ) );第一个问题是我们需要知道所有对象中Task的status属性值为OPEN的points之和是多少?在Java 8之前我们通常的做法是使用foreach对集合进行迭代。但是在Java 8中我们可以使用stream:一个序列的元素支持顺序和并行聚合操作。
// 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这里有几件事情需要做,第一,task集合被转换成它的stream形式,接着调用filter操作过滤掉状态为CLOSED的Task对象,下一步是调用mapToInt操作將Task流转换为Integer流,使用每个实例的Task::getPoints方法。最后使用sum方法,计算出最终结果。
// 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通常,我们需要执行一个集合元素的分组操作,stream也可以帮我们来完成,例如下面的例子:
// 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]]}接下来看一个完整的例子,我们需要计算每一个Task对象的points值占整个集合中所有Task对象points属性之和的百分比:
// Calculate the weight of each tasks (as percent of total points) final Collection< String > result = tasks .stream() // Stream< String > .mapToInt( Task::getPoints ) // IntStream .asLongStream() // LongStream .mapToDouble( points -> points / totalPoints ) // DoubleStream .boxed() // Stream< Double > .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream .mapToObj( percentage -> percentage + "%" ) // Stream< String> .collect( Collectors.toList() ); // List< String > System.out.println( result );控制台输出:
[19%, 50%, 30%]
final Path path = new File( filename ).toPath(); try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) { lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println ); }onClose方法的调用返回一个等价的流和一个额外的关闭处理程序。stream的close()方法调用时,关闭处理程序会被执行。
// 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接下来我们要看的类是LocaleDate和LocalTime。LocalDate只持有ISO-8601日历系统中的日期部分而没有时区部分。而LocalTime只持有IOS-8601日历系统中的时间部分,没有时区部分。LocaleDate和LocaleTime都能够使用Clock创建。
// 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 );该案例控制台输出:
2014-04-12 2014-04-12 11:25:54.568 15:25:54.568LocalDateTime將LocaleDate和LocalTime结合起来,持有ISO-8601日历系统中的时间和日期而没有时区部分。一个 使用案例如下:
// Get the local date/time final LocalDateTime datetime = LocalDateTime.now(); final LocalDateTime datetimeFromClock = LocalDateTime.now( clock ); System.out.println( datetime ); System.out.println( datetimeFromClock );控制台输出:
2014-04-12T11:37:52.309 2014-04-12T15:37:52.309如果你需要一个特定时区的日期/时间,可以使用ZonedDateTime。它持有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" ) ); System.out.println( zonedDatetime ); System.out.println( zonedDatetimeFromClock ); System.out.println( zonedDatetimeFromZone );控制台输出:
2014-04-12T11:47:01.017-04:00[America/New_York] 2014-04-12T15:47:01.017Z 2014-04-12T08:47:01.017-07:00[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() );上面的例子中我们使用Duration类计算出两个日期相差的间隔,控制台输出:
Duration in days: 365 Duration in hours: 8783总得来讲Java 8中新的日期/时间API是非常实用的。更多细节请查看 官方文档。
ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName( "JavaScript" ); System.out.println( engine.getClass().getName() ); System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );控制台输出:
jdk.nashorn.api.scripting.NashornScriptEngine Result: 2在后面的Java tools章节中将会再对犀牛引擎进行介绍。
package com.javacodegeeks.java8.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!Base64也提供了URL和MIME编码解码器(Base64.getUrlEncoder()/Base64.getUrlDecoder(),Base64.getMimeEncoder()/Base64.getMimeDecoder())。
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()方法在数组中填充2000个随机值,然后调用parallelSort()方法,程序输出排序后的前10个元素以查看是否是有序的。
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378 Sorted: 39 220 263 268 325 607 655 678 723 793
function f() { return 1; }; print( f() + 1 );在控制台中运行以下命令:
2更多细节请查看相关 官方文档。
jdeps org.springframework.core-3.0.5.RELEASE.jar控制台输出大量信息,这里我们只截取一小段。依赖关系以包进行分组,如果在类路径中找不到,將显示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