个人博客
个人博客: https://www.crystalblog.xyz/
备用地址: https://wang-qz.gitee.io/crystal-blog/
1. 简介
JDK8是官方发布的一个大版本, 提供了很多新特性功能给开发者使用, 包含语言、编译器、库、工具和JVM
等方面的十多个新特性。 本文将介绍编码过程中常用的一些新特性. JDK8帮助文档
- Lambda表达式
- 函数式接口
- 方法引用和构造器调用
- Stream API
- 接口中的默认方法和静态方法
- 新的日期时间API
- Optional
2. Lambda表达式
2.1 介绍
lambda
表达式 (也称为闭包), 本质上是一段匿名内部类或一段可以传递的代码 . 可以理解为使用更加方便的“语法糖”,但原理不变的编写语法。 它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理 .
语法糖: 简洁而紧凑的语言结构 .
2.2 使用
Lambda表达式由逗号分隔的参数列表
、->符号
和语句块
三部分组成 .
// jdk8之前需要循环集合进行遍历输出
Arrays.asList( "a", "b", "d" ).forEach( (String s) -> {
System.out.print( s );
} );
如果语句块只有一句, 可以省略大括号; 参数列表的类型也可以省略, 如果只有一个参数, 参数的括号也可以省略.
Arrays.asList( "a", "b", "d" ).forEach( s -> System.out.print( s ) );
如果有语句块中有返回值
// jdk8之前,sort(Comparator c)需要写一个匿名内部类实现
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
int result = e1.compareTo( e2 );
return result;
} );
可以继续简化, 只有一个返回值语句, 大括号和return都可以省略.
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
2.3 案例
需求: 商城浏览商品信息, 筛选出颜色为红色, 价格小于8000的商品.
传统方式编写, 每次筛选都要对集合进行循环遍历判断, 这些都是重复的操作.
/**
* 需求: 商城浏览商品信息, 筛选出颜色为红色, 价格小于8000的商品.
*/
public class LambdaTest {
@Test
public void testFilterProducts() {
List<Product> products = getProducts();
products = this.filterProductByColor(products);
products = this.filterProductByPrice(products);
products.forEach(System.out::println);
}
// 筛选颜色
public List<Product> filterProductByColor(List<Product> list) {
List<Product> prods = new ArrayList<>();
for (Product product : list) {
if ("红色".equals(product.getColor())) {
prods.add(product);
}
}
return prods;
}
// 筛选价格
public List<Product> filterProductByPrice(List<Product> list) {
List<Product> prods = new ArrayList<>();
for (Product product : list) {
if (product.getPrice() < 8000) {
prods.add(product);
}
}
return prods;
}
// 添加商品数据
private List<Product> getProducts() {
List<Product> products = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
Product pro = Product.builder()
.color(i % 2 == 0 ? "红色" : "绿色")
.price(i * 1000)
.build();
products.add(pro);
}
return products;
}
}
使用lambda表达式+函数接口进行过滤
package cn.itcast.test.newf;
import cn.itcast.pojo.Product;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
/**
* 需求: 商城浏览商品信息, 筛选出颜色为红色, 价格小于8000的商品.
*/
public class LambdaTest {
// lambda表达式过滤
@Test
public void testFilterProducts2() {
List<Product> products = getProducts();
products = filterProductByPredicate(products, (product -> Objects.equals("红色", product.getColor())));
products = filterProductByPredicate(products, (product -> Double.compare(8000, product.getPrice()) > 0));
products.forEach(System.out::println);
}
// 使用lambda表达式, Predicate为断言的函数接口, 接口方法返回boolean类型
public List<Product> filterProductByPredicate(List<Product> list, Predicate<Product> p) {
List<Product> prods = new ArrayList<>();
for (Product product : list) {
if (p.test(product)) {
prods.add(product);
}
}
return prods;
}
}
不使用函数接口, 单纯使用lambda表达式实现
@Test
public void testFilterProducts3() {
List<Product> products = getProducts();
products.stream()
.filter((product -> Objects.equals("红色", product.getColor())))
.filter(product -> Double.compare(8000, product.getPrice()) > 0)
.forEach(System.out::println);
}
测试结果
Product(color=红色, price=2000.0)
Product(color=红色, price=4000.0)
Product(color=红色, price=6000.0)
2.4 总结
Lmabda表达式的语法总结: () -> ();
场景 | 语法 |
---|---|
无参数无返回值 | () -> System.out.println(“Hello WOrld”) |
有一个参数无返回值 | (x) -> System.out.println(x) |
有且只有一个参数无返回值 | x -> System.out.println(x) |
有多个参数,有返回值,有多条lambda体语句 | (x,y) -> {System.out.println(“xxx”);return xxxx;}; |
有多个参数,有返回值,只有一条lambda体语句 | (x,y) -> xxxx |
口诀:左右遇一省括号,左侧推断类型省
3. 函数式接口
函数式接口的提出是为了给Lambda表达式的使用提供更好的支持。
3.1 简介
函数接口
指的是只有一个抽象方法的接口,这样的接口可以隐式转换为Lambda表达式。 当然接口中可以包含其他的方法(默认,静态,私有) .
java.lang.Runnable和java.util.concurrent.Callable是函数式接口的最佳例子。函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解**@FunctionalInterface** , 所以可以通过@FunctionalInterface
注解检测接口是否为一个函数式接口 .
默认方法和静态方法不会破坏函数式接口的定义 , 如下所示:
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
// 默认方法
default void defaultMethod() {}
// 静态方法
static void staticMethod(){}
}
下面学习JDK8提供的常用函数式接口API, 主要有Consumer, Supplier , Predicate , Function.
3.2 Consumer
3.2.1 介绍
消费型接口,有参无返回值 , 参数类型由泛型决定 .
@FunctionalInterface
public interface Consumer<T> {
//....
}
3.2.2 accept方法
表示消费一个指定泛型类型的数据 .
void accept(T t);
accept使用
/**
* Consumer<T>:消费型接口,有参无返回值
*/
public class ConsumerTest {
//定义一个方法
//方法的参数传递一个字符串的姓名
//方法的参数传递Consumer接口,泛型使用String
//可以使用Consumer接口消费字符串的姓名
private void accept(String name, Consumer<String> consumer) {
consumer.accept(name);
}
@Test
public void test() {
//对传递的字符串进行消费
//消费方式,把字符串进行反转输出
accept("admin", (name) -> {
String reName = new StringBuilder(name).reverse().toString();
System.out.println(reName);
});
}
}
3.2.3 andThen方法
Consumer接口的默认方法andThen , 可以把两个Consumer接口组合到一起,再对数据进行消费 ( 谁写前边,谁先消费 ).
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
andThen方法使用
/**
* Consumer<T>:消费型接口,有参无返回值
*/
public class ConsumerTest {
//定义一个方法,方法的参数传递一个字符串和两个Consumer接口,Consumer接口的泛型使用字符串
private void andThen(String s, Consumer<String> c1,
Consumer<String> c2) {
//使用andThen方法,把两个Consumer接口连接到一起,先消费c1再消费c2
c1.andThen(c2).accept(s);
}
@Test
public void test2() {
andThen("admin",
(name) -> {
System.out.println(name);
},
(name) -> {
//消费方式,把字符串转换为大写输出
System.out.println(name.toUpperCase(Locale.ROOT));
});
}
}
3.2.4 案例
需求: 格式化打印信息 , 字符串数组中存有多条信息,请按照格式,“姓名:xx,性别:xx“的格式信息打印出来 .
分析:
将打印姓名的动作作为第一个Consumer接口的Lambda实例
将打印性别的动作作为第二个Consumer接口的Lambda实例
将两个Consumer接口按照顺序”拼接“到一起
package cn.itcast.test.jdk8;
import org.junit.Test;
import java.util.Locale;
import java.util.function.Consumer;
/**
* Consumer<T>:消费型接口,有参无返回值
*/
public class ConsumerTest {
//定义一个方法,参数传递String类型的数组和两个Consumer接口,泛型使用String
// 用两个Consumer接口消费字符串
private void printInfo(String[] arr, Consumer<String> consumer, Consumer<String> consumer2) {
//遍历字符串数组
for (String s : arr) {
consumer.andThen(consumer2).accept(s);
}
}
@Test
public void test3() {
//定义一个字符串类型的数据
String[] arr = {"刘辣子,女", "杨哈哈,男", "赵屌屌,男"};
//调用printInfo方法,传递一个字符串数组,和两个Lambda表达式
printInfo(arr,
(s) -> {
String name = s.split(",")[0];
System.out.print("姓名:" + name);
},
(s) -> {
String gender = s.split(",")[1];
System.out.println("、性别:" + gender + ".");
});
}
}
输出结果
姓名:刘辣子、性别:女.
姓名:杨哈哈、性别:男.
姓名:赵屌屌、性别:男.
3.3 Supplier
3.3.1 介绍
供给型接口,无参有返回值 .
@FunctionalInterface
public interface Supplier<T> {
T get();
}
3.3.2 get方法
Supplier接口仅包含一个无参的方法:T get() .
用来获取一个泛型参数指定类型的对象数据。意味着对应的Lambda表达式需要"对外提供"一个符合泛型类型的对象数据。
/**
* Supplier<T>:供给型接口,无参有返回值
*/
public class SupplierTest {
public String getString(Supplier<String> supplier) {
return supplier.get();
}
@Test
public void test() {
//定义一个方法,方法的参数传递Supplier<T>接口,泛型执行String,get方法就会返回一个String
String s = getString(() -> "人才");
System.out.println(s);
}
private Integer getMax(Supplier<Integer> supplier) {
//定义一个方法,用于获取int类型数组中元素的最大值,方法的参数传递Supplier接口,泛型使用包装类Integer
return supplier.get();
}
@Test
public void test2() {
//定义一个int类型的数组,并赋值
int[] arr = {100, 78, -887, 66, 90};
//调用getMax方法,方法参数Supplier是一个函数式接口,可以传递Lambda表达式
int maxValue = getMax(() -> {
int max = arr[0];
for (int a : arr) {
if (a > max) {
max = a;
}
}
return max;
});
System.out.println(maxValue);
}
}
3.4 Predicate
3.4.1 介绍
断言型接口,有参有返回值, 对某种类型的数据进行判断 , 返回boolean类型 .
@FunctionalInterface
public interface Predicate<T> {
//...
}
3.4.2 test方法
boolean test(T t):用于对指定类型数据进行判断, 符合条件返回true,不符合条件返回false
boolean test(T t);
test方法的使用
/**
* Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
*/
public class PredicateTest {
//定义一个方法
//参数传递一个String类型的字符串
//传递一个Predicate接口,泛型使用String
//使用predicate中的方法test对字符串进行判断,并把判断的结果返回
public boolean checkString(String s, Predicate<String> predicate) {
// 对某种数据类型的数据进行判断,结果返回一个boolean值
return predicate.test(s);
}
@Test
public void test() {
//定义一个字符串
String s = "abcdef";
//调用checkString方法对字符串进行校验,参数传递字符串和Lambda表达式
//对参数传递的字符串进行判断,判断字符串的长度是否大于5,并把判断的结果返回
boolean b = checkString(s, str -> str.length() > 5);
System.out.println(b);
}
}
3.4.3 and方法
逻辑表达式:可以连接多个判断的条件 .
&&:与运算符. 连接的多个判断条件都为true才返回true, 否则false.
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
需求:判断一个字符串,有两个判断的条件
1、判断字符串的长度是否大于5
2、判断字符串中是否包含a
两个条件必须同时满足,使用&&运算符连接两个条件 .
/**
* Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
*/
public class PredicateTest {
//定义一个方法,方法的参数,传递一个字符串
//传递俩个Predicate接口
//1、判断字符串的长度是否大于5 2、判断字符串中是否包含a 两个条件必须同时满足,使用&&运算符连接两个条件
private boolean checkString(String s, Predicate<String> predicate, Predicate<String> predicate2) {
//等价于return pre1.test(s)&&pre2.test(s);
return predicate.and(predicate2).test(s);
}
@Test
public void test2() {
//定义一个字符串
String s = "asdfgi";
//调用checkString方法,参数传递字符串和两个Lambda表达式
boolean b = checkString(s,
str -> !StringUtils.isEmpty(str) && str.length() > 5,
str -> !StringUtils.isEmpty(str) && str.contains("a"));
System.out.println(b);
}
}
3.4.4 or方法
||:或运算符,连接的多个判断条件有一个为true就返回true, 都为false才返回false.
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
需求:判断一个字符串,有两个判断的条件
1、判断字符串的长度是否大于5
2、判断字符串中是否包含a
两个条件满足一个即可,使用||运算符连接两个条件 .
/**
* Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
*/
public class PredicateTest {
@Test
public void test2() {
//定义一个字符串
String s = "asdfgi";
//调用checkStringOr方法,参数传递字符串和两个Lambda表达式
boolean b = checkStringOr(s,
str -> !StringUtils.isEmpty(str) && str.length() > 5,
str -> !StringUtils.isEmpty(str) && str.contains("a")
);
System.out.println(b);
}
//定义一个方法,方法的参数,传递一个字符串
//传递俩个Predicate接口
//1、判断字符串的长度是否大于5 2、判断字符串中是否包含a 满足一个条件即可,使用||运算符连接两个条件
private boolean checkStringOr(String s, Predicate<String> predicate, Predicate<String> predicate2) {
return predicate.or(predicate2).test(s);
}
}
3.4.5 negate方法
!:非(取反):非真则假,非假则真 .
default Predicate<T> negate() {
return (t) -> !test(t);
}
需求:判断一个字符串长度是否大于5
如果字符串的长度大于5,返回false
如果字符串的长度小于5,返回true
使用取反符号!对判断的结果进行取反 .
/**
* Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
*/
public class PredicateTest {
//定义一个方法,方法的参数,传递一个字符串
public boolean checkStringNegate(String s, Predicate<String> predicate) {
//使用Predicate接口判断字符串的长度是否大于5
//return !pre.test(s);
return predicate.negate().test(s);
}
@Test
public void testNegate() {
//定义一个字符串
String s = "asdfg";
//调用checkString方法,参数传递字符串和Lambda表达式
boolean b = checkStringNegate(s, str -> str.length() > 5);
System.out.println(b);
}
}
3.4.6 案例
需求: 集合信息筛选
数组当中有多条“姓名+性别”的信息如下:
String[] array = {"刘小甜甜,女","杨哈哈,男","赵屌屌,男"}
通过Predicate接口的拼装将符合要求的字符串筛选到ArrayList集合中
需求同时满足两个条件:必须为女生,姓名为4个字
1、有两个判断条件,需要使用两个Predicate接口,对条件进行判断
2、必须同时满足两个条件,可以使用and方法连接两个判断条件
/**
* Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
*/
public class PredicateTest {
// 定义一个方法, 两个Predicate接口参数
public ArrayList<String> filter(String[] arr, Predicate<String> predicate, Predicate<String> predicate2) {
ArrayList<String> list = new ArrayList<>();
for (String s : arr) {
if (predicate.and(predicate2).test(s)) {
list.add(s);
}
}
return list;
}
@Test
public void test4() {
//定义一个存储字符串的数组
String[] array = {"刘小甜甜,女", "杨哈哈,男", "赵屌屌,男"};
//调用filter方法,传递字符串数组和两个Lambda表达式
ArrayList<String> list = filter(array,
str -> !StringUtils.isEmpty(str) && str.split(",")[0].length() == 4,
str -> !StringUtils.isEmpty(str) && str.split(",")[1].equals("女")
);
list.forEach(System.out::println);
}
}
3.5 Function
3.5.1 介绍
函数式接口,有参有返回值 . java.util.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据.
前者称为前置条件,后者称为后置条件 .
@FunctionalInterface
public interface Function<T, R> {
// ...
}
3.5.2 apply方法
Function接口中最主要的抽象方法为:R apply(T,t),根据类型T的参数获取类型的R结果 .
R apply(T t);
例如将String转换为Integer类型 .
/**
* Function<T, R> : 函数式接口,有参有返回值
* java.util.function<T,R>接口用来根据一个类型的数据得到另一个类型的数据
*/
public class FunctionTest {
//定义一个方法
//方法的参数传递一个字符串类型的整数
//方法的参数传递一个Function接口,泛型使用<String,Integer>
//使用Function接口中的方法apply,把字符串类型的整数,转换为Integer类型的整数
private Integer change(String s, Function<String, Integer> function) {
Integer apply = function.apply(s);
return apply;
}
@Test
public void test() {
//定义一个字符串类型的整数
String s = "1234";
//调用change方法传递字符串类型的整数,和Lambda表达式
Integer change = change(s, str -> Integer.parseInt(str));
System.out.println(change instanceof Integer);
}
}
3.5.3 andThen方法
Function接口中的默认方法andThen,用来进行组合操作 . 先转换第一个, 再转换第二个.
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
需求:把String类型的“123”,转换为Integer类型,把转换后的结果+10, 再把增加之后的Integer类型的数据转换为String类型 .
分析: String->Integer-> 加10->String.
/**
* Function<T, R> : 函数式接口,有参有返回值
* java.util.function<T,R>接口用来根据一个类型的数据得到另一个类型的数据
*/
public class FunctionTest {
//定义一个方法
//参数传一个字符串类型的整数
//参数在传两个Function接口
//一个泛型使用Function<String,Integer>
//一个泛型使用Function<Integer,String>
private String change(String s, Function<String, Integer> fun, Function<Integer, String> fun2) {
System.out.println("apply...");
return fun.andThen(fun2).apply(s);
}
@Test
public void test2() {
//定义一个字符串类型的整数
String s = "123";
//调用change方法,传递字符串和两个Lambda表达式
String change = change(s,
str -> Integer.parseInt(s) + 10,
integer -> String.valueOf(integer)
);
System.out.println(change + ", " + change instanceof String);
}
}
3.5.4 案例
使用Function进行函数模型的拼接,按照顺序需要执行的多个函数操作为:String str = “刘辣子,18”;
分析:
(1)、将字符串截取数字年龄部分,得到数字字符串
Function<String,String> “刘辣子,18”->“18”
(2)、将上一步的数字字符串转换为int类型的数字
Function<String,Integer>“18”->18
(3)、将上一步的int数字累加100,得到结果int数字
Function<Integer,Inerger>18->118
/**
* Function<T, R> : 函数式接口,有参有返回值
* java.util.function<T,R>接口用来根据一个类型的数据得到另一个类型的数据
*/
public class FunctionTest {
//定义一个方法
//参数传递包含姓名和年龄的字符串
//参数传递3个Function接口用于类型转换
private Integer change(String s, Function<String, String> fun, Function<String, Integer> fun2, Function<Integer, Integer> fun3) {
return fun.andThen(fun2).andThen(fun3).apply(s);
}
/**
* 1、将字符串截取数字年龄部分,得到数字字符串
* Function<String,String> “刘辣子,18”->“18”
* 2、将上一步的数字字符串转换为int类型的数字
* Function<String,Integer>“18”->18
* 3、将上一步的int数字累加100,得到结果int数字
* Function<Integer,Inerger>18->118
*/
@Test
public void test3() {
//定义一个字符串
String s = "刘辣子,18";
//调用change方法,参数传递字符串和3个Lambda表达式
Integer change = change(s,
str -> str.split(",")[1],
str -> Integer.parseInt(str),
num -> num + 100
);
System.out.println(change);
}
}
3.6 自定义函数式接口
定义函数式接口
@FunctionalInterface
public interface MyFunctionalInterface {
//定义一个抽象方法, public abstract可以省略
public abstract void method();
}
接口实现类方式实现接口方法
public class MyFunctionalInterfaceImpl implements MyFunctionalInterface {
@Override
public void method() {
System.out.println(">>>MyFunctionalInterfaceImpl");
}
}
函数式接口的使用
/**
* 函数式接口的使用测试
*/
public class MyFunctionalInterfaceTest {
@Test
public void test() {
// 调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象
show(new MyFunctionalInterfaceImpl());
// 调用show方法,方法的参数是一个接口,所以可以传递接口的匿名内部类
show(new MyFunctionalInterface() {
@Override
public void method() {
System.out.println(">>>MyFunctionalInterface的匿名内部类实现");
}
});
// 调用show方法,方法的参数是一个函数式接口,可以传递Lambda表达式
show(() -> System.out.println(">>>MyFunctionalInterface的lambda表达式实现"));
}
// 定义一个方法,参数使用函数式接口MyFunctionalInterInterface
public void show(MyFunctionalInterface myFunctionalInterface) {
myFunctionalInterface.method();
}
}
测试结果
>>>MyFunctionalInterfaceImpl
>>>MyFunctionalInterface的匿名内部类实现
>>>MyFunctionalInterface的lambda表达式实现
3.7 Lambda的延迟加载
使用函数式接口, 应用Lambda表达式方式实现可以达到延迟加载的目的, 优化提升系统性能.
场景: 程序运行根据日志级别输出日志信息.
实现方式1
/**
* 根据不同日志级别输出日志信息
*/
public class LogPrintTest {
@Test
public void test() {
//定义三个日志信息
String msg1 = "Hello ";
String msg2 = "World ";
String msg3 = "Java";
//调用showLog方法,传递日志级别和日志信息
printLog(2, msg1 + msg2 + msg3);
}
//定义一个根据日志的级别,显示日志信息的方法
public void printLog(int level, String message) {
//对日志的等级进行判断,如果式1级别,那么输出日志信息
if (level == 1) {
System.out.println(message);
}
}
}
分析:
以上代码存在性能浪费, 调用printLog方法,第二个参数是一个拼接后的字符串, 先把字符串拼接好,然后在调用printLog方法打印日志信息. printLog方法中如果传递的日志等级不是1, 也会进行字符串的拼接, 导致不必要的资源浪费.
实现方式2: Lamdba表达式优化
/**
* 根据不同日志级别输出日志信息
*/
public class LogPrintTest {
@Test
public void test2() {
//定义三个日志信息
String msg1 = "Hello ";
String msg2 = "World ";
String msg3 = "Java";
//调用showLog方法,参数MessageBulider是一个函数式接口,所以可以传递Lambda表达式
showLog(1, () -> msg1 + msg2 + msg3);
}
//定义一个显示日志的方法,方法的参数传递日志的等级和Message接口
public void showLog(int level, MessageBulider mb) {
//对日志的等级进行判断,如果是1级,则调用MessageBulider接口中的BuilderMessage方法
if (level == 1) {
System.out.println(mb.builderMessage());
}
}
// 定义一个函数式接口
@FunctionalInterface
public interface MessageBulider {
//定义一个拼接消息的抽象方法,返回拼接的消息
String builderMessage();
}
}
分析:
使用Lambda表达式作为参数传递,仅仅是把参数传递到showLog方法中 , 只有满足日志等级是1的条件, 才会传递第二个参数(
接口MessageBuilder中的方法builderMessage拼接日志信息
); 如果不满足日志等级是1的条件, 那么MessageBuilder接口中的方法builderMessage也不会执行, 拼接字符串的代码也不会执行, 所以不会存在性能的浪费 .
3.8 函数式接口作为方法的参数
Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果方法的参数
是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。
例如, java.lang.Runnable 接口就是一个函数式接口,假设有一个startThread方法使用该接口作为参数,那么就可以使用Lambda进行传参。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
lambda表达式作为方法的参数使用
/**
* lambda表达式作为方法的参数
*/
public class LambdaTest2 {
@Test
public void test() {
startThread(() -> System.out.println(">>>lambda表达式实现线程任务执行!"));
startThread(new Runnable() {
@Override
public void run() {
System.out.println(">>>匿名内部类方式实现线程任务执行!");
}
});
}
//定义一个方法startThread,方法的参数使用函数式接口Runnable
private void startThread(Runnable task) {
//开启多线程
new Thread(task).start();
}
}
3.9 函数式接口作为方法的返回值
类似地,如果一个方法的返回值类型
是一个函数式接口,那么就可以直接返回一个Lambda表达式。
例如, 当需要通过一个方法来获取一个java.util.Comparator 接口类型的对象作为排序器时, 就可以调该方法获取。
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
// .....
}
lambda表达式作为方法的返回值使用
/**
* lambda表达式作为方法的返回值
*/
public class LambdaTest3 {
@Test
public void test() {
String[] array = {"aaa", "bbb", "ccc"};
System.out.println(Arrays.toString(array));
// lambda表达式实现 Comparator#compare方法返回值
Arrays.sort(array, (o1, o2) -> o2.length() - o1.length());
System.out.println(Arrays.toString(array));
// 匿名内部类方式实现 Comparator#compare方法返回值
Arrays.sort(array, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.length() - o1.length();
}
});
}
}
4. 方法引用
4.1 介绍
若lambda体中的内容有方法已经实现了,那么可以使用方法引用
, 也可以理解为方法引用
是lambda表达式的另外一种表现形式, 并且其语法比lambda表达式更加简单 . 下面先通过一个简单的案例来说明.
/**
* 方法的基本引用
*/
public class PrintFunctionalTest {
@Test
public void test() {
// 方式1: lambda表达式实现了函数式接口, 进行字符串的输出打印
this.printstring("test print", str -> System.out.println(str));
// 方式2: 方法引用方式实现字符串的输出打印
// 引用方法的参数就是lambda表达式的参数
this.printstring("test print by qout", System.out::println);
}
//定义一个方法,方法的参数传递PrintFunctionInterface接口
private void printstring(String str, PrintFunctionInterface printFunctionInterface) {
printFunctionInterface.printStr(str);
}
// 定义一个函数式接口
@FunctionalInterface
public interface PrintFunctionInterface {
//定义打印字符串的抽象方法
void printStr(String str);
}
}
双冒号::
为引用运算符 , 它所在的表达式被称为方法引用 . 如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号::
来引用该方法作为Lambda表达式的替代者。
语义分析 :
上例中,System.out对象中有一个重载的println(String str)方法恰好就是我们所需要的。 那么对于printstring方法的函数式接口参数,对比下面两种写法,完全等效:
- Lambda表达式写法 str -> system.out.println(str);
- 方法引用写法 System.out : : println
第一种语义是拿到参数之后经Lambda表达式,继而传递给 System.out.println方法去处理。
第二种语义是直接让System.out中的println方法来取代Lambda表达式。
两种写法的执行效果完全一样,而第二种方法引用的写法复用了已有方案,更加简洁。
注意 : Lambda表达式中传递的参数一定是方法引用中的那个方法可以接收的类型 , 否则会抛出异常
4.2 对象名引用成员方法
对象名已经存在,成员方法也已经存在的, 就可以使用对象名来引用成员方法 .
public class PrintFunctionalTest {
@Test
public void test2() {
// 方式1: 通过对象直接调用类的成员方法
this.printString2("test print by qout", str -> {
MethodRerObject methodRerObject = new MethodRerObject();
methodRerObject.printUpperCaseString(str);
});
// 方式2: 使用方法引用来优化lambda, 使用对象名引用成员方法
MethodRerObject methodRerObject = new MethodRerObject();
this.printString2("test print by qout", methodRerObject::printUpperCaseString);
}
//定义一个方法,方法的参数传递PrintFunctionInterface接口
private void printString2(String str, PrintFunctionInterface printFunctionInterface) {
printFunctionInterface.printStr(str);
}
// 定义一个函数式接口
@FunctionalInterface
public interface PrintFunctionInterface {
//定义打印字符串的抽象方法
void printStr(String str);
}
// 定义一个类,提供一个字符串输出方法
class MethodRerObject {
//定义一个成员方法,传递字符串,把字符串按照大写输出
public void printUpperCaseString(String str) {
System.out.println(str.toUpperCase(Locale.ROOT));
}
}
}
上面自定义的函数式接口PrintFunctionInterface
是消费型接口 , 可以直接使用JDK8自带的Consumer
接口, 调用里面accept
抽象方法消费数据即可.
@Test
public void test3() {
MethodRerObject methodRerObject = new MethodRerObject();
this.printString3("test print by qout", methodRerObject::printUpperCaseString);
}
//定义一个方法,方法的参数传递Consumer接口, 也就是消费数据
private void printString3(String str, Consumer<String> consumer) {
consumer.accept(str);
}
4.3 类名引用成员方法
当lambda表达式的实现内容有两个相同类型的参数 , 且一个参数对象调用成员方法的参数列表是另外一个参数, 那么可以使用类名引用成员方法来简化代码.
@Test
public void test4() {
// 方式1: 参数对象进行调用
boolean b = this.equalsStr("hello", "world",
(str1, str2) -> !StringUtils.isEmpty(str1) && str1.equals(str2));
// 方式2: 方法引用-类名::实例方法名
b = this.equalsStr("hello", "world", String::equals);
System.out.println(b);
}
// 定义一个成员方法, 比较两个字符串是否相等, 第三个参数传递一个BiFunction函数
private boolean equalsStr(String str1, String str2, BiFunction<String, String, Boolean> fun) {
return fun.apply(str1, str2);
}
4.4 类名引用静态方法
类已经存在,静态成员方法也已经存在,就可以通过类名直接引用静态成员方法 .
/**
* 类名引用静态方法
*/
public class StaticMethodReferenceTest {
@Test
public void test() {
//方式1: 调用method方法,传递计算绝对值的整数和Lambda表达式
int number = method(-10, num -> Math.abs(num));
System.out.println(number);
//方式2: 使用方法引用优化Lambda表达式, 通过类名引用静态方法
number = method(-10, Math::abs);
System.out.println(number);
}
//定义一个方法,方法的参数传递要计算绝对值的整数和函数式接口Calcable
public int method(int num, Calcable c) {
return c.calsAbs(num);
}
@FunctionalInterface
public interface Calcable {
//定义一个抽象方法,传递一个整数,对整数进行绝对值计算并返回
int calsAbs(int num);
}
}
案例2
@Test
public void test2() {
Integer number = method2(100, 200, (num1, num2) -> Integer.compare(num1, num2));
System.out.println(number);
number = method2(200, 100, Integer::compare);
System.out.println(number);
}
// 定义一个方法, 消费参数num1和num2, 并返回消费结果
private Integer method2(Integer num1, Integer num2, BiFunction<Integer, Integer, Integer> biFunction) {
return biFunction.apply(num1, num2);
}
4.5 super引用父类成员方法
因为有子父类关系,所以存在一个关键字super,代表父类,所以直接使用Super调用父类的成员方法.
定义父类
public class Parent {
void sayHello() {
System.out.println("hello, i'm parent");
}
}
定义子类
/**
* super引用父类成员方法
*/
public class SuperReferenceTest extends Parent {
@Test
public void test() {
//调用method方法,方法的参数Greetable是一个函数式接口,所以可传递Lambda
greet(() -> {
Parent parent = new Parent();
parent.sayHello();
});
// 直接使用super调用
greet(() -> {
super.sayHello();
});
// supper优化lambda表达式, 方法引用
greet(super::sayHello);
}
//定义一个方法参数传递Greetable接口
public void greet(Greetable greetable) {
greetable.greet();
}
@Override
void sayHello() {
System.out.println("hello, i'm children");
}
// 定义打招呼的函数式接口
@FunctionalInterface
interface Greetable {
void greet();
}
}
4.6 this引用本类成员方法
使用this引用本类的成员方法
/**
* super引用父类成员方法
*/
public class SuperReferenceTest extends Parent {
@Test
public void test2() {
// 方式1
greet(() -> {
SuperReferenceTest children = new SuperReferenceTest();
children.sayHello();
});
// 方式2
greet(() -> this.sayHello());
// 方式三 this引用
greet(this::sayHello);
}
//定义一个方法参数传递Greetable接口
public void greet(Greetable greetable) {
greetable.greet();
}
@Override
void sayHello() {
System.out.println("hello, i'm children");
}
// 定义打招呼的函数式接口
@FunctionalInterface
interface Greetable {
void greet();
}
}
4.7 类的构造器引用
格式:ClassName::new
定义一个创建Person对象的函数式接口 , 创建Person类.
有参构造器方法的引用
/**
* 构造器的方法引用
*/
public class ConstructRefrenceTest {
/**
* 有参构造方法的引用
*/
@Test
public void test() {
// 方式1: lambda表达式, 自己new对象实例化, 传入name参数
this.printName("crysw", name -> new Person(name));
// 方式2: 传入的name参数就是构造方法的参数, 可以使用类的构造器引用来替换lambda表达式实现
this.printName("crysw agin", Person::new);
}
private void printName(String name, PersonBuilder personBuilder) {
personBuilder.builderPerson(name);
System.out.println(name);
}
//定义一个创建Person对象的函数式接口
@FunctionalInterface
interface PersonBuilder {
//定义一个方法,根据传递的姓名,创建Person对象返回
Person builderPerson(String name);
}
// 定义一个Person类
public class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
无参构造方法的引用
/**
* 无参构造方法的引用
*/
@Test
public void test2() {
// 方式1:
Person person = this.getPerson(() -> new Person());
System.out.println(">>" + person);
person = this.getPerson(Person::new);
System.out.println(">>>" + person);
}
private Person getPerson(Supplier<Person> supplier) {
return supplier.get();
}
4.8 数组的构造器引用
格式:Type[]::new
demo1
/**
* 数组的构造器引用
*/
public class ArrayRefrenceTest {
@Test
public void test() {
// 方式1: lambda表达式,传递length参数构造数组对象
int[] array = this.createArray(2, (length) -> new int[length]);
System.out.println(">>" + array.length);
// 方式2: 传递的length参数就是构造数组对象的参数, 可以使用数组的构造器引用
array = this.createArray(2, int[]::new);
System.out.println(">>>" + array.length);
}
//定义一个方法,方法的参数传递创建数组的长度和ArrayBuilder接口
//方法内部根据传递的长度使用ArrayBuilder中的方法创建数组并返回
private int[] createArray(int length, ArrayBuilder arrayBuilder) {
return arrayBuilder.builderArray(length);
}
@FunctionalInterface
interface ArrayBuilder {
//定义一个创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组
int[] builderArray(int length);
}
}
demo2
@Test
public void test2() {
// 方式1: lambda表达式, 通过传递的参数构建String数组
String[] strArr = this.getString(10, integer -> new String[integer]);
System.out.println(">>" + strArr.length);
// 方式2: String数组构造方法的引用实现
strArr = this.getString(20, String[]::new);
System.out.println(">>>" + strArr.length);
}
// 定义一个方式, 参数传入函数接口, 将Integer类型参数转换为一个String[interger]类型返回
private String[] getString(Integer integer, Function<Integer, String[]> function) {
return function.apply(integer);
}
5. Stream流
5.1 介绍
Java8 中添加了一个新的流接口操作的概念, 在 java.util.stream包下面,相当于高级版的 Iterator,它可以通过 Lambda 表达式对集合进行大批量数据操作,或者各种便利、高效的聚合数据操作, 用于解决集合类库既有的弊端。
为什么选择Stream流
在 Java8 之前,主要通过 for 循环或者 Iterator 迭代来重新排序合并数据,又或者通过重新定义 Collections.sorts 的 Comparator 方法来实现,这两种方式对于大数据量系统来说,效率并不是很理想。
Stream 的聚合操作与数据库 SQL 的聚合操作 sorted、filter、map 等类似。我们在应用层就可以高效地实现类似数据库 SQL 的 聚合操作了,而在数据操作方面,Stream 不仅可以通过串行的方式实现数据操作,还可以通过并行的方式处理大批量数据,提高数据 的处理效率。
超类接口 BaseStream
派生出了 IntStream
,LongStream
,Stream
以及 DoubleStream
4个流接口.
public interface IntStream extends BaseStream<、Intege、r, IntStream> {}
public interface LongStream extends BaseStream<Long, LongStream> {}
public interface DoubleStream extends BaseStream<Double, DoubleStream> {}
public interface Stream<T> extends BaseStream<T, Stream<T>> {}
Stream操作的三个步骤 : 创建Stream, 中间操作(过滤, map), 终止操作.
5.2 创建Stream
获取Stream流的方式
-
所有的Collection集合都可以通过默认的
stream方法
获取Stream流 .default Stream stream()
-
所有的Collection集合也可以通过默认的
parallelStream方法
获取Stream流, 将数据拆分并行处理.default Stream<E> parallelStream()
-
Stream接口的
of静态方法
可以获取数组对应的流.static Stream of (T… values)
@Test
public void test2() {
//把集合转换为Stream流
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
// parallelStream 将数据拆分成小块并行处理
Stream<String> parallelStream = list.parallelStream();
Set<String> set = new HashSet<>();
Stream<String> stream1 = set.stream();
Map<String, String> map = new HashMap<>();
//获取键,存储到一个Set集合中
Set<String> keySet = map.keySet();
Stream<String> stream2 = keySet.stream();
//获取值,存储到一个Collection集合中
Collection<String> values = map.values();
Stream<String> stream3 = values.stream();
//获取键值对(键与值的映射关系 entrySet)
Set<Map.Entry<String, String>> entries = map.entrySet();
Stream<Map.Entry<String, String>> stream4 = entries.stream();
//把数组转换为Stream流
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
//可变参数可以传递数组
Integer[] arr = {1, 2, 3, 4, 5};
Stream<Integer> stream5 = Stream.of(arr);
String[] arr2 = {"a", "bb", "ccc"};
Stream<String> stream6 = Stream.of(arr2);
// 4.创建无限流
// 迭代
Stream<Integer> stream7 = Stream.iterate(0, x -> x + 2);
//生成
Stream<Double> stream8 = Stream.generate(() -> Math.random());
}
5.3 中间操作
中间操作会返回一个新的流,一个流可以后面跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤. 然后会返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),仅仅调用到这类方法,并没有真正开始流的遍历, 而是在终结操作开始的时候才真正开始执行。
中间操作又可以分为无状态(Stateless)与有状态(Stateful)操作.
无状态是指元素的处理不受之前元素的影响;
有状态是指该操作只有拿到所有元素之后才能继续下去。
因为 Stream 操作类型非常多,列出常用的:
- map():将流中的元素进行再次加工形成一个新流,流中的每一个元素映射为另外的元素。
- filter(): 返回结果生成新的流中只包含满足筛选条件的数据
- limit():返回指定数量的元素的流。返回的是 Stream 里前面的 n 个元素。
- skip():和 limit()相反,将前几个元素跳过(取出)再返回一个流,如果流中的元素小于或者等于 n,就会返回一个空的流。
- sorted():将流中的元素按照自然排序方式进行排序。
- distinct():将流中的元素去重之后输出。
- peek():对流中每个元素执行操作,并返回一个新的流,返回的流还是包含原来流中的元素。
5.3.1 遍历 forEach
/**
* Stream流中的常用方法 遍历forEach
* void forEach(Consumer<? super T> action);
* 该方法接收一个Consumer接口函数,会将一个流元素交给函数进行处理
* forEach方法,用来遍历流中的数据
* 是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法
*/
@Test
public void test3() {
//获取一个Stream流
Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六", "田七");
//使用Stream流中的方法forEach对Stream流中的数据进行遍历
stream.forEach(System.out::println);
}
5.3.2 过滤 filter
/**
* Stream流中的常用方法filter,用于对Stream流中的数据进行过滤
* Stream filter(Predicate<? super T>predicate);
* filter方法的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤
* Predicate中的抽象方法:boolean test(T t)
*/
@Test
public void test4() {
//创建一个Stream流
Stream<String> stream = Stream.of("张三丰", "张翠山", "赵煜", "杨哲", "刘哈哈", "武屌屌");
//对Stream流中的元素进行filter过滤,只要姓张的人
stream.filter(name -> name.startsWith("张")).forEach(System.out::println);
// 第一个Stream流已经使用完毕,就会关闭了,所以第一个Stream就不能再调用方法了
// java.lang.IllegalStateException: stream has already been operated upon or closed
// System.out.println(stream.count());
}
Stream流属于一个管道流,只能被消费(使用)一次.
第一个Stream流调用完毕,数据就会流转到下一个Stream上, 而这时第一个Stream流已经使用完毕,就会关闭了, 所以第一个Stream就不能再调用方法了. Stream流调用filter方法后返回一个新的Stream流.
5.3.3 映射 map
例如,我们之前使用的元素为圆形,调用map方法就可以映射为菱形 .
/**
* 如果需要将流中的元素映射到另一个流中,可以使用map方法
* Stream map(Function<? super T,? extends R> mapper);
* IntStream mapToInt(ToIntFunction mapper):产生包含这些元素的一个新的流
* 该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流
* Function中的抽象方法 apply(T t)
*/
@Test
public void test5() {
//获取一个String类型的Stream流
Stream<String> stream = Stream.of("1", "2", "3", "4", "5");
//使用map方法,把字符串类型的整数转换(映射)为Integer类型的整数
Stream<Integer> integerStream = stream.map((str) -> Integer.parseInt(str));
//遍历Stream1流
integerStream.forEach(System.out::println);
}
5.3.4 截取流中的元素 limit
/**
* Stream流中常用方法:limit:用于截取流中的元素
* limit方法可以对流进行截取,只取用前n个,
* Stream limit(long maxSize);
* 参数是一个long型,如果集合当前长度大于参数则进行截取,否则不进行操作
* limit是一个延迟方法,只对流的元素进行截取,返回一个新的流,所以可以继续调用Stream流中的其他方法
*/
@Test
public void test7() {
//获取一个Stream流
String[] arr = {"慢羊羊", "喜羊羊", "懒羊羊", "美羊羊", "沸羊羊", "暖羊羊", "快羊羊", "瘦羊羊"};
Stream<String> stream = Stream.of(arr);
Stream<String> limitStream = stream.limit(4);
limitStream.forEachOrdered(System.out::println);
}
5.3.5 跳过元素 skip
/**
* Stream流中的常用方法:skip:用于跳过元素
* 如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流
* Stream ship(long n);
* 如果流的当前长度大于n,则跳过前n个,否则将会得到一个长度为0的空流
*/
@Test
public void test8() {
//获取一个Stream流
String[] arr = {"慢羊羊", "喜羊羊", "懒羊羊", "美羊羊", "沸羊羊", "暖羊羊", "快羊羊", "瘦羊羊"};
Stream<String> stream = Stream.of(arr);
Stream<String> stringStream = stream.skip(4);
stringStream.forEach(System.out::println);
}
5.3.6 去重 distinct
/**
* Stream流中的常用方法 distinct, 返回流中的不同元素, 达到去重的效果
* Stream<T> distinct();
* 需要流中的元素重写hashCode和equals方法
*/
@Test
public void test90() {
String[] arr = {"喜羊羊", "喜羊羊", "懒羊羊", "美羊羊", "喜羊羊", "暖羊羊", "暖羊羊", "暖羊羊"};
Stream<String> stream = Stream.of(arr);
stream.distinct().forEach(System.out::println);
}
5.3.7 排序 sorted
/**
* Stream流中的常用方法 sorted, 排序
* Stream<T> sorted(); 默认排序
* Stream<T> sorted(Comparator<? super T> comparator); 自定义排序
* {@link Comparator#compare}
*/
@Test
public void test91() {
//获取一个String类型的Stream流
Stream<String> stream = Stream.of("3", "1", "2", "5", "4");
// 升序
stream.sorted().forEach(System.out::println);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>");
Stream<String> stream2 = Stream.of("3", "1", "2", "5", "4");
// 降序, 自定义排序
stream2.sorted((a, b) -> b.compareTo(a)).forEach(System.out::println);
}
5.3.8 组合流 concat
/**
* Stream流中的常用方法concat 用于把流组合到一起
* 如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat, 返回一个新的流
* static Stream concat(Stream<? extends T>a,Stream(Stream<? extends T>b)
*/
@Test
public void test9() {
String[] arr = {"慢羊羊", "喜羊羊", "懒羊羊", "美羊羊", "沸羊羊", "暖羊羊", "快羊羊", "瘦羊羊"};
Stream<String> stream = Stream.of("张三丰", "张翠山", "赵煜", "杨哲", "刘哈哈", "武屌屌");
Stream<String> stream1 = Stream.of(arr);
//把以上两个流组合为一个流
Stream<String> concatStream = Stream.concat(stream, stream1);
concatStream.forEach(System.out::println);
}
5.3.9 迭代 iterate
@Test
public void test6() {
Stream.iterate(0, n -> n + 3). // 从0开始, 迭代加3
limit(10). // 返回10个元素
forEach(x -> System.out.print(x + " "));
// 执行结果 0 3 6 9 12 15 18 21 24 27
}
5.4 终止操作
终止操作是指返回最终的结果。一个流只能有一个终止操作,当这个操作执行后,这个流就被使用完毕了,无法再被操作。终止操作的执行才会真正开始流的遍历,并且会生成一个结果。
终止操作又可以分为短路(Short-circuiting)与非短路(Unshort-circuiting)操作.
短路是指遇到某些符合条件的元素就可以得到最终结果.
非短路是指必须处理完所有元素才能得到最终结果.
常用终止操作的方法:
allMatch-检查是否匹配所有元素
anyMatch-检查是否至少匹配一个元素
noneMatch-检查是否没有匹配所有元素
findFirst-返回第一个元素
findAny-返回当前流中的任意元素
count-返回流中元素的总个数
max-返回流中最大值
min-返回流中最小值
5.4.1 统计个数 count
/**
* Stream流中的常用方法,count,用于统计Stream流中元素的个数
* long count();
* count方法是一个终结方法,返回值是一个long类型的整数
* 所以不能再继续调用Stream流中的其他方法
*/
@Test
public void test6() {
//获取一个Stream流
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
Stream<Integer> stream = list.stream();
long count = stream.count();
System.out.println(count);
}
5.4.2 缩减数据 reduce
reduce 的主要作用是把 Stream 元素组合起来。它提供一个起始值, 然后依照运算规则(BinaryOperator) , 和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就可以用reduce实现,如下:
@Test
public void test7() {
// reduce(T identity, BinaryOperator<T> accumulator);
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(sum);
// Optional<T> reduce(BinaryOperator<T> accumulator);
Optional<Integer> sum2 = list.stream()
.reduce(Integer::sum);
System.out.println(sum2.get());
}
字符串拼接, 求和, 计算最大值, 最小值操作.
@Test
public void test4() {
// 字符串连接,有起始值(空字符串), concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
System.out.println(concat);
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MIN_VALUE, Double::min);
System.out.println(minValue);
minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double::min).get();
System.out.println(minValue);
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
System.out.println(sumValue);
// 求和,sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
System.out.println(sumValue);
// 过滤,字符串连接,有起始值(空字符串), concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F")
.filter(x -> x.compareTo("Z") > 0)
.reduce("", String::concat);
System.out.println(concat);
}
5.4.3 收集数据 collect
对数据使用Stream流的方式操作完毕后,可以把流中的数据收集到集合中 .
collect(Collectors.toList()) 返回List集合
@Test
public void test8(){
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
List<Integer> squaresList = numbers.stream()
.map(i -> i * i) // 计算元素平方
.distinct() // 去重
.collect(Collectors.toList()); // 聚合元素
System.out.println("Squares List: " + squaresList);
}
**collect(Collectors.joining(“,”) )**聚合数据后加入分隔符, 并转换为字符串类型返回
@Test
public void testJdk8P() {
//数字平方排序(倒叙)输出
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
String squaresList = numbers.stream()
.map(i -> i * i) // 获取对应的平方数
.sorted((x, y) -> y - x) // 降序排序
.map(x -> String.valueOf(x)) // 集合元素由Integer转换为String
.collect(Collectors.joining(","));// 聚合数据且加入分隔符, 返回String类型结果集
System.out.println(squaresList); // 49,25,9,9,9,4,4
}
collect(Collectors.toMap(Function keyMapper, Function valueMapper)) 将List转换成Map, keyMapper的lambda实现对应Map的key, valueMapper的lambda实现对应Map的value
@Test
public void testJdk8P2() {
//字符串转 map 输出
List<String> strList = Arrays.asList("a", "ba", "bb", "abc", "cbb", "bba", "cab");
Map<Integer, String> strMap =
strList.stream().collect(Collectors.toMap(
// key-value key=索引 value=str
str -> strList.indexOf(str), // key=index
str -> str) // value=str
);
strMap.forEach((key, value) -> {
System.out.println(key + ":" + value);
/** 执行结果
0:a
1:ba
2:bb
3:abc
4:cbb
*/
});
}
5.5 JDK7与JDK8实现对比
JDK7实现
@Test
public void testJdk7() {
System.out.println("使用 Java 7: ");
// 计算空字符串
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
System.out.println("列表: " + strings);
long count = getCountEmptyStringUsingJava7(strings);
System.out.println("空字符数量为: " + count);
// 计算字符串长度为3的个数
count = getCountLength3UsingJava7(strings);
System.out.println("字符串长度为 3 的数量为: " + count);
// 删除空字符串
List<String> filtered = deleteEmptyStringsUsingJava7(strings);
System.out.println("筛选后的列表: " + filtered);
// 删除空字符串,并使用逗号把它们合并起来
String mergedString = getMergedStringUsingJava7(strings, ", ");
System.out.println("合并字符串: " + mergedString);
// 获取列表元素平方数
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
List<Integer> squaresList = getSquares(numbers);
System.out.println("平方数列表: " + squaresList);
List<Integer> integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19);
System.out.println("列表: " + integers);
System.out.println("列表中最大的数 : " + getMax(integers));
System.out.println("列表中最大的数 : " + getMax2(integers));
System.out.println("列表中最小的数 : " + getMin(integers));
System.out.println("所有数之和 : " + getSum(integers));
System.out.println("平均数 : " + getAverage(integers));
System.out.println("随机数: " + new Random().nextInt());
}
// 计算空字符串的个数
private static int getCountEmptyStringUsingJava7(List<String> strings) {
int count = 0;
for (String string : strings) {
if (string.isEmpty()) {
count++;
}
}
return count;
}
// 计算字符串长度为3的个数
private static int getCountLength3UsingJava7(List<String> strings) {
int count = 0;
for (String string : strings) {
if (!StringUtils.isEmpty(string) && Objects.equals(3, string.length())) {
count++;
}
}
return count;
}
// 删除空字符串
private List<String> deleteEmptyStringsUsingJava7(List<String> strings) {
List<String> resultList = new ArrayList<>();
for (String string : strings) {
if (!StringUtils.isEmpty(string)) {
resultList.add(string);
}
}
return resultList;
}
// 删除空字符串,并使用分隔符(逗号)把它们合并起来
private String getMergedStringUsingJava7(List<String> strings, String delimiter) {
List<String> resultList = deleteEmptyStringsUsingJava7(strings);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < resultList.size(); i++) {
if (i == resultList.size() - 1) {
sb.append(resultList.get(i));
} else {
sb.append(resultList.get(i)).append(delimiter);
}
}
return sb.toString();
}
// 获取列表元素平方数
private List<Integer> getSquares(List<Integer> numbers) {
List<Integer> resultList = new ArrayList<>();
for (int i = 0; i < numbers.size(); i++) {
Integer num = numbers.get(i);
resultList.add(num * num);
}
return resultList;
}
private Integer getMax(List<Integer> integers) {
Integer max = integers.get(0);
for (int i = 0; i < integers.size(); i++) {
Integer num = integers.get(i);
if (num > max) {
max = num;
}
}
return max;
}
private Integer getMax2(List<Integer> integers) {
// 先集合元素升序排序, 然后取最大值
Collections.sort(integers, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});
return integers.get(integers.size() - 1);
}
private Integer getMin(List<Integer> integers) {
Integer min = integers.get(0);
for (int i = 0; i < integers.size(); i++) {
Integer num = integers.get(i);
if (num < min) {
min = num;
}
}
return min;
}
// 集合元素求和
private Integer getSum(List<Integer> integers) {
Integer sum = 0;
for (int i = 0; i < integers.size(); i++) {
Integer num = integers.get(i);
sum += num;
}
return sum;
}
// 集合元素计算平均数
private double getAverage(List<Integer> integers) {
double average = getSum(integers) * 1.00 / integers.size();
return average;
}
JDK8实现
@Test
public void testJdk8() {
System.out.println("使用 Java 8: ");
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
System.out.println("列表: " + strings);
long count = strings.stream().filter(str -> StringUtils.isEmpty(str)).count();
System.out.println("空字符串数量:" + count);
count = strings.stream().filter(str -> !StringUtils.isEmpty(str) && Objects.equals(3, str.length())).count();
System.out.println("字符串长度为 3 的数量为: " + count);
List<String> filtered = strings.stream().filter(str -> !StringUtils.isEmpty(str)).collect(Collectors.toList());
System.out.println("筛选后的列表: " + filtered);
String mergedString = strings.stream().filter(str -> !StringUtils.isEmpty(str)).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
List<Integer> squaresList = numbers.stream()
.map(i -> i * i)
.distinct()
.collect(Collectors.toList());
System.out.println("Squares List: " + squaresList);
List<Integer> integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19);
System.out.println("列表: " + integers);
IntSummaryStatistics stats = integers.stream().mapToInt(x -> x).summaryStatistics();
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());
System.out.println("随机数: ");
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
// 并行处理
count = strings.parallelStream().filter(str -> StringUtils.isEmpty(str)).count();
System.out.println("空字符串的数量为: " + count);
}
5.6 综合练习
文件流读取文件的文本内容, 然后转换成Stream流进行一些列操作.
@Test
public void test5() throws IOException {
// 找出最长一行的长度
BufferedReader br = new BufferedReader(new FileReader("c:\\SUService.log"));
int longest = br.lines()
.mapToInt(String::length) // 读取每行的字符长度
.max() // 最大长度
.getAsInt(); // 转换为int返回
br.close();
System.out.println(longest);
// 找出全文的单词,转小写,并排序
List<String> words = br.lines().
flatMap(line -> Stream.of(line.split(" "))) // 每行分割单词
.filter(word -> word.length() > 0) // 去除空格
.map(String::toLowerCase) // 单词转大写
.distinct() // 去重
.sorted() // 默认升序排序
.collect(Collectors.toList()); // 收集数据
br.close();
System.out.println(words);
}
6. 接口中的默认方法和静态方法
Java 8使用两个新概念扩展了接口的含义:默认方法和静态方法。 默认方法
让开发者可以在不破坏二进制兼容性的前提下,往现存接口中添加新的方法,不用强制那些已经实现了该接口的类也同时实现这个新加的方法。
6.1 默认方法
默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下:
private interface Defaulable {
// 默认方法
default String notRequired() {
return "Default implementation";
}
}
// 继承接口的默认方法
private static class DefaultableImpl implements Defaulable {
}
// 重写接口的默认方法
private static class OverridableImpl implements Defaulable {
@Override
public String notRequired() {
return "Overridden implementation";
}
}
Defaulable接口使用关键字default定义了一个默认方法notRequired()。DefaultableImpl类实现了这个接口,同时默认继承了这个接口中的默认方法;OverridableImpl类也实现了这个接口,但覆写了该接口的默认方法,并提供了一个不同的实现。
6.2 静态方法
Java 8带来的另一个有趣的特性是在接口中可以定义静态方法,例子代码如下:
private interface DefaulableFactory {
// 静态方法
static Defaulable create( Supplier< Defaulable > supplier ) {
return supplier.get();
}
}
6.3 最佳实践
下面的代码片段整合了默认方法和静态方法的使用场景:
/**
* 接口中的默认方法和静态方法
*/
public class DefaultNStaticInterfaceTest {
@Test
public void test() {
Defaulable defaulable = DefaulableFactory.create(DefaultableImpl::new);
System.out.println(defaulable.notRequired());
defaulable = DefaulableFactory.create(OverridableImpl::new);
System.out.println(defaulable.notRequired());
}
private interface DefaulableFactory {
// 静态方法
static Defaulable create(Supplier<Defaulable> supplier) {
return supplier.get();
}
}
private interface Defaulable {
// 默认方法
default String notRequired() {
return "Default implementation";
}
}
// 继承接口的默认方法
private static class DefaultableImpl implements Defaulable {
}
// 重写接口的默认方法
private static class OverridableImpl implements Defaulable {
@Override
public String notRequired() {
return "Overridden implementation";
}
}
}
尽管默认方法有这么多好处,但在实际开发中应该谨慎使用. 在复杂的继承体系中,默认方法可能引起歧义和编译错误。如果你想了解更多细节,可以参考官方文档。
7. 新的日期时间API
7.1 LocalDateTime
LocalDateTime
是一个不可变的日期时间对象,代表日期时间,通常被视为年 - 月 - 日 - 时 - 分 - 秒。 也可以访问其他日期和时间字段,例如日期,星期几和星期。 时间表示为纳秒精度。 例如,值“2007年10月2日在13:45.30.123456789”可以存储在LocalDateTime 。
获取当前的日期时间
LocalDateTime date = LocalDateTime.now();
System.out.println("当前时间: " + date);
System.out.println(date.getYear());
System.out.println(date.getMonthValue());
System.out.println(date.getDayOfMonth());
System.out.println(date.getHour());
System.out.println(date.getMinute());
System.out.println(date.getSecond());
System.out.println(date.getNano());
手动创建一个LocalDateTime
实例
LocalDateTime date2 = LocalDateTime.of(2017, 12, 17, 9, 31, 31, 31);
//LocalDateTime date3 = date2.withMonth(10).withDayOfMonth(10).withYear(2012);
System.out.println(date2);
// 进行加操作,得到新的日期实例
LocalDateTime date3 = date2.plusDays(12);
System.out.println(date3);
// 进行减操作,得到新的日期实例
LocalDateTime date4 = date3.minusYears(2);
System.out.println(date4);
7.2 LocalDate
LocalDate
是一个不可变的日期对象,表示日期,通常被视为年月日。 也可以访问其他日期字段,例如日期,星期几和星期。 例如,值“2007年10月2日”可存储在LocalDate 。
创建LocalDate
实例
// 12 december 2014
LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
System.out.println("date3: " + date3);
// 解析日期字符串
LocalDate parseDate = LocalDate.parse("2014-12-12");
System.out.println("parseDate: " + parseDate);
7.3 LocalTime
LocalTime
是一个不可变的时间对象,代表一个时间,通常被看作是小时 - 秒。 时间表示为纳秒精度。
创建LocalTime
实例
// 22 小时 15 分钟
LocalTime date4 = LocalTime.of(22, 15);
System.out.println("date4: " + date4);
// 解析时间字符串
LocalTime date5 = LocalTime.parse("20:15:30");
System.out.println("date5: " + date5);
7.4 ZonedDateTime
ZonedDateTime
是具有时区的日期时间的不可变表示。 此类存储所有日期和时间字段,精度为纳秒,时区为区域偏移量,用于处理模糊的本地日期时间。
创建ZonedDateTime
对象
// 获取当前时间日期
ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
System.out.println("date1: " + date1); // date1: 2015-12-03T10:15:30+08:00[Asia/Shanghai]
// 获取时区
System.out.println(date1.getZone().getId()); // Asia/Shanghai
ZoneId id = ZoneId.of("Europe/Paris");
System.out.println("ZoneId: " + id); // Europe/Paris
7.5 Instant
Instant表示在时间线上建立单个瞬时点。 这可能用于在应用程序中记录事件时间戳。
// 时间戳 1970年1月1日00:00:00 到某一个时间点的毫秒值
// 默认获取UTC时区
Instant ins = Instant.now();
System.out.println(ins);
// 下面两个等价
long x = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
System.out.println(x);
long x1 = System.currentTimeMillis();
System.out.println(x1);
System.out.println(Instant.now().toEpochMilli());
System.out.println(
Instant.now().atOffset(ZoneOffset.ofHours(8)).toInstant().toEpochMilli()
);
7.6 Duration
Duration用于计算两个时间之间的间隔 .
计算Instance
@Test
public void test6() throws InterruptedException {
// Duration:计算两个时间之间的间隔
Instant ins1 = Instant.now();
TimeUnit.MILLISECONDS.sleep(1000);
Instant ins2 = Instant.now();
Duration dura = Duration.between(ins1, ins2);
System.out.println(dura);
System.out.println(dura.toMillis());
}
计算LocalTime
@Test
public void test7() throws InterruptedException {
// Period:计算两个日期之间的间隔
LocalTime localTime = LocalTime.now();
TimeUnit.MILLISECONDS.sleep(1000);
LocalTime localTime2 = LocalTime.now();
Duration du2 = Duration.between(localTime, localTime2);
System.out.println(du2);
System.out.println(du2.toMillis());
}
计算LocalDateTime
@Test
public void test8() throws InterruptedException {
LocalDateTime localDateTime = LocalDateTime.now();
TimeUnit.MILLISECONDS.sleep(1000);
LocalDateTime localDateTime2 = LocalDateTime.now();
Duration du2 = Duration.between(localDateTime, localDateTime2);
System.out.println(du2);
System.out.println(du2.toMillis());
}
7.7 Period
Period:计算两个日期之间的间隔.
@Test
public void test9() throws InterruptedException {
LocalDate localDate = LocalDate.now();
TimeUnit.MILLISECONDS.sleep(1000);
LocalDate localDate1 = LocalDate.now();
Period p = Period.between(localDate, localDate1);
System.out.println(p);
System.out.println(p.getYears() + "-" + p.getMonths() + "-" + p.getDays());
}
7.8 常用API转换
LocalDate
@Test
public void localDateTest() {
//获取当前日期,只含年月日 固定格式 yyyy-MM-dd 2018-05-04
LocalDate today = LocalDate.now();
// 根据年月日取日期,5月就是5,
LocalDate oldDate = LocalDate.of(2018, 5, 1);
// 根据字符串取:默认格式yyyy-MM-dd,02不能写成2
LocalDate yesteday = LocalDate.parse("2022-05-03");
// 如果不是闰年 传入29号也会报错
LocalDate.parse("2022-02-28");
}
LocalDate常用转化
/**
* 日期转换常用,第一天或者最后一天...
*/
@Test
public void localDateTransferTest() {
//2018-05-04
LocalDate today = LocalDate.now();
// 取本月第1天: 2018-05-01
LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth());
// 取本月第2天:2018-05-02
LocalDate secondDayOfThisMonth = today.withDayOfMonth(2);
// 取本月最后一天,再也不用计算是28,29,30还是31: 2018-05-31
LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth());
// 取下一天:2018-06-01
LocalDate nextDay = lastDayOfThisMonth.plusDays(1);
// 取2018年10月第一个周三 so easy?: 2018-10-03
LocalDate thirdMondayOf2018 = LocalDate.parse("2018-10-01").with(TemporalAdjusters.firstInMonth(DayOfWeek.WEDNESDAY));
}
LocalTime
@Test
public void localTimeTest() {
//16:25:46.448(纳秒值)
LocalTime todayTimeWithMillisTime = LocalTime.now();
//16:28:48 不带纳秒值
LocalTime todayTimeWithNoMillisTime = LocalTime.now().withNano(0);
LocalTime time1 = LocalTime.parse("23:59:59");
}
LocalDateTime
@Test
public void localDateTimeTest() {
//LocalDateTime转化为时间戳 毫秒值
long time1 = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
long time2 = System.currentTimeMillis();
//时间戳转化为localdatetime
DateTimeFormatter df = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss.SSS");
String dateTime = df.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(time1), ZoneId.of("Asia/Shanghai")));
System.out.println(dateTime);
}
Clock
@Test
public void clockTest() {
// Get the system clock as UTC offset
final Clock clock = Clock.systemUTC();
clock.withZone(ZoneId.of("Asia/Shanghai"));
System.out.println(clock.instant());
System.out.println(clock.millis());
System.out.println(clock.getZone().getId());
}
7.9 总结
- 之前使用的java.util.Date月份从0开始,我们一般会+1使用,很不方便,java.time.LocalDate月份和星期都改成了enum.
- java.util.Date和SimpleDateFormat都不是线程安全的,而LocalDate和LocalTime和最基本的String一样,是不变类型,不但线程安全,而且不能修改。
- java.util.Date是一个“万能接口”,它包含日期、时间,还有毫秒数,更加明确需求取舍
- 新接口更好用的原因是考虑到了日期时间的操作,经常发生往前推或往后推几天的情况。用java.util.Date配合Calendar要写好多代码,而且一般的开发人员还不一定能写对。
8. Optional容器
8.1 介绍
Java应用中最常见的bug就是空指针异常NPE。在Java 8之前,Google Guava引入了Optionals类来解决这个空指针异常NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java 8也将Optional加入了官方库。官方资料
Optional仅仅是一个容器:存放T类型的值
或者null
。它提供了方法来避免显式的null检查.
常用的方法介绍:
Optional.of(T t) 创建一个非空Optional实例, 如果传递的参数是null,抛出NullPointerException.
Optional.empty() 创建一个空的Optional实例
Optional.ofNullable(T t) 若参数T t不为null,创建一个非空Optional实例,否则创建一个空实例.
isPresent() 如果Optional实例持有一个非空值 , 返回true ; 否则 返回false.
ifPresent(Consumer<? super T> consumer) 如果存在值,则使用该值调用指定的消费者,否则不执行任何操作。
orElse(T t) 如果Optional实例包含值,返回该值,否则返回默认值T t.
orElseGet(Supplier s) 如果Optional实例包含值,返回该值,否则返回函数接口的lambda表达式实现生成的默认值;
map(Function f) 如果Optional实例有值, 将现有值转换成新的值,否则返回Optional.empty();
flatMap(Function f, , Optional> mapper) 与 map(Function f)类似, 返回值是Optional.
提示:Optional.of(null) 会直接报NullPointerException.
8.2 Optional测试1
@Test
public void test() {
Integer value1 = null;
Integer value2 = new Integer(10);
// Optional.ofNullable - 允许传递为 null 参数
Optional a = Optional.ofNullable(value1);
// Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException (NPE)
Optional b = Optional.of(value2);
System.out.println("求和:" + sum(a, b));
System.out.println(a.toString());
System.out.println(b.toString());
}
private Integer sum(Optional<Integer> a, Optional<Integer> b) {
// Optional.isPresent - 判断值是否存在
System.out.println("第一个参数值存在: " + a.isPresent());
System.out.println("第二个参数值存在: " + b.isPresent());
// Optional.orElse - 如果值存在,返回它,否则返回默认值
Integer value = a.orElse(new Integer(20));
//Optional.get - 获取值,值需要存在
Integer value2 = b.get();
return value + value2;
}
8.3 Optional测试2
@Test
public void test2() {
Optional<String> fullName = Optional.ofNullable("admin");
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? true
Full Name: admin
Hey admin!
8.4 Optional测试3
private void print(String s) {
// 如果参数s构建的Optional实例不为空, 则执行指定的lambda进行消费
Optional.ofNullable(s).ifPresent(System.out::println);
}
private Integer getLength(String s) {
// 如果参数s构建的Optional实例不为空, 则继续转换获取s的长度, 如果为空返回-1
return Optional.ofNullable(s).map(String::length).orElse(-1);
}
@Test
public void test3() {
String strA = "abcd ";
String strB = null;
print(strA); // abcd
print(strB); // 没有操作被执行
System.out.println(getLength(strA)); // 4
System.out.println(getLength(strB)); // -1
// java.util.NoSuchElementException: No value present
System.out.println(Optional.empty().get());
// java.lang.NullPointerException
System.out.println(Optional.of(null));
// java.util.NoSuchElementException: No value present
System.out.println(Optional.ofNullable(null).get());
}
9. 并行流和串行流
在jdk1.8新的stream包中针对集合的操作也提供了并行操作流和串行操作流。并行流就是把内容切割成多个数据块,并且使用多个线程分别处理每个数据块的内容。Stream api中声明可以通过parallel()
与sequential()
方法在并行流和串行流之间进行切换; 集合中的 parallelStream ()方法开启一个并行流处理。jdk1.8并行流使用的是fork/join
框架进行并行操作.
常用流转换方法:
- boolean isParallel():判断一个流是否是并行流,如果是则返回 true,否则返回 false。
- S sequential():基于调用流返回一个顺序流,如果调用流本身就是顺序流,则返回其本身。
- S parallel():基于调用流,返回一个并行流,如果调用流本身就是并行流,则返回其本身。
- S unordered():基于调用流,返回一个无序流。
- S onClose(Runnable closeHandler):返回一个新流,closeHandler 指定了该流的关闭处理程序,当关闭该流时,将调用这个处理程序。
- void close():从 AutoCloseable 继承来的,调用注册关闭处理程序,关闭调用流(很少会被使用到)。
Stream#parallel()
/**
* java8 并行流 parallel()
*/
@Test
public void test2() {
//开始时间
Instant start = Instant.now();
// 并行流计算 累加求和
long reduce = LongStream.rangeClosed(0, 10L).parallel()
.reduce(0, Long::sum);
System.out.println("result=" + reduce);
//结束时间
Instant end = Instant.now();
System.out.println(Duration.between(start, end).getSeconds());
}
List#parallelStream()
@Test
public void test3() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
// 顺序流执行
list.stream().forEach(System.out::print); // 12345
System.out.println("\n");
// 并行流, 多核多线程处理, 打印结果不是顺序打印
list.parallelStream()
.forEach(System.out::print); // 34521
System.out.println("\n");
}
10. Fork/Join
Fork/Join 框架:Java7就出现的框架, 在必要的情况下,将一个大任务拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行汇总( join )。
在一般的线程池中, 如果一个线程正在执行的任务由于某些原因无法继续运行, 那么该线程会处于等待(阻塞)状态.
在fork/join框架中,如果某个子任务由于等待另外一个子任务的完成而无法继续运行. 那么处理该子任务的线程会主动寻找其他尚未运行的子任务来执行. 这种方式减少了线程的等待时间, 提高了性能.。
/**
* 要想使用Fark—Join,类必须继承
* RecursiveAction(无返回值)
* Or
* RecursiveTask(有返回值)
*
*/
public class ForkJoin extends RecursiveTask<Long> {
/**
* 要想使用Fark—Join
* 类必须继承RecursiveAction(无返回值) 或者 RecursiveTask(有返回值)
*/
private static final long serialVersionUID = 23423422L;
private long start;
private long end;
public ForkJoin() {
}
public ForkJoin(long start, long end) {
this.start = start;
this.end = end;
}
// 定义阙值
private static final long THRESHOLD = 10000L;
@Override
protected Long compute() {
if (end - start <= THRESHOLD) {
long sum = 0;
for (long i = start; i < end; i++) {
sum += i;
}
return sum;
} else {
long middle = (end - start) / 2;
ForkJoin left = new ForkJoin(start, middle);
//拆分子任务,压入线程队列
left.fork();
ForkJoin right = new ForkJoin(middle + 1, end);
right.fork();
//合并并返回
return left.join() + right.join();
}
}
}
测试
/**
* 实现数的累加
*/
@Test
public void test1() {
//开始时间
Instant start = Instant.now();
//这里需要一个线程池的支持
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoin(0L, 10000000000L);
// 没有返回值
pool.execute(task);
// 有返回值
// long sum = pool.invoke(task);
// System.out.println("sum=" + sum);
//结束时间
Instant end = Instant.now();
System.out.println(Duration.between(start, end).get(ChronoUnit.SECONDS));
}
over.