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.Runnable和java.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