java8 新特性

2016-05-23 21:09:01   最后更新: 2016-05-24 10:20:29   访问数量:436




众所周知,java 有两个最具革命性的版本,一个是 java1.5 一个是 java1.8,上一篇日志中,我们介绍了 java5 也就是 java1.5 版本更新的十余个最具重要性的更新:

java5 新特性

本篇日志中,我们就来介绍一下 java1.8 的新特性

 

基本介绍

Lambda 表达式的加入可以说是 java8 最大的变化,也是最受期待的 java 语法上的改变

随着近年来很多函数式语言的持续升温,尤其是 Scala 这种多范式语言的兴起,Scala 中常用的 Lambda 表达式也被推崇有加

Lambda 表达式是一个匿名函数,直接应用于代码中进行推算,可以用来表示一个闭包

所谓的“闭包”,指的就是可以包含自由变量的代码块,代码块中包含的自由变量并没有在定义是绑定任何对象,他们也不是在这个代码块内或任何全局上下文中定义的,而是在代码块环境中定义的局部变量,这个特性在很多种语言上都有着不同程度的支持

这里简单叙述的闭包概念看起来和传统意义上的函数非常类似,事实上这是完全不对的,所谓的闭包指的是一个代码块,十分类似于传统意义上的函数,但是这个局部的代码块必须是可以嵌套定义的,以及可以访问到他上级函数的局部变量

比如下面这个 C 语言中会报错的例子:

int main() { int a[6] = {1, 4, 2, 3, 8, 6}; return compare() { return a[0] - a[5]; } }

 

首先,C、C++ 中不支持嵌套定义函数,其次,函数中不能访问函数外的变量,因此上面这段闭包的代码是无法编译通过的

 

java 中 Lambda 表达式的语法

java 中 Lambda 表达式的形式十分简单,他由都好分割的参数列表、-> 符号与函数体三部分表示:

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

 

这里使用了 return 语句,但作为闭包的匿名函数却没有声明返回类型,编译器会自动根据 return 的参数设定这个匿名函数的返回类型

 

实际上,在循环遍历 ArrayList 的情况下,Lambda 表达式更加常用:

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

 

与返回类型一样,你也可以省去参数列表中的类型声明而让编译器自动判断,甚至如果你的函数体只有一行,你也可以省去大括号:

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

 

 

之前的例子也可以简化成

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

 

 

正如上面所说,闭包中可以引用闭包外的局部变量

 

Lambda 表达式的另一个支持是函数接口

“函数接口”这个概念是 java8 新引入的概念,他指的是只有一个显式声明的方法的接口,你可以使用 @FunctionalInterface 将只有一个显式方法的接口声明为一个函数接口,一旦添加这个注解,则意味着你的接口不能再放入另一个方法,当然,由于接口可以继承自其它接口,因此,他继承了父接口中的所有方法,这也就意味着,子接口与父接口中的方法数之和只能刚好是 1,然而,由于所有的接口都继承自 Object 类,所以接口中你也可以显式声明额外的 equals、hashCode、toString 方法

你可以用一个 Lambda 表达式为一个函数接口赋值:

@FunctionalInterface interface SaySomething { void say(); } public class ForTest { SaySomething saySomething = () -> { System.out.println("Hello World!"); }; saySomething.say(); }

 

上面已经介绍了 Lambda 表达式的基本语法,这里就不过多介绍各种参数及返回值的情况了

 

jdk1.8 中定义了三个常用的函数接口,十分实用,分别是有返回值、无返回值及返回 boolean 类型的函数接口:

@FunctionalInterface public interface Function<T, R> { R apply(T t); }

 

 

@FunctionalInterface public interface Consumer<T> { void accept(T t); }

 

 

@FunctionalInterface public interface Predicate<T> { boolean test(T t); }

 

 

现在,你可以十分方便的声明一个线程:

Thread gaoDuanDaQiShangDangCi = new Thread(() -> { System.out.println("This is from an anonymous method (lambda exp)."); });

 

 

java8 对于类中的某个静态方法(包括构造器)或静态成员的方法,都可以使用方法引用的新语法来简化调用

下面我们通过一个 Lambda 表达式和一个方法引用的例子做对比来展示方法引用的用法:

 

实例方法引用

上面例子中的 Lambda 表达式可以简化成:

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

 

由于 out 是 System 的静态成员变量,因此,可以直接通过 :: 符号引用他的成员方法

 

参数方法引用

也可以直接引用参数的成员方法:

persons.forEach(person -> person.eat()); persons.forEach(Person::eat);

 

 

静态方法引用

对于类静态方法,也可以直接使用 :: 符号来进行引用,与实例方法引用非常类似:

Comparator<Integer> c2 = (x, y) -> Integer.compare(x, y); Comparator<Integer> c1 = Integer::compare;

 

上面例子中的两个语句是完全一样的

 

构造器引用:

Lambda 表达式中的参数可以直接作为构造器参数进行构造器引用

strList.stream().map(s -> new Integer(s)); strList.stream().map(Integer::new);

 

 

概述

java8 中,接口可以声明默认的方法实现了,这被称为“默认方法”,这样的方法必须在前面加上 default 关键字

public interface MyInterf { String m1(); default String m2() { return "Hello default method!"; } }

 

这让接口看起来更像是抽象类了,这与 java 诞生之初的设计初衷产生了矛盾,java 之所以不允许多重继承,以及接口诞生的一个原因正是因为 C++ 中多重继承引起的各种复杂的问题

