概述:
Java 8 新特性概述:https://www.ibm.com/developerworks/cn/java/j-lo-jdk8newfeature/index.html
JAVA8 十大新特性详解:https://www.cnblogs.com/xingzc/p/6002873.html
关于lambda表达式:
State of the Lambda:http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html
Translation of Lambda Expressions:http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html
关于Stream:
Java的Stream流式处理:https://blog.csdn.net/qq_20989105/article/details/81234175
Java 8 中的 Streams API 详解:https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/
Java 8 新特性
结构安排与大部分内容转自:https://www.ibm.com/developerworks/cn/java/j-lo-jdk8newfeature/index.html
关于Stream的大部分内容转自:https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/
内容较多,建议通过目录浏览
函数式接口(Functional Interfaces)
在接口里面添加抽象方法,这些方法可以直接从接口中运行。
如果一个接口定义唯一一个抽象方法,那么这个接口就成为函数式接口。
新的注解:@FunctionalInterface
。可以把他它放在一个接口前,表示这个接口是一个函数式接口。
这个注解是非必须的,只要接口只包含一个方法的接口,虚拟机会自动判断。不过最好在接口上使用注解 @FunctionalInterface
进行声明,编译器会判断接口中是否只有一个方法,否则编译器会报错。
//java.lang.Runnable 就是一个函数式接口。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
通用函数式接口
Function、Predicate、Consumer
java.util.function
-
Function<T, R>
:将 T 作为输入,返回 R 作为输出,还包含了和其他函数组合的默认方法。public class Test { public static void main(String[] args) throws InterruptedException { String name = ""; String name1 = "12345"; System.out.println(validInput(name, inputStr -> inputStr.isEmpty() ? "名字不能为空":inputStr)); System.out.println(validInput(name1, inputStr -> inputStr.length() > 3 ? "名字过长":inputStr)); } public static String validInput(String name,Function<String,String> function) { return function.apply(name); } }
-
Predicate<T>
:将 T 作为输入,返回一个布尔值作为输出,该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(与、或、非)。public class Test { public static void main(String[] args) throws InterruptedException { String name = ""; String name1 = "12345"; validInput(name, inputStr -> System.out.println(inputStr.isEmpty() ? "名字不能为空":"名字正常")); validInput(name1, inputStr -> System.out.println(inputStr.isEmpty() ? "名字不能为空":"名字正常")); } public static void validInput(String name,Consumer<String> function) { function.accept(name); } }
-
Consumer<T>
:将 T 作为输入,不返回任何内容,表示在单个参数上的操作。public class Test { public static void main(String[] args) throws InterruptedException { String name = ""; String name1 = "12"; String name2 = "12345"; System.out.println(validInput(name,inputStr -> !inputStr.isEmpty() && inputStr.length() <= 3 )); System.out.println(validInput(name1,inputStr -> !inputStr.isEmpty() && inputStr.length() <= 3 )); System.out.println(validInput(name2,inputStr -> !inputStr.isEmpty() && inputStr.length() <= 3 )); } public static boolean validInput(String name,Predicate<String> function) { return function.test(name); } }
函数式接口与通用函数式接口比较
//例如,People 类中有一个方法 getMaleList 需要获取男性的列表,这里需要定义一个函数式接口 PersonInterface:
interface PersonInterface {
public boolean test(Person person);
}
public class People {
private List<Person> persons= new ArrayList<Person>();
public List<Person> getMaleList(PersonInterface filter) {//自定义函数式接口
List<Person> res = new ArrayList<Person>();
persons.forEach((Person person) -> {
if (filter.test(person)) {
//调用 PersonInterface 的方法
res.add(person);
}
});
return res;
}
}
//为了去除 PersonInterface 这个函数式接口,可以用通用函数式接口 Predicate 替代如下:
class People{
private List<Person> persons= new ArrayList<Person>();
public List<Person> getMaleList(Predicate<Person> predicate) {//通用函数式接口
List<Person> res = new ArrayList<Person>();
persons.forEach(person -> {
if (predicate.test(person)) {//调用 Predicate 的抽象方法 test
res.add(person);
}
});
return res;
}
}
Lambda 表达式
函数式接口的重要属性是:我们能够使用 Lambda 实例化它们。
Lambda 表达式让我们能够将函数作为方法参数,或者将代码作为数据对待。
Lambda 表达式由三个部分组成:
第一部分为一个括号内用逗号分隔的形式参数(参数列表),参数是函数式接口里面方法的参数;
第二部分为一个箭头符号:->
;
第三部分为方法体,可以是表达式和代码块。
形式参数的类型可以由Java的类型推断机制进行推断,也可以显示声明
涉及知识点:类型参数推断(Java8的泛型目标类型推断)
为了进一步简化 Lambda 表达式,可以使用方法引用。
例如,下面三种分别是使用内部类、Lambda 表示式和方法引用的比较:
//1. 使用匿名内部类
Function<Integer, String> f = new Function<Integer,String>(){
@Override
public String apply(Integer t) {
return null;
}
};
//2. 使用 Lambda 表达式
Function<Integer, String> f2 = (t)->String.valueOf(t);
//3. 使用方法引用的方式
Function<Integer, String> f1 = String::valueOf;
方法引用、构造器引用
方法引用和构造器引用:https://www.cnblogs.com/aoeiuv/p/5911692.html
objectName::instanceMethod
ClassName::staticMethod
ClassName::instanceMethod
ClassName::new
接口的增强
默认方法 ——default
关键字
Java 8 允许我们给接口添加一个非抽象的方法实现,只需要使用default
关键字即可,这个特征又叫做扩展方法。
在实现该接口时,该默认方法在子类上可以直接使用,它的使用方式类似于抽象类中非抽象成员方法。
默认方法不能够重载 Object 中的方法。例如:toString、equals、 hashCode 不能在接口中被重载。
静态方法 ——static
关键字
使用static关键字
流式操作 Stream
概述
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
通过Stream操作可以实现对集合(Collection)的并行处理和函数式操作。
根据操作返回的结果不同,流式操作分为中间操作和最终操作两种:
- 最终操作返回一特定类型的结果
- 中间操作返回流本身,这样就可以将多个操作依次串联起来。
根据流的并发性,流又可以分为串行和并行两种。流式操作实现了集合的过滤、排序、映射等功能。
当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。
Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。
Stream 的另外一大特点是,数据源本身可以是无限的。
Stream 和 Collection 集合的区别:
Collection
是一种静态的内存数据结构,主要面向内存,存储在内存中,Stream
是有关计算的。主要是面向 CPU,通过 CPU 实现计算。
流的构成
流的生成方式
有多种方式生成 Stream Source:
- 从 Collection 和数组
Collection.stream() Collection.parallelStream() Arrays.stream(T array) Stream.of()
- 从 BufferedReader
java.io.BufferedReader.lines()
- 静态工厂
java.util.stream.IntStream.range() java.nio.file.Files.walk()
- 自己构建
java.util.Spliterator
- 其它
Random.ints() BitSet.stream() Pattern.splitAsStream(java.lang.CharSequence) JarFile.stream()
流的操作类型
- Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
- Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
在对于一个 Stream 进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都做掉的总和吗?
其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。
我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。
还有一种操作被称为 short-circuiting,它表示:
- 对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。
- 对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。
Intermediate 中间操作
该操作会保持 stream 处于中间状态,允许做进一步的操作。它返回的还是的 Stream,允许更多的链式操作。常见的中间操作有:
filter():对元素进行过滤;
sorted():对元素排序;
map():元素的映射;
distinct():去除重复元素;
subStream():获取子 Stream 等。
list.stream()
.filter((s) -> s.startsWith("s"))
.forEach(System.out::println);
Terminal 终止操作
该操作必须是流的最后一个操作,一旦被调用,Stream 就到了一个终止状态,而且不能再使用了。常见的终止操作有:
forEach():对每个元素做处理;
toArray():把元素导出到数组;
findFirst():返回第一个匹配的元素;
anyMatch():是否有匹配的元素等。
list.stream() //获取列表的 stream 操作对象
.filter((s) -> s.startsWith("s"))//对这个流做过滤操作
.forEach(System.out::println);
short-circuiting
当操作一个无限大的 Stream,而又希望在有限时间内完成操作,则在管道内拥有一个 short-circuiting 操作是必要非充分条件。
示例:
int sum = widgets.stream()
.filter(w -> w.getColor() == RED)
.mapToInt(w -> w.getWeight())
.sum();
stream() 获取当前widgets的 source。
filter 和 mapToInt 是 intermediate 操作,进行数据筛选和转换。
最后一个 sum() 为 terminal 操作,对符合条件的全部小物件作重量求和。
(这里没太明白,哪里体现了short-circuiting 操作?)
流的使用详解
简单说,对 Stream 的使用就是实现一个 filter-map-reduce 过程,产生一个最终结果,或者导致一个副作用(side effect)。
具体内容:https://blog.csdn.net/u011848397/article/details/89108648
注解的更新
对于注解,Java 8 主要有两点改进:类型注解和重复注解。
类型注解
现在几乎可以为任何东西添加注解:局部变量、类与接口,就连方法的异常也能添加注解。
Java 8 本身虽然没有自带类型检测的框架,但可以通过使用 Checker Framework 这样的第三方工具,自动检查和确认软件的缺陷,提高生产效率。
import org.checkerframework.checker.nullness.qual.NonNull;
public class TestAnno {
public static void main(String[] args) {
@NonNull Object obj = null;
obj.toString();
}
}
console:
C:\workspace\TestJava8\src\TestAnno.java:4: Warning:
(assignment.type.incompatible) $$ 2 $$ null $$ @UnknownInitialization @NonNull Object $$ ( 152, 156 )
$$ incompatible types in assignment.
@NonNull Object obj = null;
^
found : null
required: @UnknownInitialization @NonNull Object
重复注解
Java 8 引入了重复注解机制,这样相同的注解可以在同一地方声明多次。重复注解机制本身必须用 @Repeatable 注解。
安全性
IO/NIO 的改进
全球化功能
新的 java.time 中包含了所有关于:
时钟(Clock),
本地日期(LocalDate)、
本地时间(LocalTime)、
本地日期时间(LocalDateTime)、
时区(ZonedDateTime)
持续时间(Duration)的类。
历史悠久的 Date 类新增了 toInstant() 方法,用于把 Date 转换成新的表示形式。