您的位置 首页 java

JAVA8–JAVA成长之路

毫无疑问, java 8发行版是自Java 5(发行于2004,已经过了相当一段时间了)以来最具革命性的版本。 Java 8 为Java语言、编译器、类库、开发工具与JVM(Java虚拟机)带来了大量新特性。在这篇教程中,我们将一一探索这些变化,并用真实的例子说明它们适用的场景。

这篇教程由以下几部分组成,它们分别涉及到Java平台某一特定方面的内容:

  • Java语言

  • 编译器

  • 类库

  • 工具

  • Java运行时(JVM)

2.Java语言的新特性

不管怎么说,Java 8都是一个变化巨大的版本。你可能认为Java 8耗费了大量的时间才得以完成是为了实现了每个Java程序员所期待的特性。在这个小节里,我们将会涉及到这些特性的大部分。

2.1 Lambda表达式与Functional接口

Lambda表达式(也称为闭包)是整个Java 8发行版中最受期待的在Java语言层面上的改变,Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中),或者把代码看成数据:函数式程序员对这一概念非常熟悉。在JVM平台上的很多语言(Groovy,Scala,……)从一开始就有Lambda,但是Java程序员不得不使用毫无新意的匿名类来代替lambda。

关于Lambda设计的讨论占用了大量的时间与社区的努力。可喜的是,最终找到了一个平衡点,使得可以使用一种即简洁又紧凑的新方式来构造Lambdas。在最简单的形式中,一个lambda可以由用逗号分隔的参数列表、–>符号与函数体三部分表示。例如:

请注意参数e的类型是由编译器推测出来的。同时,你也可以通过把参数类型与参数包括在括号中的形式直接给出参数的类型:

在某些情况下lambda的函数体会更加复杂,这时可以把函数体放到在一对花括号中,就像在Java中定义普通函数一样。例如:

“a” “b” “d” System.out.print( e );

和:

Lambda可能会返回一个值。返回值的类型也是由编译器推测出来的。如果lambda的函数体只有一行的话,那么没有必要显式使用return语句。下面两个代码片段是等价的:

和:

“a” “b” “d” int return

public Functional {

method();

}

需要记住的一件事是:默认方法与静态方法并不影响函数式接口的契约,可以任意使用:

Lambda是Java 8最大的卖点。它具有吸引越来越多程序员到Java平台上的潜力,并且能够在纯Java语言环境中提供一种优雅的方式来支持函数式编程。更多详情可以参考官方文档。

2.2 接口的默认方法与静态方法

Java 8用默认方法与静态方法这两个新概念来扩展接口的声明。默认方法使接口有点像Traits(Scala中特征(trait)类似于Java中的Interface,但它可以包含实现代码,也就是目前Java8新增的功能),但与传统的接口又有些不一样,它允许在已有的接口中添加新方法,而同时又保持了与旧版本代码的兼容性。

默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是默认方法则没有这个要求。相反,每个接口都必须提供一个所谓的默认实现,这样所有的接口实现者将会默认继承它(如果有必要的话,可以覆盖这个默认实现)。让我们看看下面的例子:

下面的一小段代码片段把上面的默认方法与静态方法黏合到一起。

在JVM中,默认方法的实现是非常高效的,并且通过字节码指令为方法调用提供了支持。默认方法允许继续使用现有的Java接口,而同时能够保障正常的编译过程。这方面好的例子是大量的方法被添加到java.util.Collection接口中去:stream(),parallelStream(),forEach(),removeIf(),……

尽管默认方法非常强大,但是在使用默认方法时我们需要小心注意一个地方:在声明一个默认方法前,请仔细思考是不是真的有必要使用默认方法,因为默认方法会带给程序歧义,并且在复杂的继承体系中容易产生编译错误。更多详情请参考官方文档

2.3 方法引用

方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

下面,我们以定义了4个方法的Car这个类作为例子,区分Java中支持的4种不同的方法引用。

第二种方法引用是静态方法引用,它的语法是Class::static_method。请注意这个方法接受一个Car类型的参数。

第三种方法引用是特定类的任意对象的方法引用,它的语法是Class::method。请注意,这个方法没有参数。

最后,第四种方法引用是特定对象的方法引用,它的语法是instance::method。请注意,这个方法接受一个Car类型的参数

Car police = Car.create( Car:: );

cars.forEach( police::follow );

运行上面的Java程序在控制台上会有下面的输出(Car的实例可能不一样):

