之前没学到这个知识点,面试被问到了,没有答上来,学习了一下整理了篇文章记录一下 :学习链接
1.概述
只需要关注函数的含义,这个函数可以对数据进行那些操作,不需要关注具体的对象和类。
优点
- 代码简洁,
- 开发快速
- 接近自然语言
- 易于理解 易于"并发编程"
下面给出一个案例
查询未成年作家的评分在70以上的书籍 ,需要进行去重
java
List<Book> bookList = new ArrayList<>();
Set<Book> uniqueBookValues = new HashSet<>();
Set<Author> uniqueAuthorValues = new HashSet<>();
for (Author author : authors) {
if (uniqueAuthorValues.add(author)) {
if (author.getAge() < 18) {
List<Book> books = author.getBooks();
for (Book book : books) {
if (book.getScore() > 70) {
if (uniqueBookValues.add(book)) {
bookList.add(book);
}
}
}
}
}
}
System.out.println(bookList);
java
List<Book> collect = authors.stream()
.distinct()
.filter(author -> author.getAge() < 18)
.map(author -> author.getBooks())
.flatMap(Collection::stream)
.filter(book -> book.getScore() > 70)
.distinct()
.collect(Collectors.toList());
System.out.println(collect);
2.Lambda表达式
2.1 概述
Java 中的 Lambda 表达式是一种简洁的表示匿名函数的方式,它允许你将函数作为方法参数,或者将代码作为数据对待。Lambda 表达式与函数式接口(只有一个抽象方法的接口)一起使用
2.2 基本格式
rust
(参数列表) -> {代码}
案例
1.线程创建
java
// 使用匿名内部类创建线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
}).start();
// 使用Lambda 表达式进行修改
new Thread(()->{
System.out.println("hello");
}).start();
- 使用
Comparator
接口对列表进行排序:
Lambda 表达式:
java
List<String> names = Arrays.asList("Jane", "Bob", "Alice", "Tom");
Collections.sort(names, (a, b) -> a.compareTo(b));
传统匿名内部类:
java
List<String> names = Arrays.asList("Jane", "Bob", "Alice", "Tom");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
2.3 省略规则
- 参数类型 :
如果编译器能够从上下文推断出参数的类型,则可以省略参数类型。
java
// 完整形式
Comparator<String> comparator = (String a, String b) -> a.compareTo(b);
// 省略参数类型
Comparator<String> comparator = (a, b) -> a.compareTo(b);
- 小括号 (当只有一个参数时):
如果 Lambda 表达式只有一个参数,并且其类型可以被省略,那么也可以省略小括号。
java
// 完整形式
Consumer<String> consumer = (String s) -> System.out.println(s);
// 省略参数类型和小括号
Consumer<String> consumer = s -> System.out.println(s);
- 大括号和 return 关键字 (当主体只有一条语句时):
如果 Lambda 表达式的主体只包含一个语句,那么可以省略大括号和return
关键字。对于表达式主体,如果它是一个表达式,那么会隐式返回该表达式的结果。
java
// 完整形式
Function<String, Integer> function = (String s) -> { return s.length(); };
// 省略大括号和 return 关键字
Function<String, Integer> function = s -> s.length();
- 分号 (当主体只有一条语句时):
如果 Lambda 表达式的主体是一个语句块,且只有一条语句,那么可以省略分号。
java
// 完整形式
Runnable runnable = () -> { System.out.println("Hello, World!"); };
// 省略分号
Runnable runnable = () -> System.out.println("Hello, World!");
需要注意的是,如果 Lambda 表达式的主体包含多条语句,或者你需要明确返回一个值(即使是单一表达式),则必须使用大括号,且在返回值的情况下必须使用 return
关键字。
java
// 多条语句需要大括号和分号
Runnable runnable = () -> {
System.out.println("Statement 1");
System.out.println("Statement 2");
};
// 需要明确返回值时需要大括号和 return 关键字
BinaryOperator<Integer> operator = (a, b) -> { return a * b; };
总结来说,Lambda 表达式的省略规则旨在让代码更加简洁,但是在使用时也需要保证代码的可读性不受影响。
Stream流
Java 中的 Stream API 是 Java 8 引入的一个强大的数据处理功能,它允许你以声明式的方式处理数据集合。Stream API 可以极大地简化集合操作,并支持顺序和并行处理。
Stream API 主要提供了以下几类操作:
- 创建 Stream :
可以通过Collection.stream()
或Stream.of()
等方法创建 Stream。 - 中间操作 (Intermediate Operations):
这些操作是惰性的,它们不会立即执行,而是创建一个新的 Stream,当终端操作被触发时才会执行。中间操作包括filter
,map
,flatMap
,sorted
,distinct
,limit
,skip
等。 - 终结操作 (Terminal Operations):
终结操作会触发流的处理,它们会消耗流并产生结果。这些操作包括forEach
,collect
,reduce
,findFirst
,findAny
,allMatch
,noneMatch
,count
,min
,max
等。 - 短路操作 (Short-circuiting Operations):
某些操作,如limit
或findFirst
, 不需要处理整个流就可以返回结果。这些操作在处理无限流时特别有用。
以下是一些使用 Java Stream API 的例子:
创建 Stream:
1.单列集合: 集合对象.stream()
java
List authors = getAuthors();
Stream stream = authors.stream();
2.数组: Arrays.stream(数组)
或者使用 Stream.of
来创建
java
Integer[] arr = {12,3,4,5};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> arr1 = Stream.of(arr);
3.双列集合:转换成单列集合后再创建(用于存储
java
Map<String,Integer> map = new HashMap<>();
map.put("tom",21);
map.put("zeus",19);
map.put("sunly",16);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();
中间操作:
-
filter:
- 用于过滤流中的元素。
- 接受一个
Predicate
函数式接口。 - 返回一个只包含满足条件的元素的新流。
java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
Stream<String> filteredNames = names.stream().filter(name -> name.startsWith("A"));
-
map:
- 用于将流中的每个元素映射到另一个元素。
- 接受一个
Function
函数式接口。 - 返回一个包含映射后元素的新流。
java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
Stream<Integer> nameLengths = names.stream().map(String::length);
-
flatMap:
- 用于将流中的每个元素映射到一个流。
- 接受一个
Function
函数式接口,该接口返回一个流的对象。 - 返回一个包含所有映射流中所有元素的新流。
java
List<List<String>> listOfLists = Arrays.asList(
Arrays.asList("a", "b"),
Arrays.asList("c", "d")
);
Stream<String> flatStream = listOfLists.stream().flatMap(List::stream);
-
distinct:
- 用于去除流中的重复元素。
- 不接受任何参数。
- 返回一个去除重复元素后的新流。
java
Stream<String> distinctElements = Stream.of("a", "b", "a", "c", "b", "d").distinct();
注意:distinct方法是依赖Object的equals方法来判断是否是相同对象的。所以需要注意重写equals 方法。
-
sorted:
- 用于对流中的元素进行排序。
- 可以接受一个
Comparator
函数式接口来定义排序逻辑,也可以不接受参数使用元素的自然顺序。 - 返回一个排序后的新流。
arduino
Stream<String> sortedStream = Stream.of("a", "d", "b", "c").sorted();
注意:如果调用空参的sorted()方法,需要流中的元素是实现了Comparable。
-
peek:
- 主要用于调试目的,在流的每个元素恢复运行时执行提供的操作。
- 接受一个
Consumer
函数式接口。 - 返回一个与原始流具有相同元素的新流。
java
Stream<String> peekStream = Stream.of("a", "b", "c", "d").peek(System.out::println);
-
limit:
- 用于截取流使其最大长度不超过给定数量。
- 接受一个
long
类型的参数作为最大长度。 - 返回一个长度不超过指定最大长度的新流。
java
Stream<String> limitedStream = Stream.of("a", "b", "c", "d").limit(2);
-
skip:
- 用于跳过流中的前N个元素。
- 接受一个
long
类型的参数作为要跳过的元素数量。 - 返回一个排除了前N个元素的新流。
java
Stream<String> skippedStream = Stream.of("a", "b", "c", "d").skip(2);
以上中间操作是构建流水线的基础,可以组合使用它们来实现复杂的数据处理逻辑。记住,这些操作不会立即执行,它们需要一个终结操作,比如 forEach
、collect
、reduce
等来触发执行。
终结操作:
Stream API 的终结操作是那些实际执行流管道并产生结果的操作。终结操作会触发流中所有惰性操作的执行,并且在执行完毕后流将不再可用。以下是一些常见的终结操作:
-
forEach:
- 用于迭代流中的每个元素。
- 接受一个
Consumer
函数式接口。 - 通常用于执行副作用,如打印。
java
Stream<String> stream = Stream.of("a", "b", "c");
stream.forEach(System.out::println); // 打印每个元素
-
collect:
- 用于将流转换成其他形式,如集合或者 Map。
- 接受一个
Collector
实例。 - 非常灵活,是最常用的终端操作之一。
java
List<String> list = Stream.of("a", "b", "c").collect(Collectors.toList()); // 收集到 List
-
toArray:
- 将流转换为数组。
- 可以提供一个生成器函数来指定数组的类型。
java
String[] array = Stream.of("a", "b", "c").toArray(String[]::new); // 转换为数组
-
reduce:
- 将流中的元素组合起来,使用一个二元函数累加器,并返回一个 Optional。
- 有多种重载方法,可以有初始值,也可以没有。
java
Optional<String> concatenated = Stream.of("a", "b", "c").reduce((acc, elem) -> acc + elem); // 组合字符串
concatenated.ifPresent(System.out::println); // res:abc
-
count:
- 返回流中元素的数量。
- 返回一个
long
类型的值。
java
long count = Stream.of("a", "b", "c").count(); // 计算元素数量
-
min 和 max:
- 返回流中的最小或最大元素,基于提供的
Comparator
。 - 返回一个
Optional
类型的值。
- 返回流中的最小或最大元素,基于提供的
java
Optional<String> min = Stream.of("a", "b", "c").min(String::compareTo); // 获取最小值
Optional<String> max = Stream.of("a", "b", "c").max(String::compareTo); // 获取最大值
-
findFirst 和 findAny:
- 返回流中的第一个元素或任意一个元素(对于并行流来说)。
- 返回一个
Optional
类型的值。
java
Optional<String> first = Stream.of("a", "b", "c").findFirst(); // 获取第一个元素
Optional<String> any = Stream.of("a", "b", "c").findAny(); // 获取任意一个元素
-
allMatch , anyMatch , noneMatch:
- 检查流中的元素是否满足给定的谓词。
allMatch
检查是否所有元素都匹配,anyMatch
检查是否至少有一个元素匹配,noneMatch
检查是否没有元素匹配。- 返回一个
boolean
类型的值。
java
boolean allMatch = Stream.of("a", "b", "c").allMatch(s -> s.length() == 1); // 检查所有元素长度是否为1
boolean anyMatch = Stream.of("a", "bb", "c").anyMatch(s -> s.length() > 1); // 检查是否存在元素长度大于1
boolean noneMatch = Stream.of("a", "b", "c").noneMatch(s -> s.length() > 1); // 检查是否没有元素长度大于1
终端操作是流处理的最后一个步骤,一旦执行,流就会被关闭,不能再被使用。这些操作可以生成一个结果,或者像 forEach
这样的操作执行一些副作用。
注意事项
- 惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)
- 流是一次性的(一旦一个流对象经过一个终结操作后。这个流就不能再被使用)
- 不会影响原数据(我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元 素的。这往往也是我们期望的)
Optional
Optional
是 Java 8 引入的一个容器类,用于封装可能为 null
的值。Optional
类的目的是提供一种更好的方法来处理 null
,避免直接使用 null
值,从而减少空指针异常(NullPointerException
)的风险
创建对象
我们一般使用Optional
的静态方法ofNullable
来把数据封装成一个Optional对象。无论传入的参数是否 为null都不会出现问题
java
Author author = getAuthor();
Optional authorOptional = Optional.ofNullable(author);
如果你确定一个对象不是空
的则可以使用Optional的静态方法of
来把数据封装成Optional对象。
java
Author author = new Author();
Optional authorOptional = Optional.of(author);
安全使用
我们获取到一个Optional对象后肯定需要对其中的数据进行使用。这时候我们可以使用其ifPresent
方法 对来消费其中的值。 这个方法会判断其内封装的数据是否为空,不为空时才会执行具体的消费代码。这样使用起来就更加安 全了。 例如,以下写法就优雅的避免了空指针异常。
java
Optional authorOptional = Optional.ofNullable(getAuthor());
authorOptional.ifPresent(author -> System.out.println(author.getName()));
常用方法使用
- empty() :
创建一个空的Optional
对象。
java
Optional<String> emptyOptional = Optional.empty();
System.out.println(emptyOptional.isPresent()); // 输出: false
- of(value) :
创建一个包含给定非null
值的Optional
对象。
java
Optional<String> nonEmptyOptional = Optional.of("Hello");
System.out.println(nonEmptyOptional.isPresent()); // 输出: true
- ofNullable(value) :
创建一个允许包含null
值的Optional
对象。
java
String nullString = null;
Optional<String> nullableOptional = Optional.ofNullable(nullString);
System.out.println(nullableOptional.isPresent()); // 输出: false
- isPresent() :
检查Optional
对象是否包含非null
值。
java
if (nonEmptyOptional.isPresent()) {
System.out.println("Value exists"); // 输出: Value exists
}
- get() :
如果Optional
包含值,则返回该值,否则抛出NoSuchElementException
。
java
String value = nonEmptyOptional.get();
System.out.println(value); // 输出: Hello
- orElse(other) :
如果Optional
包含值,则返回该值,否则返回other
。
java
Optional<Object> empty = Optional.empty();
System.out.println(empty.orElse("default value")); //输出:default value
- orElseGet(supplier) :
如果Optional
包含值,则返回该值,否则返回由Supplier
接口提供的值。
java
String suppliedValue = nullableOptional.orElseGet(() -> "Supplied Value");
System.out.println(suppliedValue); // 输出: Supplied Value
- orElseThrow(exceptionSupplier) :
如果Optional
包含值,则返回该值,否则抛出由Supplier
接口提供的异常。
java
try {
value = nullableOptional.orElseThrow(IllegalStateException::new);
} catch (IllegalStateException e) {
System.out.println(e.getMessage()); // 输出: null
}
- ifPresent(consumer) :
如果Optional
包含值,则执行Consumer
接口的操作。
java
nonEmptyOptional.ifPresent(v -> System.out.println("Value: " + v)); // 输出: Value: Hello
- map(function) :
如果Optional
包含值,则对该值应用Function
接口的函数,然后返回一个包含函数结果的Optional
。
java
Optional<Integer> lengthOptional = nonEmptyOptional.map(String::length);
lengthOptional.ifPresent(length -> System.out.println("Length: " + length)); // 输出: Length: 5
- flatMap(function) :
类似于map
,但要求Function
接口的函数返回Optional
对象。
java
Optional<Optional<String>> nestedOptional = Optional.of(Optional.of("Nested"));
Optional<String> flatOptional = nestedOptional.flatMap(o -> o);
flatOptional.ifPresent(System.out::println); // 输出: Nested
- ifPresentOrElse(consumer, runnable) :
如果Optional
包含值,则执行Consumer
接口的操作,否则执行Runnable
接口的操作(Java 9 及更高版本)。
java
nullableOptional.ifPresentOrElse(
v -> System.out.println("Value: " + v),
() -> System.out.println("No Value") // 输出: No Value
);
- stream() :
如果Optional
包含值,则返回一个单元素流,否则返回一个空流(Java 9 及更高版本)。
java
Stream<String> optionalStream = nonEmptyOptional.stream();
optionalStream.forEach(System.out::println); // 输出: Hello
这些案例展示了 Optional
类中方法的用法,以及如何利用 Optional
来优雅地处理可能为 null
的情况。
Stream进阶
基本数据类型优化
我们之前用到的很多Stream的方法由于都使用了泛型。所以涉及到的参数和返回值都是引用数据类型。 即使我们操作的是整数小数,但是实际用的都是他们的包装类。JDK5中引入的自动装箱和自动拆箱让我 们在使用对应的包装类时就好像使用基本数据类型一样方便。但是你一定要知道装箱和拆箱肯定是要消 耗时间的。虽然这个时间消耗很下。但是在大量的数据不断的重复装箱拆箱的时候,你就不能无视这个 时间损耗了。 所以为了让我们能够对这部分的时间消耗进行优化。Stream还提供了很多专门针对基本数据类型的方 法。 例如:mapToInt,mapToLong,mapToDouble,flatMapToInt,flatMapToDouble等。
java
List<Author> authors = getAuthors();
authors.stream()
.map(author -> author.getAge())
.map(age -> age + 10)
.filter(age->age>18)
.map(age->age+2)
.forEach(System.out::println);
authors.stream()
.mapToInt(author -> author.getAge())
.map(age -> age + 10)
.filter(age->age>18)
.map(age->age+2)
.forEach(System.out::println);
并行流
在 Java 中,Stream API 提供了一种高级迭代数据集合的方式。并行流(Parallel Stream)是 Stream API 的一个特性,它利用多核处理器的优势,将数据集合的操作分配到多个线程上执行,从而实现并行处理。并行流使用 ForkJoinPool
来分割任务并执行它们,通常可以提高大数据集合的处理速度。
并行流的创建
并行流可以通过在一个现有流上调用 parallel()
方法来创建,或者直接从集合的 parallelStream()
方法创建。
java
// 从集合创建并行流
List<String> myList = Arrays.asList("a", "b", "c", "d", "e");
Stream<String> parallelStream = myList.parallelStream();
// 从顺序流创建并行流
Stream<String> sequentialStream = myList.stream();
Stream<String> anotherParallelStream = sequentialStream.parallel();
并行流的操作
并行流支持与顺序流相同的操作,如 filter
, map
, reduce
, collect
等。但是,由于并行流的操作可能在多个线程上同时执行,因此需要注意线程安全和无状态行为。
注意事项
- 当使用并行流时,确保操作是无状态的,并且没有共享的可变状态。
- 对于有副作用的操作(如修改共享变量),并行流可能会导致不确定的结果。
- 对于小数据集,由于线程管理的开销,使用并行流可能不会带来性能提升。
- 在并行流中使用排序操作(如
sorted()
)可能会导致性能下降,因为它需要跨线程的额外协调。 - 使用并行流时,最终操作可能需要合并各个线程的结果,这可能是一个开销较大的过程。
案例
java
public class Test {
public static void main(String[] args) {
// 生成一个大的随机数组
int size = 10_000_0000; // 一亿个元素
int[] numbers = new int[size];
for (int i = 0; i < size; i++) {
numbers[i] = (int) (Math.random() * 100);
}
// 顺序流处理
long startTime = System.currentTimeMillis();
long sumSequential = IntStream.of(numbers).map(x -> x * x).asLongStream().sum();
long endTime = System.currentTimeMillis();
System.out.println("Sequential stream time: " + (endTime - startTime) + "ms"); // 83ms
System.out.println("Sequential stream result: " + sumSequential);
// 并行流处理
startTime = System.currentTimeMillis();
long sumParallel = IntStream.of(numbers).parallel().map(x -> x * x).asLongStream().sum();
endTime = System.currentTimeMillis();
System.out.println("Parallel stream time: " + (endTime - startTime) + "ms"); // 43ms
System.out.println("Parallel stream result: " + sumParallel);
}
}
请注意,输出的顺序在并行流中可能是不确定的,因为各个元素的处理可能会在不同的时间完成。如果需要保持特定的顺序,可以在最终操作中使用像 forEachOrdered
这样的顺序保持操作,或者在流的末端进行排序。然而,这可能会降低并行流的性能优势。
并行流输出顺序不一致&forEachOrdered保持输出顺序案例
java
public class Test {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
System.out.println("Parallel Stream (forEach):");
numbers.parallelStream().forEach(number -> System.out.print(number + " "));
System.out.println("\n\nParallel Stream (forEachOrdered):");
numbers.parallelStream().forEachOrdered(number -> System.out.print(number + " "));
}
}