给自己总结一个所有JAVA版本更新的核心内容总结:
JAVA 8
JAVA 8在java更新的历史中是一个非常重要的一个版本,引入函数式编程使得java编程更为强大,主要更新的内容:
1.Lambda表达式
Lambda表达式(也叫做闭包)是Java 8中最大的也是期待已久的变化。它允许我们将一个函数当作方法的参数(传递函数),或者说把代码当作数据,这是每个函数式编程者熟悉的概念。很多基于JVM平台的语言一开始就支持Lambda表达式,但是Java程序员没有选择,只能使用匿名内部类来替代Lambda表达式。
Lambda表达式的设计被讨论了很久,而且花费了很多的功夫来交流。不过最后取得了一个折中的办法,得到了一个新的简明并且紧凑的Lambda表达式结构。最简单的Lambda表达式可以用逗号分隔的参数列表、->符号和功能语句块来表示。示例如下:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
请注意到编译器会根据上下文来推测参数的类型,或者你也可以显示地指定参数类型,只需要将类型包在括号里。举个例子:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
如果Lambda的功能语句块太复杂,我们可以用大括号包起来,跟普通的Java方法一样,如下:
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );
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来克服上面提到的脆弱性并且显示地表明函数接口的目的(java里所有现存的接口都已经加上了@FunctionalInterface)。让我们看看一个简单的函数接口定义:
@FunctionalInterface
public interface Functional {
void method();
}
我们要记住默认的方法和静态方法(下一节会具体解释)不会违反函数接口的约定,例子如下:
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
default void defaultMethod() {
}
}
支持Lambda是Java 8最大的卖点,他有巨大的潜力吸引越来越多的开发人员转到这个开发平台来,并且在纯Java里提供最新的函数式编程的概念。
2.新的日期API
Java 8引入了新的日期时间API(JSR 310)改进了日期时间的管理。日期和时间管理一直是Java开发人员最痛苦的问题。java.util.Date和后来的java.util.Calendar一点也没有改变这个情况(甚至让人们更加迷茫)。
因为上面这些原因,产生了Joda-Time,可以替换Java的日期时间API。Joda-Time深刻影响了 Java 8新的日期时间API,Java 8吸收了Joda-Time 的精华。新的java.time包包含了所有关于日期、时间、日期时间、时区、Instant(跟日期类似但精确到纳秒)、duration(持续时间)和时钟操作的类。设计这些API的时候很认真地考虑了这些类的不变性(从java.util.Calendar吸取的痛苦教训)。如果需要修改时间对象,会返回一个新的实例。
让我们看看一些关键的类和用法示例。第一个类是Clock,Clock使用时区来访问当前的instant, date和time。Clock类可以替换System.currentTimeMillis()和TimeZone.getDefault().
1// Get the system clock as UTC offset
2final Clock clock = Clock.systemUTC();
3System.out.println( clock.instant() );
4System.out.println( clock.millis() );
控制台输出如下:
2014-04-12T15:19:29.282Z
1397315969360
其他类我们看看LocalTime和LocalDate。LocalDate只保存有ISO-8601日期系统的日期部分,有时区信息,相应地,LocalTime只保存ISO-8601日期系统的时间部分,没有时区信息。LocalDate和LocalTime都可以从Clock对象创建。
01// Get the local date and local time
02final LocalDate date = LocalDate.now();
03final LocalDate dateFromClock = LocalDate.now( clock );
04
05System.out.println( date );
06System.out.println( dateFromClock );
07
08// Get the local date and local time
09final LocalTime time = LocalTime.now();
10final LocalTime timeFromClock = LocalTime.now( clock );
11
12System.out.println( time );
13System.out.println( timeFromClock );
控制台输出如下:
2014-04-12
2014-04-12
11:25:54.568
15:25:54.568
LocalDateTime类合并了LocalDate和LocalTime,它保存有ISO-8601日期系统的日期和时间,但是没有时区信息。让我们看一个简单的例子。
1// Get the local date/time
2final LocalDateTime datetime = LocalDateTime.now();
3final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );
4
5System.out.println( datetime );
6System.out.println( datetimeFromClock );
输出如下:
2014-04-12T11:37:52.309
2014-04-12T15:37:52.309
如果您需要一个类持有日期时间和时区信息,可以使用ZonedDateTime,它保存有ISO-8601日期系统的日期和时间,而且有时区信息。让我们看一些例子:
1// Get the zoned date/time
2final ZonedDateTime zonedDatetime = ZonedDateTime.now();
3final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
4final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );
5
6System.out.println( zonedDatetime );
7System.out.println( zonedDatetimeFromClock );
8System.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类,Duration持有的时间精确到纳秒。它让我们很容易计算两个日期中间的差异。让我们来看一下:
1// Get duration between two dates
2final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
3final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );
4
5final Duration duration = Duration.between( from, to );
6System.out.println( "Duration in days: " + duration.toDays() );
7System.out.println( "Duration in hours: " + duration.toHours() );
上面的例子计算了两个日期(2014年4月16日和2014年5月16日)之间的持续时间(基于天数和小时)输出如下:
Duration in days: 365
Duration in hours: 8783
对于Java 8的新日期时间的总体印象还是比较积极的。一部分是因为有经历实战的Joda-Time的基础,还有一部分是因为日期时间终于被认真对待而且听取了开发人员的声音。
3.引入Optional
著名的NullPointerException是引起系统失败最常见的原因。很久以前Google Guava项目引入了Optional作为解决空指针异常的一种方式,不赞成代码被null检查的代码污染,期望程序员写整洁的代码。受Google Guava的鼓励,Optional现在是Java 8库的一部分。
Optional只是一个容器,它可以保存一些类型的值或者null。它提供很多有用的方法,所以没有理由不显式地检查null。请参照java 8的文档查看详细信息。
让我们看看两个Optional用法的小例子:一个是允许为空的值,另外一个是不允许为空的值。
1Optional< String > fullName = Optional.ofNullable( null );
2System.out.println( "Full Name is set? " + fullName.isPresent() );
3System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
4System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
如果Optional实例有非空的值,方法 isPresent() 返回true否则返回false。方法orElseGet提供了回退机制,当Optional的值为空时接受一个方法返回默认值。map()方法转化Optional当前的值并且返回一个新的Optional实例。orElse方法和orElseGet类似,但是它不接受一个方法,而是接受一个默认值。上面代码运行结果如下:
Full Name is set? false
Full Name: [none]
Hey Stranger!
让我们大概看看另外一个例子。
1Optional< String > firstName = Optional.of( "Tom" );
2System.out.println( "First Name is set? " + firstName.isPresent() );
3System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );
4System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
5System.out.println();
输出如下:
First Name is set? true
First Name: Tom
Hey Tom!
4.使用Base64
5.接口的默认方法和静态方法
Java 8增加了两个新的概念在接口声明的时候:默认和静态方法。默认方法和Trait有些类似,但是目标不一样。默认方法允许我们在接口里添加新的方法,而不会破坏实现这个接口的已有类的兼容性,也就是说不会强迫实现接口的类实现默认方法。
默认方法和抽象方法的区别是抽象方法必须要被实现,默认方法不是。作为替代方式,接口可以提供一个默认的方法实现,所有这个接口的实现类都会通过继承得倒这个方法(如果有需要也可以重写这个方法),让我们来看看下面的例子:
01private interface Defaulable {
02 // Interfaces now allow default methods, the implementer may or
03 // may not implement (override) them.
04 default String notRequired() {
05 return "Default implementation";
06 }
07}
08
09private static class DefaultableImpl implements Defaulable {
10}
11
12private static class OverridableImpl implements Defaulable {
13 @Override
14 public String notRequired() {
15 return "Overridden implementation";
16 }
17}
接口Defaulable使用default关键字声明了一个默认方法notRequired(),类DefaultableImpl实现了Defaulable接口,没有对默认方法做任何修改。另外一个类OverridableImpl重写类默认实现,提供了自己的实现方法。
Java 8 的另外一个有意思的新特性是接口里可以声明静态方法,并且可以实现。例子如下:
1private interface DefaulableFactory {
2 // Interfaces now allow static methods
3 static Defaulable create( Supplier< Defaulable > supplier ) {
4 return supplier.get();
5 }
6}
下面是把接口的静态方法和默认方法放在一起的示例(::new 是构造方法引用,后面会有详细描述):
1public static void main( String[] args ) {
2 Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
3 System.out.println( defaulable.notRequired() );
4
5 defaulable = DefaulableFactory.create( OverridableImpl::new );
6 System.out.println( defaulable.notRequired() );
7}
控制台的输出如下:
Default implementation
Overridden implementation
JVM平台的接口的默认方法实现是很高效的,并且方法调用的字节码指令支持默认方法。默认方法使已经存在的接口可以修改而不会影响编译的过程。java.util.Collection中添加的额外方法就是最好的例子:stream(),parallelStream(),forEach(),removeIf()
虽然默认方法很强大,但是使用之前一定要仔细考虑是不是真的需要使用默认方法,因为在层级很复杂的情况下很容易引起模糊不清甚至变异错误。
6.新增方法引用格式
方法引用提供了一个很有用的语义来直接访问类或者实例的已经存在的方法或者构造方法。结合Lambda表达式,方法引用使语法结构紧凑简明。不需要复杂的引用。
下面我们用Car 这个类来做示例,Car这个类有不同的方法定义。让我们来看看java 8支持的4种方法引用。
01public static class Car {
02 public static Car create( final Supplier< Car > supplier ) {
03 return supplier.get();
04 }
05
06 public static void collide( final Car car ) {
07 System.out.println( "Collided " + car.toString() );
08 }
09
10 public void follow( final Car another ) {
11 System.out.println( "Following the " + another.toString() );
12 }
13
14 public void repair() {
15 System.out.println( "Repaired " + this.toString() );
16 }
17}
第一种方法引用是构造方法引用,语法是:Class::new,对于泛型来说语法是:Class<T >::new,请注意构造方法没有参数:
1final Car car = Car.create( Car::new );
2final List< Car > cars = Arrays.asList( car );
第二种方法引用是静态方法引用,语法是:Class::static_method请注意这个静态方法只支持一个类型为Car的参数。
1cars.forEach( Car::collide );
第三种方法引用是类实例的方法引用,语法是:Class::method请注意方法没有参数。
1cars.forEach( Car::repair );
最后一种方法引用是引用特殊类的方法,语法是:instance::method,请注意只接受Car类型的一个参数。
1final Car police = Car.create( Car::new );
2cars.forEach( police::follow );
运行这些例子我们将会在控制台得到如下信息(Car的实例可能会不一样):
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
关于方法引用更多的示例和详细信息,请参考官方文档
7.新增Stream类
新增加的Stream API (java.util.stream)引入了在Java里可以工作的函数式编程。这是目前为止对java库最大的一次功能添加,希望程序员通过编写有效、整洁和简明的代码,能够大大提高生产率。
Stream API让集合处理简化了很多(我们后面会看到不仅限于Java集合类)。让我们从一个简单的类Task开始来看看Stream的用法。
01public class Streams {
02private enum Status {
03OPEN, CLOSED
04};
05
06private static final class Task {
07private final Status status;
08private final Integer points;
09
10Task( final Status status, final Integer points ) {
11this.status = status;
12this.points = points;
13}
14
15public Integer getPoints() {
16return points;
17}
18
19public Status getStatus() {
20return status;
21}
22
23@Override
24public String toString() {
25return String.format( "[%s, %d]", status, points );
26}
27}
28}
Task类有一个分数的概念(或者说是伪复杂度),其次是还有一个值可以为OPEN或CLOSED的状态.让我们引入一个Task的小集合作为演示例子:
1final Collection< Task > tasks = Arrays.asList(
2 new Task( Status.OPEN, 5 ),
3 new Task( Status.OPEN, 13 ),
4 new Task( Status.CLOSED, 8 )
5);
第一个问题是所有的开放的Task的点数是多少?在java 8 之前,通常的做法是用foreach迭代。但是Java8里头我们会用Stream。Stream是多个元素的序列,支持串行和并行操作。
1// Calculate total points of all active tasks using sum()
2final long totalPointsOfOpenTasks = tasks
3 .stream()
4 .filter( task -> task.getStatus() == Status.OPEN )
5 .mapToInt( Task::getPoints )
6 .sum();
7
8System.out.println( "Total points: " + totalPointsOfOpenTasks );
控制台的输出将会是:
Total points: 18
上面代码执行的流程是这样的,首先Task集合会被转化为Stream表示,然后filter操作会过滤掉所有关闭的Task,接下来使用Task::getPoints 方法取得每个Task实例的点数,mapToInt方法会把Task Stream转换成Integer Stream,最后使用Sum方法将所有的点数加起来得到最终的结果。
在我们看下一个例子之前,我们要记住一些关于Stream的说明。Stream操作被分为中间操作和终点操作。
中间操作返回一个新的Stream。这些中间操作是延迟的,执行一个中间操作比如filter实际上不会真的做过滤操作,而是创建一个新的Stream,当这个新的Stream被遍历的时候,它里头会包含有原来Stream里符合过滤条件的元素。
终点操作比如说forEach或者sum会遍历Stream从而产生最终结果或附带结果。终点操作执行完之后,Stream管道就被消费完了,不再可用。在几乎所有的情况下,终点操作都是即时完成对数据的遍历操作。
Stream的另外一个价值是Stream创造性地支持并行处理。让我们看看下面这个例子,这个例子把所有task的点数加起来。
1// Calculate total points of all tasks
2final double totalPoints = tasks
3 .stream()
4 .parallel()
5 .map( task -> task.getPoints() ) // or map( Task::getPoints )
6 .reduce( 0, Integer::sum );
7
8System.out.println( "Total points (all tasks): " + totalPoints );
这个例子跟上面那个非常像,除了这个例子里使用了parallel()方法 并且计算最终结果的时候使用了reduce方法。
输出如下:
Total points (all tasks): 26.0
经常会有这个一个需求:我们需要按照某种准则来对集合中的元素进行分组。Stream也可以处理这样的需求,下面是一个例子:
1// Group tasks by their status
2final Map< Status, List< Task > > map = tasks
3 .stream()
4 .collect( Collectors.groupingBy( Task::getStatus ) );
5System.out.println( map );
控制台的输出如下:
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
让我们来计算整个集合中每个task分数(或权重)的平均值来结束task的例子。
01// Calculate the weight of each tasks (as percent of total points)
02final Collection< String > result = tasks
03 .stream() // Stream< String >
04 .mapToInt( Task::getPoints ) // IntStream
05 .asLongStream() // LongStream
06 .mapToDouble( points -> points / totalPoints ) // DoubleStream
07 .boxed() // Stream< Double >
08 .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
09 .mapToObj( percentage -> percentage + "%" ) // Stream< String>
10 .collect( Collectors.toList() ); // List< String >
11
12System.out.println( result );
控制台输出如下:
[19%, 50%, 30%]
最后,就像前面提到的,Stream API不仅仅处理Java集合框架。像从文本文件中逐行读取数据这样典型的I/O操作也很适合用Stream API来处理。下面用一个例子来应证这一点。
1final Path path = new File( filename ).toPath();
2try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
3 lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
4}
Stream的方法onClose返回一个等价的有额外句柄的Stream,当Stream的close()方法被调用的时候这个句柄会被执行。
Stream API、Lambda表达式还有接口默认方法和静态方法支持的方法引用,是Java 8对软件开发的现代范式的响应。
8.注解相关的改变
9.支持并行(parallel)数组
Java 8新增加了很多方法支持并行的数组处理。最重要的大概是parallelSort()这个方法显著地使排序在多核计算机上速度加快。下面的小例子演示了这个新的方法(parallelXXX)的行为。
01</pre>
02
03
04import java.util.Arrays;
05import java.util.concurrent.ThreadLocalRandom;
06
07public class ParallelArrays {
08 public static void main( String[] args ) {
09 long[] arrayOfLong = new long [ 20000 ];
10
11 Arrays.parallelSetAll( arrayOfLong,
12 index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
13 Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
14 i -> System.out.print( i + " " ) );
15 System.out.println();
16
17 Arrays.parallelSort( arrayOfLong );
18 Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
19 i -> System.out.print( i + " " ) );
20 System.out.println();
21 }
22}</pre>
23<pre>
这一小段代码使用parallelSetAll()t方法填充这个长度是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
10.对并发类(Concurrency)的扩展。
在新增Stream机制与lambda的基础之上,在java.util.concurrent.ConcurrentHashMap中加入了一些新方法来支持聚集操作。同时也在java.util.concurrent.ForkJoinPool类中加入了一些新方法来支持共有资源池(common pool)
新增的java.util.concurrent.locks.StampedLock类提供一直基于容量的锁,这种锁有三个模型来控制读写操作(它被认为是不太有名的java.util.concurrent.locks.ReadWriteLock类的替代者)。
在java.util.concurrent.atomic包中还增加了下面这些类:
DoubleAccumulator
DoubleAdder
LongAccumulator
LongAdder
JAVA 9
Java 9 发布于 2017 年 9 月 22 日,带来了很多新特性,其中最主要的变化是已经实现的模块化系统。
1 模块系统:模块是一个包的容器,Java 9 最大的变化之一是引入了模块系统(Jigsaw 项目)。
2 REPL (JShell):交互式编程环境。
JAVA 10
1. 局部变量类型推断
局部变量类型推断可以说是Java 10中最值得注意的特性,这是Java语言开发人员为了简化Java应用程序的编写而采取的又一步,如下图所示。
局部变量类型推断将引入"var"关键字,也就是你可以随意定义变量而不必指定变量的类型
局部变量类型推荐仅限于如下使用场景:
局部变量初始化
for循环内部索引变量
传统的for循环声明变量
Java官方表示,它不能用于以下几个地方:
方法参数
构造函数
参数方法返回
类型字段捕获表达式(或任何其他类型的变量声明)
2 JEP304,统一的垃圾回收接口。
3 JEP307,G1 垃圾回收器的并行完整垃圾回收,实现并行性来改善最坏情况下的延迟。
JAVA 11
1 本地变量类型推断
2、字符串加强
Java 11 增加了一系列的字符串处理方法,如以下所示。
// 判断字符串是否为空白" ".isBlank(); // true// 去除首尾空格" Javastack ".strip(); // "Javastack"// 去除尾部空格 " Javastack ".stripTrailing(); // " Javastack"// 去除首部空格 " Javastack ".stripLeading(); // "Javastack "// 复制字符串"Java".repeat(3); // "JavaJavaJava"// 行数统计"A\nB\nC".lines().count(); // 3
3、集合加强
自 Java 9 开始,Jdk 里面为集合(List/ Set/ Map)都添加了of和copyOf方法,它们两个都用来创建不可变的集合,来看下它们的使用和区别。
示例1:
var list = List.of("Java", "Python", "C");var copy = List.copyOf(list);System.out.println(list == copy); // true
示例2:
var list = new ArrayList<String>();var copy = List.copyOf(list);System.out.println(list == copy); // false
示例1和2代码差不多,为什么一个为true,一个为false?
来看下它们的源码:
static <E> List<E> of(E... elements) {switch (elements.length) { // implicit null check of elements case 0: return ImmutableCollections.emptyList(); case 1: return new ImmutableCollections.List12<>(elements[0]); case 2: return new ImmutableCollections.List12<>(elements[0], elements[1]); default: return new ImmutableCollections.ListN<>(elements); }}static <E> List<E> copyOf(Collection<? extends E> coll) { return ImmutableCollections.listCopy(coll);}static <E> List<E> listCopy(Collection<? extends E> coll) { if (coll instanceof AbstractImmutableList && coll.getClass() != SubList.class) { return (List)coll; } else { return (List)List.of(coll.toArray()); }}
可以看出copyOf方法会先判断来源集合是不是AbstractImmutableList类型的,如果是,就直接返回,如果不是,则调用of创建一个新的集合。
示例2因为用的 new 创建的集合,不属于不可变AbstractImmutableList类的子类,所以copyOf方法又创建了一个新的实例,所以为false.
注意:使用 of 和 copyOf 创建的集合为不可变集合,不能进行添加、删除、替换、排序等操作,不然会报
java.lang.UnsupportedOperationException异常。
上面演示了 List 的 of 和 copyOf 方法,Set 和 Map 接口都有。
4、Stream 加强
Stream 是 Java 8 中的新特性,Java 9 开始对 Stream 增加了以下 4 个新方法。
增加单个参数构造方法,可为null
Stream.ofNullable(null).count(); // 0
增加 takeWhile 和 dropWhile 方法
Stream.of(1, 2, 3, 2, 1).takeWhile(n -> n < 3) .collect(Collectors.toList()); // [1, 2]
从开始计算,当 n < 3 时就截止。
Stream.of(1, 2, 3, 2, 1).dropWhile(n -> n < 3) .collect(Collectors.toList()); // [3, 2, 1]
这个和上面的相反,一旦 n < 3 不成立就开始计算。
3)iterate重载
这个 iterate 方法的新重载方法,可以让你提供一个 Predicate (判断条件)来指定什么时候结束迭代。
如果你对 JDK 8 中的 Stream 还不熟悉,可以看之前分享的这一系列教程。
5、Optional 加强
Opthonal 也增加了几个非常酷的方法,现在可以很方便的将一个 Optional 转换成一个 Stream, 或者当一个空 Optional 时给它一个替代的。
Optional.of("javastack").orElseThrow(); // javastackOptional.of("javastack").stream().count(); // 1Optional.ofNullable(null).or(() -> Optional.of("javastack")) .get(); // javastack
6、InputStream 加强
InputStream 终于有了一个非常有用的方法:transferTo,可以用来将数据直接传输到 OutputStream,这是在处理原始数据流时非常常见的一种用法,如下示例。
var classLoader = ClassLoader.getSystemClassLoader();var inputStream = classLoader.getResourceAsStream("javastack.txt");var javastack = File.createTempFile("javastack2", "txt");try (var outputStream = new FileOutputStream(javastack)) {inputStream.transferTo(outputStream);}
7、HTTP Client API
这是 Java 9 开始引入的一个处理 HTTP 请求的的孵化 HTTP Client API,该 API 支持同步和异步,而在 Java 11 中已经为正式可用状态,你可以在
java.net
包中找到这个 API。
来看一下 HTTP Client 的用法:
var request = HttpRequest.newBuilder().uri(URI.create("https://javastack.cn")) .GET() .build();var client = HttpClient.newHttpClient();// 同步HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());System.out.println(response.body());// 异步client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenAccept(System.out::println);
上面的.GET()可以省略,默认请求方式为 Get!
更多使用示例可以看这个 API,后续有机会再做演示。
现在 Java 自带了这个 HTTP Client API,我们以后还有必要用 Apache 的 HttpClient 工具包吗?
8、化繁为简,一个命令编译运行源代码
看下面的代码。
// 编译javac Javastack.java// 运行java Javastack
在我们的认知里面,要运行一个 Java 源代码必须先编译,再运行,两步执行动作。而在未来的 Java 11 版本中,通过一个java命令就直接搞定了,如以下所示。
javaJavastack.java
JAVA 12
1、Shenandoah:低暂停时间的 GC(实验性功能)
新增了一个名为 Shenandoah 的 GC 算法,通过与正在运行的 Java 线程同时进行 evacuation 工作来减少 GC 暂停时间。使用 Shenandoah 的暂停时间与堆大小无关,这意味着无论堆是 200 MB 还是 200 GB,都将具有相同的暂停时间。
2、微基准测试套件
JDK 源码中新增了一套微基准测试套件,使开发人员可以轻松运行现有的微基准测试并创建新的基准测试。
3、Switch 表达式(预览功能)
扩展了 switch 语句,使其不仅可以作为语句(statement),还可以作为表达式(expression),并且两种写法都可以使用传统的 switch 语法,或者使用简化的“case L ->”模式匹配语法作用于不同范围并控制执行流。这些更改将简化日常编码工作,并为 switch 中的模式匹配(JEP 305)做好准备。
4、JVM 常量 API
引入 API 对关键类文件和运行时工件建模,特别是可从常量池加载的常量。在新的 java.lang.invoke.constant 包中定义了一系列基于值的符号引用(JVMS 5.1)类型,它们能够描述每种可加载常量。
符号引用以纯 nominal 形式描述可加载常量,与类加载或可访问性上下文区分开。有些类可以作为自己的符号引用(例如 String),而对于可链接常量,定义了一系列符号引用类型(ClassDesc、MethodTypeDesc、MethodHandleDesc 和 DynamicConstantDesc),它们包含描述这些常量的 nominal 信息。
5、只保留一个 AArch64 实现
删除了与 arm64 相关的所有源,同时保留 32 位 ARM 实现和 64 位 aarch64。
JDK 中存在两套 64 位 ARM 实现,主要存在于 src/hotspot/cpu/arm 和 open/src/hotspot/cpu/aarch64 目录。两者都实现了 aarch64,现在将只保留后者,删除由 Oracle 提供的 arm64。这将使贡献者将他们的精力集中在单个 64 位 ARM 实现上,并消除维护两套实现所需的重复工作。
6、默认类数据共享归档文件
针对 64 位平台,使用默认类列表增强 JDK 构建过程以生成类数据共享(class data-sharing,CDS)档。
7、可中止的 G1 Mixed GC
如果 G1 Mixed GC 存在超出暂停目标的可能性,则使其可中止。
8、G1 及时返回未使用的已分配内存
增强 G1 GC,在空闲时自动将 Java 堆内存返回给操作系统。为了实现向操作系统返回最大内存量的目标,G1 将在应用程序不活动期间定期执行或触发并发周期以确定整体 Java 堆使用情况。这将导致它自动将 Java 堆的未使用部分返回给操作系统。而在用户控制下,可以可选地执行完整的 GC,以使返回的内存量最大化。
JAVA 13
1、Dynamic CDS Archives
这一特性是在JEP310:Application Class-Data Sharing 基础上扩展而来的,Dynamic CDS Archives中的CDS指的就是Class-Data Sharing。
那么,这个JEP310是个啥东西呢?
我们知道在同一个物理机/虚拟机上启动多个JVM时,如果每个虚拟机都单独装载自己需要的所有类,启动成本和内存占用是比较高的。所以Java团队引入了CDS的概念,通过把一些核心类在每个JVM间共享,每个JVM只需要装载自己的应用类,启动时间减少了,另外核心类是共享的,所以JVM的内存占用也减少了。
CDS 只能作用于 Boot Class Loader 加载的类,不能作用于 App Class Loader 或者自定义的 Class Loader 加载的类。
在 Java 10 中,则将 CDS 扩展为 AppCDS,顾名思义,AppCDS 不止能够作用于 Boot Class Loader了,App Class Loader 和自定义的 Class Loader 也都能够起作用,大大加大了 CDS 的适用范围。也就说开发自定义的类也可以装载给多个JVM共享了。
Java 10中包含的JEP310的通过跨不同Java进程共享公共类元数据来减少了内存占用和改进了启动时间。
但是,JEP310中,使用AppCDS的过程还是比较复杂的,需要有三个步骤:
1、决定要 Dump 哪些 Class
2、将类的内存 Dump 到归档文件中
3、使用 Dump 出来的归档文件加快应用启动速度
这一次的JDK 13中的JEP 350 ,在JEP310的基础上,又做了一些扩展。允许在Java应用程序执行结束时动态归档类,归档类将包括默认的基础层 CDS(class data-sharing)存档中不存在的所有已加载的应用程序类和库类。
也就是说,在Java 13中再使用AppCDS的时候,就不在需要这么复杂了。
在讨论这个问题之前,想先问一个问题,JVM的GC释放的内存会还给操作系统吗?
GC后的内存如何处置,其实是取决于不同的垃圾回收器的。因为把内存还给OS,意味着要调整JVM的堆大小,这个过程是比较耗费资源的。
在JDK 11中,Java引入了ZGC,这是一款可伸缩的低延迟垃圾收集器,但是当时只是实验性的。并且,ZGC释放的内存是不会还给操作系统的。
而在Java 13中,JEP 351再次对ZGC做了增强,本次 ZGC 可以将未使用的堆内存返回给操作系统。之所以引入这个特性,是因为如今有很多场景中内存是比较昂贵的资源,在以下情况中,将内存还给操作系统还是很有必要的:
1、那些需要根据使用量付费的容器
2、应用程序可能长时间处于空闲状态并与许多其他应用程序共享或竞争资源的环境。
3、应用程序在执行期间可能有非常不同的堆空间需求。例如,启动期间所需的堆可能大于稍后在稳定状态执行期间所需的堆。
3、Reimplement the Legacy Socket API
使用易于维护和调试的更简单、更现代的实现替换 java.net.Socket 和 java.net.ServerSocket API。
java.net.Socket和java.net.ServerSocket的实现非常古老,这个JEP为它们引入了一个现代的实现。现代实现是Java 13中的默认实现,但是旧的实现还没有删除,可以通过设置系统属性jdk.net.usePlainSocketImpl来使用它们。
运行一个实例化Socket和ServerSocket的类将显示这个调试输出。这是默认的(新的):
4、Switch Expressions (Preview)
在JDK 12中引入了Switch表达式作为预览特性。JEP 354修改了这个特性,它引入了yield语句,用于返回值。这意味着,switch表达式(返回值)应该使用yield, switch语句(不返回值)应该使用break。
在以前,我们想要在switch中返回内容,还是比较麻烦的,一般语法如下:
inti;
switch(x) {
case"1":
i=1;
break;
case"2":
i=2;
break;
default:
i = x.length();
break;
}
在JDK13中使用以下语法:
int i =switch(x) {
case"1"-> 1;
case"2"-> 2;
default-> {
int len = args[1].length();
yieldlen;
}
};
或者
inti =switch(x) {
case"1":yield1;
case"2":yield2;
default: {
intlen = args[1].length();
yieldlen;
}
};
在这之后,switch中就多了一个关键字用于跳出switch块了,那就是yield,他用于返回一个值。和return的区别在于:return会直接跳出当前循环或者方法,而yield只会跳出当前switch块。
在JDK 12中引入了Raw String Literals特性,但在发布之前就放弃了。这个JEP在引入多行字符串文字(text block)在意义上是类似的。
text block,文本块,是一个多行字符串文字,它避免了对大多数转义序列的需要,以可预测的方式自动格式化字符串,并在需要时让开发人员控制格式。
我们以前从外部copy一段文本串到Java中,会被自动转义,如有一段以下字符串:
Hello, Hollis
将其复制到Java的字符串中,会展示成以下内容:
"
" +
"
" +
"
Hello, Hollis
" +
"
" +
"
";
即被自动进行了转义,这样的字符串看起来不是很直观,在JDK 13中,就可以使用以下语法了:
"""
Hello, Hollis
"""
;
使用"""作为文本块的开始符合结束符,在其中就可以放置多行的字符串,不需要进行任何转义。看起来就十分清爽了。