正如我们看到的,这里有个使用@Repeatable( Filter s.class )注解的注解类Filter,Filters仅仅是Filter注解的数组,但Java编译器并不想让程序员意识到Filters的存在。这样,接口Filterable就拥有了两次Filter(并没有提到Filter)注解。

同时,反射相关的 API 提供了新的函数getAnnotationsByType()来返回重复注解的类型(请注意Filterable.class.getAnnotation( Filters.class )经编译器处理后将会返回Filters的实例)。

程序输出结果如下:

null}

T getOrDefault( T value, T defaultValue ) {

( value != ) ? value : defaultValue;

public TypeInference {

static main(String[] args) {

Value< String > value = Value<>();

“22” }

}

Value.defaultValue()的参数类型可以被推测出,所以就不必明确给出。在Java 7中,相同的例子将不会通过编译,正确的书写方式是 Value.< String >defaultValue()。

2.6 扩展注解的支持

Java 8扩展了注解的上下文。现在几乎可以为任何东西添加注解:局部变量、泛型类、父类与接口的实现,就连方法的异常也能添加注解。下面演示几个例子:

com.javacodegeeks.java8.annotations;

java.lang.annotation.ElementType;

import java.lang.annotation.RetentionPolicy;

import java.util.ArrayList;

import

public Annotations {

( RetentionPolicy.RUNTIME )

( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )

@interface }

static Holder< T > @NonEmpty public method() @NonEmpty }

@SuppressWarnings”unused” public void final new Holder< String >();

Collection< String > strings = ArrayList<>();

public ParameterNames {

static main(String[] args) Exception {

class “main” class forfinal System.out.println( + parameter.getName() );

}

}

如果不使用– parameters 参数来编译这个类,然后运行这个类,会得到下面的输出:

如果使用–parameters参数来编译这个类,程序的结构会有所不同(参数的真实名字将会显示出来):

对于有经验的Maven用户,通过maven-compiler-plugin的配置可以将-parameters参数添加到编译器中去。

plugin <>org.apache.maven.plugins</>

artifactId artifactId <>3.1</>

configuration <>-parameters</>

source source <>1.8</>

configuration plugin

);

System.out.println( + fullName.isPresent() );

System.out.println( + fullName.orElseGet( () -> ) );

System.out.println( fullName.map( s -> + s + ).orElse( ) );

如果Optional类的实例为非空值的话,isPresent()返回true,否从返回false。为了防止Optional为空值,orElseGet()方法通过回调函数来产生一个默认值。map()函数对当前Optional的值进行转化,然后返回一个新的Optional实例。orElse()方法和orElseGet()方法类似,但是orElse接受一个默认值而不是一个回调函数。下面是这个程序的输出:

更多详情请参考官方文档

4.2 Stream

最新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

Stream API极大简化了集合框架的处理(但它的处理的范围不仅仅限于集合框架的处理,这点后面我们会看到)。让我们以一个简单的Task类为例进行介绍:

class private Status {

};

static class private Status status;

final

final final thisthis}

Integer getPoints() {

points;

public return }

public return “[%s, %d]”}

Task( Status.OPEN, ),

Task( Status.OPEN, ),

Task( Status.CLOSED, )

);

我们下面要讨论的第一个问题是所有状态为OPEN的任务一共有多少分数?在Java 8以前,一般的解决方式用foreach循环,但是在Java 8里面我们可以使用stream:一串支持连续、并行聚集操作的元素。

long .stream()

.mapToInt( Task::getPoints )

“Total points: ”

totalPoints = tasks

.parallel()

// or map( Task::getPoints )

0 “Total points (all tasks): ”

这个例子的控制台输出如下:

让我们来计算整个集合中每个task分数(或权重)的平均值来结束task的例子。

Collection< String > result = tasks

// Stream< String >

// IntStream

// LongStream

// DoubleStream

// Stream< Double >

long 100 // LongStream

“%” // Stream< String>

// List< String >

System.out.println( result );

下面是这个例子的控制台输出:

最后,就像前面提到的,Stream API不仅仅处理Java集合框架。像从文本文件中逐行读取数据这样典型的I/O操作也很适合用Stream API来处理。下面用一个例子来应证这一点。

Path path = File( filename ).toPath();

try lines.onClose( () -> System.out.println() ).forEach( System.out::println );

}

对一个stream对象调用onClose方法会返回一个在原有功能基础上新增了关闭功能的stream对象,当对stream对象调用close()方法时,与关闭相关的处理器就会执行。

Stream API、Lambda表达式与方法引用在接口默认方法与静态方法的配合下是Java 8对现代软件开发范式的回应。更多详情请参考官方文档。

