Java8发布时间是2014年3月19日,距离今日已经很久了,那么Java8新特性你了解吗?
java8是Java的一次重大升级,巨大的里程碑式的改进!!
Java语言新特性:
1.与传统结合 -- Lambda(闭包)表达式和函数式接口以及注解
注意:要能写成Lambda 其对象类一定支持函数式接口的规范,这个规范是jdk检验的,那么什么是函数式接口的规范,原理是什么???
叙说背景和原理:
随着回调模式和函数式编程风格的日益流行,我们需要在Java中提供一种尽可能轻量级的将代码封装为数据(Model code as data)的方法。匿名内部类并不是一个好的选择,因为:
- 语法过于冗余
- 匿名类中的
this
和变量名容易使人产生误解 - 类型载入和实例创建语义不够灵活
- 无法捕获非
final
的局部变量 - 无法对控制流进行抽象
上面的多数问题均在Java SE 8中得以解决:
- 通过提供更简洁的语法和局部作用域规则,Java SE 8彻底解决了问题1和问题2
- 通过提供更加灵活而且便于优化的表达式语义,Java SE 8绕开了问题3
- 通过允许编译器推断变量的“常量性”(finality),Java SE 8减轻了问题4带来的困扰
不过,Java SE 8的目标并非解决所有上述问题。因此捕获可变变量(问题4)和非局部控制流(问题5)并不在Java SE 8的范畴之内。(尽管我们可能会在未来提供对这些特性的支持)
尽管匿名内部类有着种种限制和问题,但是它有一个良好的特性,它和Java类型系统结合的十分紧密:每一个函数对象都对应一个接口类型。之所以说这个特性是良好的,是因为:
- 接口是Java类型系统的一部分
- 接口天然就拥有其运行时表示(Runtime representation)
- 接口可以通过Javadoc注释来表达一些非正式的协定(contract),例如,通过注释说明该操作应可交换(commutative)
上面提到的ActionListener
接口只有一个方法,大多数回调接口都拥有这个特征:比如Runnable
接口和Comparator
接口。我们把这些只拥有一个方法的接口称为函数式接口。(之前它们被称为SAM类型,即单抽象方法类型(Single Abstract Method))
我们并不需要额外的工作来声明一个接口是函数式接口:编译器会根据接口的结构自行判断(判断过程并非简单的对接口方法计数:一个接口可能冗余的定义了一个Object
已经提供的方法,比如toString()
,或者定义了静态方法或默认方法,这些都不属于函数式接口方法的范畴)。不过API作者们可以通过@FunctionalInterface
注解来显式指定一个接口是函数式接口(以避免无意声明了一个符合函数式标准的接口),加上这个注解之后,编译器就会验证该接口是否满足函数式接口的要求。
实现函数式类型的另一种方式是引入一个全新的结构化函数类型,我们也称其为“箭头”类型。例如,一个接收String
和Object
并返回int
的函数类型可以被表示为(String, Object) -> int
。我们仔细考虑了这个方式,但出于下面的原因,最终将其否定:
- 它会为Java类型系统引入额外的复杂度,并带来结构类型(Structural Type)和指名类型(Nominal Type)的混用。(Java几乎全部使用指名类型)
- 它会导致类库风格的分歧——一些类库会继续使用回调接口,而另一些类库会使用结构化函数类型
- 它的语法会变得十分笨拙,尤其在包含受检异常(checked exception)之后
- 每个函数类型很难拥有其运行时表示,这意味着开发者会受到类型擦除(erasure)的困扰和局限。比如说,我们无法对方法
m(T->U)
和m(X->Y)
进行重载(Overload)
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
JDK 1.8 新增加的函数接口:
- java.util.function
java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口有:
序号 | 接口 & 描述 |
---|---|
1 | BiConsumer<T,U> 代表了一个接受两个输入参数的操作,并且不返回任何结果 |
2 | BiFunction<T,U,R> 代表了一个接受两个输入参数的方法,并且返回一个结果 |
3 | BinaryOperator<T> 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果 |
4 | BiPredicate<T,U> 代表了一个两个参数的boolean值方法 |
5 | BooleanSupplier 代表了boolean值结果的提供方 |
6 | Consumer<T> 代表了接受一个输入参数并且无返回的操作 |
7 | DoubleBinaryOperator 代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。 |
8 | DoubleConsumer 代表一个接受double值参数的操作,并且不返回结果。 |
9 | DoubleFunction<R> 代表接受一个double值参数的方法,并且返回结果 |
10 | DoublePredicate 代表一个拥有double值参数的boolean值方法 |
11 | DoubleSupplier 代表一个double值结构的提供方 |
12 | DoubleToIntFunction 接受一个double类型输入,返回一个int类型结果。 |
13 | DoubleToLongFunction 接受一个double类型输入,返回一个long类型结果 |
14 | DoubleUnaryOperator 接受一个参数同为类型double,返回值类型也为double 。 |
15 | Function<T,R> 接受一个输入参数,返回一个结果。 |
16 | IntBinaryOperator 接受两个参数同为类型int,返回值类型也为int 。 |
17 | IntConsumer 接受一个int类型的输入参数,无返回值 。 |
18 | IntFunction<R> 接受一个int类型输入参数,返回一个结果 。 |
19 | IntPredicate :接受一个int输入参数,返回一个布尔值的结果。 |
20 | IntSupplier 无参数,返回一个int类型结果。 |
21 | IntToDoubleFunction 接受一个int类型输入,返回一个double类型结果 。 |
22 | IntToLongFunction 接受一个int类型输入,返回一个long类型结果。 |
23 | IntUnaryOperator 接受一个参数同为类型int,返回值类型也为int 。 |
24 | LongBinaryOperator 接受两个参数同为类型long,返回值类型也为long。 |
25 | LongConsumer 接受一个long类型的输入参数,无返回值。 |
26 | LongFunction<R> 接受一个long类型输入参数,返回一个结果。 |
27 | LongPredicate R接受一个long输入参数,返回一个布尔值类型结果。 |
28 | LongSupplier 无参数,返回一个结果long类型的值。 |
29 | LongToDoubleFunction 接受一个long类型输入,返回一个double类型结果。 |
30 | LongToIntFunction 接受一个long类型输入,返回一个int类型结果。 |
31 | LongUnaryOperator 接受一个参数同为类型long,返回值类型也为long。 |
32 | ObjDoubleConsumer<T> 接受一个object类型和一个double类型的输入参数,无返回值。 |
33 | ObjIntConsumer<T> 接受一个object类型和一个int类型的输入参数,无返回值。 |
34 | ObjLongConsumer<T> 接受一个object类型和一个long类型的输入参数,无返回值。 |
35 | Predicate<T> 接受一个输入参数,返回一个布尔值结果。 |
36 | Supplier<T> 无参数,返回一个结果。 |
37 | ToDoubleBiFunction<T,U> 接受两个输入参数,返回一个double类型结果 |
38 | ToDoubleFunction<T> 接受一个输入参数,返回一个double类型结果 |
39 | ToIntBiFunction<T,U> 接受两个输入参数,返回一个int类型结果。 |
40 | ToIntFunction<T> 接受一个输入参数,返回一个int类型结果。 |
41 | ToLongBiFunction<T,U> 接受两个输入参数,返回一个long类型结果。 |
42 | ToLongFunction<T> 接受一个输入参数,返回一个long类型结果。 |
43 | UnaryOperator<T> 接受一个参数为类型T,返回值类型也为T。 |
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 ) );
如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来,类似于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;
} );
上述的forEach(),sort() 就是一个Consumer<String>函数式接口,传入String值。
以上是函数式接口的lambda表达,接着看几个依据函数式接口的方法引用:
public 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,实例如下:
cars . forEach ( Car :: collide ) ; -
特定类的任意对象的方法引用:它的语法是Class::method实例如下:
cars . forEach ( Car :: repair ) ; -
特定对象的方法引用:它的语法是instance::method实例如下:
final Car police = Car . create ( Car :: new ) ; cars . forEach ( police :: follow ) ;
Java思想:
Java8 新增加上述的函数式接口是为了干什么?如果你在未实际开发中使用这些接口,而是在了解这些函数式接口就能体会到,那么你对Java的思想的理解还是挺到位的!
上述函数式接口无非就是传入,提供的操作(返回值不是强制的),注意:因为既然是函数式接口,那么必然是接口实现的过程,只是语法表达上使用函数式罢了。既然接口要么就是回调开始端传入参数或者对象,也就是“传入”。要么就是接口回调执行端执行操作之后返回对象(就是提供对象)或者返回值。
那么是不是有人问:传入值和返回值(对象),直接在函数式表达中声明不就可以了,其实这样也可以,只是iava8多了一层对传入和返回的封装(对应做成了函数式接口),就是上面罗列的函数式接口。
@FunctionInterface 注解声明函数式接口,就是声明一个函数式接口,里面的抽象方法,就是可以使用Lambda表达式;但是注意函数参数类型。
2.接口的默认方法,静态方法
Java8中允许接口存在静态方法,增加了默认方法。默认方法就是可以在接口中有自己的函数体(注意不是虚方法),其他扩展接口,那么直接覆盖默认方法。
3.处理时间、日期的API
4.base64编码
5.nashorn js 引擎
提到nashorn,必须提到jjs(nashorn命令行工具),在cmd中你为什么配置环境变量,就可以输入命令行,编译Java。为什么呢,因为jdk中包含命令行工具,那么Jdk8中,包含了nashorn命令行工具--jjs。
在配置Jdk8环境变量之后,直接在cmd中输入jjs.
执行js脚本:
创建并保存sample.js在 C:> JAVA 文件夹。
sample.jsprint('Hello World!');
打开控制台并使用下面的命令。
C:\JAVA>jjs sample.js
看到结果
Hello World!
在Java中调用js:
import javax.script.ScriptEngineManager; import javax.script.ScriptEngine; import javax.script.ScriptException; public class Java8Tester { public static void main(String args[]){ ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn"); String name = "Mahesh"; Integer result = null; try { nashorn.eval("print('" + name + "')"); result = (Integer) nashorn.eval("10 + 2"); }catch(ScriptException e){ System.out.println("Error executing script: "+ e.getMessage()); } System.out.println(result.toString()); } }
6.并发与并行数组
7.util下面的stream接口
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
+--------------------+ +------+ +------+ +---+ +-------+ | stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect| +--------------------+ +------+ +------+ +---+ +-------+
以上的流程转换为 Java 代码为:
List<Integer> transactionsIds = widgets.stream() .filter(b -> b.getColor() == RED) .sorted((x,y) -> x.getWeight() - y.getWeight()) .mapToInt(Widget::getWeight) .sum();
什么是 Stream?
Stream(流)是一个来自数据源的元素队列并支持聚合操作
- <strong元素队列< strong="">元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
- 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
生成流
在 Java 8 中, 集合接口有两个方法来生成流:
-
stream() − 为集合创建串行流。
-
parallelStream() − 为集合创建并行流。
forEach
Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:
map
map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:
filter
filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:
limit
limit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据:
sorted
sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:
并行(parallel)程序
parallelStream 是流并行处理程序的代替方法。以下实例我们使用 parallelStream 来输出空字符串的数量:
我们可以很容易的在顺序运行和并行直接切换。
Collectors
Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:
统计
另外,一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。