所在包:
java.util.function.Supplier
概述:
- 泛型接口。
- 用来生产数据,生产数据的类型通过泛型变量指定。
抽象方法:
T get()
- 生产指定类型的数据。
案例:求数组的最大值。
示例代码:
import java.util.function.Supplier;
public class DemoIntArray {
public static void main(String[] args) {
int[] array = { 10, 20, 100, 30, 40, 50 };
printMax(() ‐> {
int max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
}
return max;
});
}
private static void printMax(Supplier supplier) {
int max = supplier.get();
System.out.println(max);
}
}
所在包:
java.util.function.Consumer
概述:
- 泛型接口。
- 用来消费数据,消费的数据类型通过泛型变量指定。
抽象方法:
void accept(T t)
- 消费指定类型的数据t。
示例代码:
import java.util.function.Consumer;
public class Demo09Consumer {
private static void consumeString(Consumer function) {
function.accept("Hello");
}
public static void main(String[] args) {
consumeString(s ‐> System.out.println(s));
consumeString(System.out::println);
}
}
default Consumer
andThen(Consumer super T> after)
- 消费同一个数据的时候,首先做一个操作,然后再做一个操作,实现组合。(一步接一步)
- 示范使用格式: one.andthen(two).accept(" aaa" );
- 等价于:one.accept(" aaa" ); two.accept(" aaa" );
该方法的源码:
default Consumer andThen(Consumer super T> after) {
Objects.requireNonNull(after);
return (T t) ‐> { accept(t); after.accept(t); };
}
示例代码:
import java.util.function.Consumer;
public class DemoConsumer {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
printInfo(s ‐> System.out.print("姓名:" + s.split(",")[0]),
s ‐> System.out.println("。性别:" + s.split(",")[1] + "。"),
array);
}
private static void printInfo(Consumer one, Consumer two, String[] array) {
for (String info : array) {
one.andThen(two).accept(info); // 姓名:迪丽热巴。性别:女。
}
}
}
所在包:
java.util.function.Predicate
概述:
- 对某种类型的数据进行判断,从而得到一个boolean值结果。
- 用来封装判断条件。
抽象方法:
boolean test(T t)
- 执行判断,返回true或false
示例代码:
public class PredicateDemo01 {
public static void main(String[] args){
// 匿名内部类调用
testPredicate(new Predicate() {
@Override
public boolean test(String s) {
return s.length() > 5;
}
});
// 使用lambda表达式调用
testPredicate(str ‐> str.length() > 5);
}
public static void testPredicate(Predicate predicate){
boolean b = predicate.test("HelloWorld");
System.out.println("b = " + b);
}
}
概述:
实现逻辑关系中的 “与”。
- 用法: one.and( two ).test( 参数);
该方法的源码:
default Predicate and(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) ‐> test(t) && other.test(t);
}
概述:
实现逻辑关系中的“或”。
- 用法: one.or( two ).test( 参数);
该方法的源码:
default Predicate or(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) ‐> test(t) || other.test(t);
}
注意:
默认方法 and 和 or 都存在短路问题。
概述:
执行了test方法之后,对结果boolean值进行 “ ! ” 取反。
该方法的源码:
default Predicate negate() {
return (t) ‐> !test(t);
}
该方法使用的注意事项:
一定要在 test 方法调用之前调用 negate 方法,正如 and 和 or 方法一样。
示例代码:
import java.util.function.Predicate;
public class Demo17PredicateNegate {
private static void method(Predicate predicate) {
boolean veryLong = predicate.negate().test("HelloWorld");
System.out.println("字符串很长吗:" + veryLong);
}
public static void main(String[] args) {
method(s ‐> s.length() < 5);
}
}
所在包:
ava.util.function.Function
概述:
接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件,有进有出。
抽象方法:
R apply(T t)
- 将参数t的数据类型从T类型转换为R类型。
示例代码:
public class FunctionDemo01 {
public static void main(String[] args){
// 整数字符串
String str = "123";
// 将字符串转换为整型数据 123
// 使用匿名内部类创建Function接口的实现类对象
Function f = new Function(){
@Override
public Integer apply(String s) {
return Integer.parseInt(s);
}
};
// 调用apply方法进行转换
int num01 = f.apply(str);
System.out.println(num01);
// 使用lambda表达式简化
Function ff = s ‐> Integer.parseInt(s);
int num02 = ff.apply(str);
System.out.println(num02);
// 使用方法引用简化lambda表达式
Function fff = Integer::parseInt;
int num03 = fff.apply(str);
System.out.println(num03);
}
}
概述:
将上一个操作执行的结果作为下一个操作的执行参数。
该方法的源码:
default Function andThen(Function super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) ‐> after.apply(apply(t));
}
示例代码:
public class FunctionDemo02 {
public static void main(String[] args){
// 字符串
String str = "123";
// 操作1:将字符串转换为整型 "123" ==> 123
// String ==> Integer
Function one = Integer::parseInt;
// 操作2:将整型数据乘以10 R apply(T t)
// Integer ==> Integer
Function two = num ‐> num * 10;
// 按顺序执行操作1和操作2
// 将上一个操作执行的结果作为下一个操作的执行参数
int result = one.andThen(two).apply(str);
System.out.println("result = " + result); // 1230
}
}
概述:
将下一个操作执行的结果作为上一个操作的执行参数。(与andThen恰好相反)
该方法的源码:
default Function compose(Function super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
函数式接口当中,方法可以分成两种:
延迟方法:
- 只是在拼接Lambda函数模型的方法,并不立即执行得到结果。
终结方法:
- 根据拼好的Lambda函数模型,立即执行得到结果值的方法。
规律:
通常情况下,这些常用的函数式接口中唯一的抽象方法为终结方法,而默认方法为延迟方法。
接口名称 | 方法名称 | 抽象 / 默认 | 延迟 / 终结 |
Supplier | get | 抽象 | 终结 |
Consumer | accept | 抽象 | 终结 |
andThen | 默认 | 延迟 | |
Predicate | test | 抽象 | 终结 |
and | 默认 | 延迟 | |
or | 默认 | 延迟 | |
negate | 默认 | 延迟 | |
Function | apply | 抽象 | 终结 |
andThen | 默认 | 延迟 | |
compose | 默认 | 延迟 |
概述:
对Java中集合或数组功能进行增强的技术。
使用Stream流操集合和数组:
- 提高编程效率和程序可读性。
- 支持并发方式处理。
Stream流获取方式:
1)单列集合流的获取方式:
调用集合对象的stream方法获取。
2)双列集合流的获取方式:
1)获得键对应的流对象。
- Stream
keyStream = map.keySet().stream(); 2)获得值对应的流对象。
- Stream
valueStream = map.values().stream(); 3)获得Entry对象对应的流对象。
- Stream
> entryStream = map.entrySet().stream();
3)数组流的获取方式
通过Stream接口中的静态方法of方法获得。
// 创建字符串数组
String[] strs = {"a","b"};
// 调用Stream接口的静态方法获得流对象
Stream arrayStream = Stream.of(strs);
Stream arrStream = Stream.of("a","b","c","d");
Stream流常用方法01:
filter:
Stream
filter(Predicate predicate)
- 根据条件过滤流中的元素。将当前流中的满足条件的元素存储到另一个流中。
- Predicate函数式接口的抽象方法:boolean test(T t);
示例代码:
public class StreamDemo01 {
public static void main(String[] args){
// 创建流对象
Stream stream01 = Stream.of("abc", "ancd", "afdsf");
// 将字符串长度大于3的元素存储到另一个流中
Stream stream02 = stream01.filter(str ‐> str.length() > 3);
}
}
Stream流常用方法02:
count:
long count()
- 获得流中元素的个数。
示例代码:
public class StreamDemo01 {
public static void main(String[] args){
// 创建流对象
Stream stream01 = Stream.of("abc", "ancd", "afdsf");
System.out.println(stream01.count());// 3
}
}
Stream流常用方法03:
limit:
Stream
limit(long maxSize)
- 将当前流中的前maxSize个元素存储到另一个流中。
- 如果maxSize大于当前流中的元素个数,则会将所有元素存储到新流中。
- 注意:maxSize必须大于等于0,如果等于0,则会产生一个空流。
示例代码:
public class StreamDemo02 {
public static void main(String[] args){
// 创建流对象
Stream stream01 = Stream.of("abc", "ancd", "afdsf");
Stream stream02 = stream01.limit(0);
System.out.println(stream02.count());
}
}
Stream流常用方法04:
skip:
Stream
skip(long n)
- 将当前流中前n个后面的所有元素存储到另一个流中。
- n必须大于等于0,如果大于当前流中的元素个数,则产生一个空流。
示例代码:
public class StreamDemo03 {
public static void main(String[] args){
// 创建流对象
Stream stream01 = Stream.of("abc", "ancd", "afdsf");
Stream stream02 = stream01.skip(0);
System.out.println(stream02.count());
}
}
Stream流常用方法05:
map:
Stream
map(Function mapper)
- map===> 映射(一个对一个)
- 将流中的元素从数据类型T转换为数据类型R并存储到新流中。
- Function函数式接口抽象方法:R apply(T t)
- 将参数t从数据类型T换行为数据类型R。
示例代码:
public class StreamDemo04 {
public static void main(String[] args){
// 创建Stream流对象
Stream stream01 = Stream.of("123", "456", "789");
// 将流中的所有元素类型从字符串转换为整型并存储到另一个流中
// Stream stream02 = stream01.map(str ‐> Integer.parseInt(str));
Stream stream02 = stream01.map(Integer::parseInt);
// 遍历输出流中的元素
stream02.forEach(System.out::println);
}
}
Stream流常用方法06:
concat:
static
Stream concat(Stream a,Stream b)
- 将流a和流b合并为一个新的流。
示例代码:
public class StreamDemo05 {
public static void main(String[] args){
// 创建两个流对象
Stream streamA = Stream.of("a","b");
Stream streamB = Stream.of("c","d");
// 合并流
Stream streamC = Stream.concat(streamA, streamB);
// 遍历输出streamC中的元素
streamC.forEach(System.out::println);
}
}
Stream流常用方法07:
forEach:
void forEach(Consumer
action)
- 逐一处理流中的元素,将每一个元素传递给消费者处理。
- Consumer函数式接口的抽象方法:void accept(T t) 消费数据t
示例代码:
public class StreamDemo04 {
public static void main(String[] args){
// 创建Stream流对象
Stream stream01 = Stream.of("123", "456", "789");
// 遍历输出流中的元素
stream02.forEach(System.out::println);
}
}
对Stream流常用方法的小结:
- 凡是返回值仍然为 Stream 接口的为非终结方法(函数拼接方法),它们支持链式调用。
- 而返回值不再为 Stream 接口的为终结方法,不再支持链式调用。
方法名 | 方法作用 | 方法种类 | 是否支持链式调用 |
count | 统计个数 | 终结 | 否 |
forEach | 逐一处理 | 终结 | 否 |
filter | 过滤 | 函数拼接 | 是 |
limit | 取用前几个 | 函数拼接 | 是 |
skip | 跳过前几个 | 函数拼接 | 是 |
map | 映射 | 函数拼接 | 是 |
concat | 组合 | 函数拼接 | 是 |
终结方法:
- count
- forEach
- collect
Stream流常用方法使用注意事项:
- 流一旦调用了终结方法就被关闭了,就不能继续调用方法操作流中的元素。
- 如果通过流方法产生了新的流,那么旧的流就不能继续操作了。
否则会报异常:
java.lang.IllegalStateException:stream has already been operated upon or closed
1)收集到List集合:
流对象.collect( Collectors.toList() )
- 获得List集合。
2)收集到Set集合:
流对象.collect( Collectors.toSet() )
- 获得Set集合。
3)收集到数组:
(1)Stream提供 toArray 方法来将结果放到一个数组中,由于泛型擦除的原因,返回值类型是Object[]的:
Object[] toArray();
示例代码:
import java.util.stream.Stream;
public class Demo16StreamArray {
public static void main(String[] args) {
Stream stream = Stream.of("10", "20", "30", "40", "50");
Object[] objArray = stream.toArray();
}
}
(2)使用 toArray 方法的另一种重载形式传递一个 IntFunction 的函数,继而从外面指定泛型参数,从而指定数组类型:
A[ ] toArray(IntFunction generator);
- 抽象方法: A[ ] apply(int value)
示例代码:
import java.util.stream.Stream;
public class Demo17StreamArray {
public static void main(String[] args) {
Stream stream = Stream.of("10", "20", "30", "40", "50");
//String[] StrArray = stream.toArray(invalue -> new String[invalue]);
String[] strArray = stream.toArray(String[]::new);
}
}
Stream流的分类:
1)串行流
- 在当前线程中按顺序执行操作(不开线程)。
2)并行流
- 开启多个线程和当前线程一起执行操作。
- 不需要手动开启线程。获得并行流后,并行流会自动开启线程。
集合返回Stream流:
java.util.Collection
新添加了两个默认方法。
default Stream stream()
- 返回串行流。
default Stream parallelStream()
- 返回并行流。
并行流的获取方式:
1)将串行流转换为并行流
- 通过调用Stream流对象的parallel方法。
2)通过Collection集合获得并行流
- 调用集合对象的parallelStream方法获得。
并行流基本使用的实例代码:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
并行流基本使用
*/
public class StreamDemo02 {
public static void main(String[] args){
// 创建集合对象
List list = new ArrayList<>();
// 添加数字
for (int i = 0; i < 10000; i++) {
list.add(i);
}
// 将集合中的元素转换为字符串并存储到另一个集合中:串行流
/*Stream stream = list.stream().map(num -> {
System.out.println(Thread.currentThread().getName());
return Integer.toString(num);
});*/
// 将集合中的元素转换为字符串并存储到另一个集合中:并行流
Stream stream = list.stream().parallel().map(num -> {
System.out.println(Thread.currentThread().getName());
return Integer.toString(num);
});
// 收集流的结果到List中
List newList = stream.collect(Collectors.toList());
System.out.println(newList);
}
}
如何从stream和parallelStream方法中进行选择:
我们要考虑三个问题:
- 是否需要并行?
- 任务之间是否是独立的?
- 结果是否取决于任务的调用顺序?
对于问题1:
- 当数据量不大时,顺序执行往往比并行执行更快。毕竟,准备线程池和其它相关资源也是需要时间的。但是,当任务涉及到I/O操作并且任务之间不互相依赖时,那么并行化就是一个不错的选择。通常而言,将这类程序并行化之后,执行速度会提升好几个等级。
对于问题2:
- 如果任务之间是独立的,那么就表明代码是可以被并行化的。
对于问题3:
- 由于在并行环境中任务的执行顺序是不确定的,因此对于依赖于顺序的任务而言,并行化也许不能给出正确的结果。
在写代码的时候,我需求是把Integer类型的数组转化成String类型的Stream流。用of方法获得Stream后,然后再用map方法转换Stream流的类型。 但是在map参数用方法块简写Function
却出现了报错。
示例代码如下:
Integer[] arr = {10,20,30,40};
Stream stream = Stream.of(arr).map(a -> Integer.toString(a));
//用Lambda可以编译通过 Integer[] arr = {10,20,30,40};
Stream stream = Stream.of(arr).map(Integer::toString);
//用方法块就不能通过了 提示报的是:
NO compile-time declaration for the method reference is found
:没有找到方法引用的编译时声明
一开始我的猜测是:
- Integer类型的元素传进Function接口的抽象方法apply为: String apply(Integer i)
- toString方法为:String toString(int i)
- 两个方法之间没有发生自动装拆箱,所以参数类型不同,所以不能用方法块。
然后我看源码,看到了有两个方法:
1)Integer的toString方法:
@HotSpotIntrinsicCandidate
public static String toString(int i) {
int size = stringSize(i);
if (COMPACT_STRINGS) {
byte[] buf = new byte[size];
getChars(i, size, buf);
return new String(buf, LATIN1);
} else {
byte[] buf = new byte[size * 2];
StringUTF16.getChars(i, size, buf);
return new String(buf, UTF16);
}
}
2)Object的toString方法
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
- 因为Integer也属于Object类,所以Object的toString方法也有被Integer继承。故Integer有两个toString方法,一个toString方法有一个参数,一个toString方法没有参数。
所以当我们用方法块时:
Stream
stream = Stream.of(arr).map(Integer::toString);
编译器会以为有两种情况:
- 调用Integer的自身的toString方法。(静态代码引用)
- 调用Integer继承Object的toString方法。(特定类型的示例方法引用)
- 而编译器无法识别是哪一种情况,故会报错。所以不能写代码块。