4.3 Date/Time API (JSR 310)

Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。对日期与时间的操作一直是Java程序员最痛苦的地方之一。标准的 java.util.Date以及后来的java.util.Calendar一点没有改善这种情况(可以这么说,它们一定程度上更加复杂)。

这种情况直接导致了Joda-Time——一个可替换标准日期/时间处理且功能非常强大的Java API的诞生。Java 8新的Date-Time API (JSR 310)在很大程度上受到Joda-Time的影响,并且吸取了其精髓。新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。在设计新版API时,十分注重与旧版API的兼容性:不允许有任何的改变(从java.util.Calendar中得到的深刻教训)。如果需要修改,会返回这个类的一个新实例。

让我们用例子来看一下新版API主要类的使用方法。第一个是Clock类,它通过指定一个时区,然后就可以获取到当前的时刻,日期与时间。Clock可以替换System.currentTimeMillis()与TimeZone.getDefault()。

Clock clock = Clock.systemUTC();

System.out.println( clock.instant() );

System.out.println( clock.millis() );

下面是程序在控制台上的输出:

LocalTime time = LocalTime.now();

final

2014-04-12

11:25:54.568

15:25:54.568

LocaleDateTime把LocaleDate与LocaleTime的功能合并起来,它持有的是ISO-8601格式无时区信息的日期与时间。下面是一个快速入门的例子。

LocalDateTime datetime = LocalDateTime.now();

final

2014-04-12T15:37:52.309

如果你需要特定时区的日期/时间,那么ZonedDateTime是你的选择。它持有ISO-8601格式具具有时区信息的日期与时间。下面是一些不同时区的例子:

Get the zoned /time

final ZonedDateTime zonedDatetime = ZonedDateTime.now();

final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );

final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( ) );

System.out.println( zonedDatetime );

System.out.println( zonedDatetimeFromClock );

System.out.println( zonedDatetimeFromZone );

下面是程序在控制台上的输出:

最后,让我们看一下Duration类:在秒与纳秒级别上的一段时间。Duration使计算两个日期间的不同变的十分简单。下面让我们看一个这方面的例子。

上面的例子计算了两个日期2014年4月16号与2014年4月16号之间的过程。下面是程序在控制台上的输出:

in in

我们在后面的Java新工具章节会再次谈到Nashorn。

4.5 Base64

在Java 8中,Base64编码已经成为Java类库的标准。它的使用十分简单,下面让我们看一个例子:

com.javacodegeeks.java8.base64;

java.nio.charset.StandardCharsets;

import

public Base64s {

static main(String[] args) {

String text = ;

String encoded = Base64

.encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );

final new Base64.getDecoder().decode( encoded ),

System.out.println( decoded );

Base64 finally Java 8!

Base64类同时还提供了对URL、MIME友好的编码器与解码器(Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder())。

4.6 并行(parallel)数组

Java 8增加了大量的新方法来对数组进行并行处理。可以说,最重要的是parallelSort()方法,因为它可以在多核机器上极大提高数组排序的速度。下面的例子展示了新方法(parallelXxx)的使用。

上面的代码片段使用了parallelSetAll()方法来对一个有20000个元素的数组进行随机赋值。然后,调用parallelSort方法。这个程序首先打印出前10个元素的值,之后对整个数组排序。这个程序在控制台上的输出如下(请注意数组元素是随机生产的):1

1;

};

-> org.apache.commons.logging not found

-> org.springframework.asm.commons not found

-> java.lang

-> java.lang.reflect

<code plain”=”” style=”border: 0px !important; font-family: Consolas, “Bitstream Vera Sans Mono”, “Courier New”, Courier, monospace !important; border-radius: 0px !important; bottom: auto !important; float: none !important; height: auto !important; left: auto !important; line-height: 1.1em !important; outline: 0px !important; overflow: visible !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important; box-sizing: content-box !important; min-height: auto !important; background: none !important;”>-> java.util

更多详情请参考官方文档

6. Java虚拟机(JVM)的新特性

PermGen空间被移除了,取而代之的是Metaspace(JEP 122)。JVM选项 -XX:PermSize -XX:MaxPermSize 分别被 -XX:MetaSpaceSize -XX:MaxMetaspaceSize 所代替。

JAVA8--JAVA成长之路

文章来源:智云一二三科技

文章标题:JAVA8–JAVA成长之路

文章地址:https://www.zhihuclub.com/194069.shtml

关于作者: 智云科技

热门文章

网站地图