这一次,java8 对 Collection、Comparator 等老接口添加了很多新方法,又不能强制修改所有这些老接口的实现,只能做出这样的妥协

 

解决冲突

随之而来,java 的某个类在实现多个接口的时候,又出现了新的问题,如果两个接口中都定义了完全一样的方法,同时有都为他们各自的这个方法添加了默认实现,那么实现类中的又不对相应方法采取自定义的实现,那么显然,这就会发生冲突,如果实现类中需要使用某个父接口的默认实现,就要使用下面的语法了:

interface Base1 { default public void hello() { System.out.println("Hello Base1"); } } interface Base2 { default public void hello() { System.out.println("Hello Base2"); } } public class Sub implements Base1, Base2 { public void hello() { Base1.super.hello(); //使用Base1的实现 } }

 

 

静态默认方法

java8 中,接口也因此可以有静态方法的实现,静态接口的默认实现无需添加 default 关键字:

public interface MyInterf { String m1(); default String m2() { return "Hello default method!"; } static String m3() { return "Hello static method in Interface!"; } }

 

 

java8 中,几乎可以为任何东西添加注解:局部变量、泛型类、父类与接口的实现,就连方法的异常也能添加注解

 

java8 中,通过反射机制,可以获取到参数名了,而在此之前,由于参数名不会保留在 java 字节码中,所以在运行时是无法取得的

想要利用这个新特性,就需要在编译中添加 -parameters 参数,可以在 maven 中配置:

<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>

 

 

运行下面的代码:

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

 

此外,Parameter类有一个很方便的方法isNamePresent()来验证是否可以获取参数的名字

 

java8 增加了 Optional 类,他是一个简单的容器,包含一个泛型元素或 null,用来防止抛出 NullPointerException 异常,这是受到 Google Guava 类库启发而诞生的新特性

 

下面的代码展示了他的用法:

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

 

输出了:

Full Name is set? false

Full Name: [none]

Hey Stranger!

 

在数据库查询等方面具有非常实用的价值

 

Stream 类库(java.util.stream)的添加,将真正的函数式编程风格引入到了 java 中,让代码变得极其简洁

 

基本用法示例

下面的例子展示了如何将一个 ArrayList 中的所有元素的 id 组合到一个 Set 中:

Set<Integer> idSet = dataInfoSet .stream() .map(dataInfo -> dataInfo.getId()).collect(Collectors.toSet());

 

 

更加复杂的,比如我们要计算一个 task 的 List 中所有状态为 OPEN 的任务积分之和,我们调用每个 task 的 getPoints 方法获取积分,使用 Stream 的方式,你可以看到下面的代码:

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 的分数平均值:

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%]

 

实现并发

Stream 可以原生实现并发:

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

 

输出了:

Total points (all tasks): 26.0

 

这个例子和上面的例子很像,但他是用并发的方式实现的,使用 reduce 方法收集结果

 

分组

stream 结合 Collectors 的 groupingBy 方法可以实现分组功能:

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]]}

 

java8 中首次将 Base64 收入标准 java 类库中,实现了非常简单的 Base64 编解码的使用方法:

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!

 

java 对日期时间的处理一直以来饱受诟病,也因此催生了 Joda-Time 项目来简化日期时间的处理,java8 汲取了 Joda-Time 的精髓,诞生了 java.time 包

java8 新增日期时间操作方式

 

在并行与并发方面,java8 做了很大的改进,如我们前面提到的在 Stream、Lambda 表达式的基础上,可以轻松实现并行处理

同时,java 为 java.util.Arrays 提供了多个 parallelXXX 方法,可是实现自动的并行执行,如排序、设置值等,java.util.concurrent.ConcurrentHashMap 也加入了很多用于并发处理的聚集操作方法

java8 在 java.util.concurrent.ForkJoinPool 类中加入了一些新方法用来支持共有资源池(common pool)

新增的java.util.concurrent.locks.StampedLock类提供一直基于容量的锁,这种锁有三个模型来控制读写操作

在java.util.concurrent.atomic包中还增加了下面这些类:

  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder

 

后面,博主会有专门介绍 java 并发机制及使用的详细介绍博文,敬请期待

 

java8 提供了一个新的工具,用来分析一个 class 文件、目录或 jar 包的依赖

如执行:

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

 

 

会输出:

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

 

在 java7 移除永久代 String 对象的常量池以后,java8 中将永久代 PermGen 空间彻底移除,取而代之的是元空间 Metaspace,它存储 java 类元数据,通过 -XX:MetaSpaceSize 与 -XX:MaxMetaspaceSize 参数设置他的大小

如果依然设置了 -XX:PermSize 与 -XX:MaxPermSize 选项,java 并不会报错,但是会忽略这两个选项的值

 

String 的 join 方法

String 提供了 join 方法:

public static String join(CharSequence delimiter, CharSequence... elements) { Objects.requireNonNull(delimiter); Objects.requireNonNull(elements); // Number of elements not likely worth Arrays.stream overhead. StringJoiner joiner = new StringJoiner(delimiter); for (CharSequence cs: elements) { joiner.add(cs); } return joiner.toString(); }

 

实现了类似于 php 中的 implode 方法,将一组字符串连接为一个分隔符分隔的字符串

 

List<String> slist = Arrays.asList("hello", "world"); System.out.println(String.join(",", slist));

 

 

输出了:

hello,world

 

 

 






技术帖      技术分享      stream      java      annotation      j2ee      新特性      java8      lambda      functional interface     


京ICP备15018585号