[Java 8] 新特性
新特性
1. Predicate
Predicate
是Java中的一个函数式接口,它代表一个输入参数并返回一个布尔值的函数。它通常用于对集合或流中的元素进行条件判断、筛选和过滤操作。
Predicate
接口定义了一个抽象方法:
boolean test(T t);
该方法接受一个泛型参数 T 的输入参数,并返回一个布尔值。当输入参数满足某个条件时,test()方法返回 true,否则返回 false。
Predicate
接口还提供了一些默认方法,可以用于组合、取反和链式操作,例如:
and(Predicate<? super T> other):
返回一个组合条件,表示当前条件与另一个条件的逻辑与关系。or(Predicate<? super T> other):
返回一个组合条件,表示当前条件与另一个条件的逻辑或关系。negate():
返回当前条件的取反条件。isEqual(Object targetRef):
返回一个判断对象是否与目标对象相等的条件。
下面是一个简单的示例,演示如何使用Predicate对集合进行筛选:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class PredicateExample {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 使用 Predicate 筛选偶数
Predicate<Integer> isEven = number -> number % 2 == 0;
List<Integer> evenNumbers = filter(numbers, isEven);
System.out.println(evenNumbers); // 输出: [2, 4, 6, 8, 10]
}
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> filteredList = new ArrayList<>();
for (T item : list) {
if (predicate.test(item)) {
filteredList.add(item);
}
}
return filteredList;
}
}
2. Consumer
Consumer
是Java中的一个函数式接口,用于表示接受单个输入参数并在执行操作后不返回任何结果的操作。它通常用于对集合或流中的元素进行遍历、消费或修改操作。
Consumer
接口定义了一个抽象方法:
void accept(T t);
该方法接受一个泛型参数 T
的输入参数,并在方法体中执行相应的操作,通常是对输入参数进行处理或消费。
Consumer
接口还提供了一些默认方法,可以用于组合操作,例如:
andThen(Consumer<? super T> after):
返回一个组合操作,表示当前操作执行完后再执行另一个操作。
下面是一个简单的示例,演示如何使用Consumer
对集合进行遍历和消费操作:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class ConsumerExample {
public static void main(String[] args) {
List<String> fruits = List.of("Apple", "Banana", "Orange");
// 使用 Consumer 遍历打印水果名称
Consumer<String> printFruit = fruit -> System.out.println(fruit);
forEach(fruits, printFruit);
// 使用 Consumer 修改水果名称为大写并打印
Consumer<String> toUpperCase = fruit -> System.out.println(fruit.toUpperCase());
forEach(fruits, toUpperCase.andThen(printFruit));
}
public static <T> void forEach(List<T> list, Consumer<T> consumer) {
for (T item : list) {
consumer.accept(item);
}
}
}
在上述示例中,我们定义了两个不同的 Consumer
操作:printFruit
和 toUpperCase
。printFruit
用于打印水果名称,而 toUpperCase
用于将水果名称转换为大写并打印。然后,我们使用 forEach()
方法将这些操作应用于字符串列表 fruits
中的每个元素。
通过使用 Consumer
,我们可以以简洁、可读的方式对集合中的元素进行消费操作,从而实现更灵活和可组合的行为。
3. Supplier
Supplier
是Java中的一个函数式接口,用于表示无参数的函数,它提供一个值或对象的生成器。它通常用于延迟计算或提供默认值的场景。
Supplier
接口定义了一个抽象方法:
T get();
该方法没有参数,返回类型为泛型参数 T
。在方法体中,可以执行相应的逻辑来生成或提供一个值。
下面是一个简单的示例,演示如何使用Supplier
生成随机数和默认值:
import java.util.Random;
import java.util.function.Supplier;
public class SupplierExample {
public static void main(String[] args) {
// 生成随机整数
Supplier<Integer> randomInteger = () -> new Random().nextInt();
System.out.println(randomInteger.get());
// 提供默认字符串
Supplier<String> defaultString = () -> "Default Value";
System.out.println(defaultString.get());
}
}
在上述示例中,我们定义了两个不同的Supplier
:randomInteger
和 defaultString
。randomInteger
使用nextInt()
方法生成一个随机整数,而 defaultString
提供一个默认字符串值。然后,我们通过调用 get() 方法来获取生成的随机数和默认字符串。
Supplier
的一个常见用例是在需要延迟计算的场景中,只有在需要时才会执行计算操作。它还可以与其他函数式接口(如Optional
、Stream
等)结合使用,提供缺省值或生成流的元素。
通过使用 Supplier
,我们可以以简洁、可读的方式生成值或对象,从而实现更灵活和可复用的代码。
4. Function
Function
是Java中的一个函数式接口,它表示一个接受一个参数并产生一个结果的函数。它通常用于对输入进行转换、映射或计算操作。
Function
接口定义了一个抽象方法:
R apply(T t);
该方法接受一个泛型参数 T
的输入参数,并返回一个泛型参数 R
的结果。在方法体中,可以执行相应的逻辑来对输入参数进行转换或计算,并返回结果。
Function
接口还提供了一些默认方法,可以用于组合操作,例如:
andThen(Function<? super T, ? extends R> after)
:返回一个组合操作,表示当前操作执行完后再执行另一个操作。compose(Function<? super V, ? extends T> before)
:返回一个组合操作,表示先执行另一个操作,再执行当前操作。
下面是一个简单的示例,演示如何使用Function
对字符串进行转换和计算操作:
import java.util.function.Function;
public class FunctionExample {
public static void main(String[] args) {
// 转换字符串为大写
Function<String, String> toUpperCase = str -> str.toUpperCase();
System.out.println(toUpperCase.apply("hello")); // 输出: HELLO
// 计算字符串的长度
Function<String, Integer> lengthFunction = str -> str.length();
System.out.println(lengthFunction.apply("hello")); // 输出: 5
// 组合操作:转换为大写后获取长度
Function<String, Integer> composedFunction = toUpperCase.andThen(lengthFunction);
System.out.println(composedFunction.apply("hello")); // 输出: 5
}
}
在上述示例中,我们定义了两个不同的 Function
操作:toUpperCase
和 lengthFunction
。toUpperCase
用于将字符串转换为大写形式,而 lengthFunction
用于获取字符串的长度。然后,我们分别应用这些操作,并展示了如何使用 andThen()
方法将它们组合在一起执行。
通过使用 Function
,我们可以以简洁、可读的方式对输入进行转换或计算操作,并返回结果。它在数据处理、转换、映射等场景中非常有用,可以提供更灵活和可组合的函数式编程能力。
5. BiFunction
BiFunction是Java中的一个函数式接口,它表示了一个接受两个参数并返回一个结果的操作。
BiFunction接口定义如下:
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
// ...
}
其中,T和U是输入参数的类型,R是返回结果的类型。
BiFunction接口定义了一个名为apply的方法,该方法接受两个参数,并返回一个结果。通过实现这个接口,可以定义具有两个输入参数的操作,例如两个数值的加法、两个字符串的拼接等。
下面是一些BiFunction的使用示例:
- 两个数值的加法:
BiFunction<Integer, Integer, Integer> addition = (a, b) -> a + b;
int result = addition.apply(5, 3); // 结果为8
- 两个字符串的拼接:
BiFunction<String, String, String> concatenation = (a, b) -> a + " " + b;
String result = concatenation.apply("Hello", "World"); // 结果为"Hello World"
- 两个对象的比较:
BiFunction<Integer, Integer, Boolean> greaterThan = (a, b) -> a > b;
boolean result = greaterThan.apply(5, 3); // 结果为true
- 自定义操作:
BiFunction<Integer, String, String> customOperation = (num, str) -> num + ": " + str;
String result = customOperation.apply(10, "Hello"); // 结果为"10: Hello"
BiFunction接口还提供了一些默认方法,如andThen和compose,用于将BiFunction与其他函数组合起来形成更复杂的操作。
总而言之,BiFunction是一个用于表示接受两个参数并返回结果的函数式接口。它在函数式编程和Lambda表达式中具有广泛的应用,可以用于各种需要处理两个参数的操作。
6. BinaryOperator
BinaryOperator是Java中的一个函数式接口,它表示了一个接受两个相同类型参数并返回一个相同类型结果的操作。
BinaryOperator接口继承自BiFunction接口,并在其基础上添加了一些默认方法,使得它更适用于处理两个参数的操作。
BinaryOperator的函数签名如下:
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T, T, T> {
// ...
}
其中,T表示参数和结果的类型。
BinaryOperator定义了一个名为apply的方法,该方法接受两个参数,并返回一个结果。这使得它适用于一些需要处理两个参数的操作,例如加法、乘法、取最大值等。
下面是一些BinaryOperator的使用示例:
- 加法操作:
BinaryOperator<Integer> addition = (a, b) -> a + b;
int result = addition.apply(5, 3); // 结果为8
- 乘法操作:
BinaryOperator<Integer> multiplication = (a, b) -> a * b;
int result = multiplication.apply(5, 3); // 结果为15
- 取最大值操作:
BinaryOperator<Integer> max = (a, b) -> a > b ? a : b;
int result = max.apply(5, 3); // 结果为5
- 字符串拼接操作:
BinaryOperator<String> concatenation = (a, b) -> a + " " + b;
String result = concatenation.apply("Hello", "World"); // 结果为"Hello World"
BinaryOperator还提供了一些默认方法,如maxBy和minBy,用于返回两个值中的最大值或最小值,可以根据自定义的比较器进行比较。
BinaryOperator<Integer> maxBy = BinaryOperator.maxBy(Comparator.naturalOrder());
int result = maxBy.apply(5, 3); // 结果为5
总而言之,BinaryOperator是一个用于表示接受两个相同类型参数并返回相同类型结果的函数式接口。它在函数式编程和Lambda表达式中具有广泛的应用,可以用于各种需要处理两个参数的操作。
7. Optional
Optional是Java中的一个类,用于处理可能为空的值。它是Java 8引入的一个容器类,旨在解决空指针异常(NullPointerException)的问题,并提供更清晰、更安全的代码处理方式。
Optional类的主要目的是封装可能为空的值,并提供一些方便的方法来处理这些值。它可以包含一个非空的值,也可以表示一个空值。
以下是Optional类的一些重要特性和用法:
- 创建Optional对象:可以使用静态方法
Optional.of(value)
来创建一个包含非空值的Optional对象,或使用Optional.empty()
创建一个空的Optional对象。
Optional<String> nonEmptyOptional = Optional.of("Hello");
Optional<String> emptyOptional = Optional.empty();
- 检查是否存在值:可以使用
isPresent()
方法检查Optional对象是否包含非空值。
Optional<String> optional = Optional.of("Hello");
if (optional.isPresent()) {
// 值存在,执行相应操作
}
- 获取值:可以使用
get()
方法获取Optional对象中的值,前提是值存在。如果Optional对象为空,调用get()
方法将抛出NoSuchElementException
异常。
Optional<String> optional = Optional.of("Hello");
String value = optional.get();
- 提供默认值:可以使用
orElse(defaultValue)
方法指定一个默认值,在Optional对象为空时返回该默认值。
Optional<String> optional = Optional.empty();
String value = optional.orElse("Default Value");
- 提供默认值(延迟计算):可以使用
orElseGet(supplier)
方法提供一个函数(Supplier接口)来生成默认值。该函数只有在Optional对象为空时才会被调用。
Optional<String> optional = Optional.empty();
String value = optional.orElseGet(() -> generateDefaultValue());
- 抛出异常:可以使用
orElseThrow(exceptionSupplier)
方法在Optional对象为空时抛出指定的异常。
Optional<String> optional = Optional.empty();
String value = optional.orElseThrow(() -> new IllegalArgumentException("Value is missing"));
- 条件操作:可以使用
filter(predicate)
方法对Optional对象中的值进行条件过滤,返回一个新的Optional对象。
Optional<Integer> optional = Optional.of(10);
Optional<Integer> filteredOptional = optional.filter(value -> value > 5);
- 值转换:可以使用
map(mapper)
方法对Optional对象中的值进行转换,返回一个新的Optional对象。
Optional<String> optional = Optional.of("Hello");
Optional<Integer> lengthOptional = optional.map(String::length);
- 链式操作:可以通过链式调用多个Optional对象的方法来进行复杂的操作。
Optional<String> optional = Optional.of("Hello");
Optional<Integer> result = optional.filter(value -> value.length() > 5)
.map(String::length);
Optional类提供了一种更优雅和安全的方式来处理可能为空的值,避免了繁琐的空值检查和空指针异常。它可以与函数式编程和流式操作相结合,提供更简单的操作
8. Comparator
Comparator是Java中的一个接口,用于定义对象的排序方式。它提供了一种比较两个对象的方式,使得我们可以在集合排序、搜索和自定义排序等场景中使用。
Comparator接口的定义如下:
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
// ...
}
其中,T表示要比较的对象的类型。
Comparator接口定义了一个名为compare的方法,该方法接受两个参数,并返回一个整数值,用于表示两个对象的相对顺序关系。根据比较结果,可以确定对象的排序顺序,如返回负数表示o1小于o2,返回正数表示o1大于o2,返回零表示o1等于o2。
下面是一些Comparator的使用示例:
- 对整数排序:
Comparator<Integer> comparator = (a, b) -> a.compareTo(b);
int result = comparator.compare(5, 3); // 结果为1,表示5大于3
- 对字符串排序:
Comparator<String> comparator = (a, b) -> a.compareToIgnoreCase(b);
int result = comparator.compare("Hello", "World"); // 结果为-1,表示"Hello"小于"World"
- 自定义对象的排序:
假设有一个名为Person的类,我们可以使用Comparator来定义基于某个属性的排序规则。
class Person {
private String name;
private int age;
// 构造函数、getter和setter等省略
public static Comparator<Person> ageComparator = Comparator.comparingInt(Person::getAge);
}
List<Person> persons = new ArrayList<>();
// 添加Person对象到persons列表中
persons.sort(Person.ageComparator); // 根据年龄排序persons列表
- 逆序排序:
Comparator<Integer> comparator = (a, b) -> b.compareTo(a);
int result = comparator.compare(5, 3); // 结果为1,表示3大于5,即逆序排序
Comparator接口还提供了一些默认方法,如reversed、thenComparing和nullsFirst/nullsLast等,用于创建更复杂的比较器和处理特殊情况,例如多级排序和处理空值。
总而言之,Comparator是一个用于定义对象排序方式的接口。通过实现该接口,我们可以定义比较两个对象的规则,并在排序、搜索和自定义排序等场景中使用。Comparator在Java集合框架中广泛应用,提供了强大的排序功能和灵活的定制选项。
stream
Java 8引入了Stream API,这是一个强大的功能,用于处理集合数据和执行复杂的数据操作。Stream API允许开发者以一种声明性的方式对数据集合进行操作,这样可以提高代码的可读性和可维护性。
下面是一些Java 8 Stream的关键概念和功能:
-
流(Stream):流是一个来自数据源的元素序列。它可以是集合、数组、I/O通道等。流提供了一种处理数据的抽象方式,可以轻松地进行过滤、映射、排序、聚合等操作。
-
中间操作(Intermediate Operations):中间操作是指在流上执行的操作,它们可以接收一个流并返回一个新的流。常见的中间操作包括过滤、映射、排序、去重等。这些操作并不会立即执行,而是等到终端操作被调用时才会触发执行。
-
终端操作(Terminal Operations):终端操作是指在流上执行的最终操作,它们会产生一个结果或一个副作用。常见的终端操作包括收集结果、计数、查找、循环等。终端操作会触发流的遍历和处理。
-
延迟执行(Lazy Evaluation):Stream API支持延迟执行,即在终端操作被调用之前,中间操作不会立即执行。这种特性可以提高性能,只处理必要的数据。
-
并行处理(Parallel Processing):Stream API支持并行处理,可以在多个线程上同时执行流的操作。通过parallel()方法将顺序流转换为并行流,可以加速处理大量数据的任务。
下面是一个使用Java 8 Stream的示例,假设有一个包含整数的列表,我们想要计算其中所有偶数的平方和:
import java.util.Arrays;
public class StreamExample {
public static void main(String[] args) {
Integer[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int sumOfEvenSquares = Arrays.stream(numbers) // 创建一个流
.filter(n -> n % 2 == 0) // 过滤偶数
.map(n -> n * n) // 映射为平方
.sum(); // 求和
System.out.println("Sum of squares of even numbers: " + sumOfEvenSquares);
}
}
上述示例首先将数组转换为流,然后使用filter
中间操作过滤出偶数,接着使用map
中间操作将每个元素映射为它的平方,最后使用sum
终端操作计算平方和。最终的结果将打印出来。
流操作:无状态和有状态你已经看到了很多的流操作。乍一看流操作简直是灵丹妙药,而且只要在从集合生成流的时候把Stream换成parallelStream就可以实现并行。当然,对于许多应用来说确实是这样,就像前面的那些例子。你可以把一张菜单变成流,用filter选出某一类的菜肴,然后对得到的流做map来对卡路里求和,最后reduce得到菜单的总热量。这个流计算甚至可以并行进行。但这些操作的特性并不相同。它们需要操作的内部状态还是有些问题的。诸如map或filter等操作会从输入流中获取每一个元素,并在输出流中得到0或1个结果。这些操作一般都是无状态的:它们没有内部状态(假设用户提供的Lambda或方法引用没有内部可变状态)。
Java 8 Stream API提供了丰富的功能和操作,可以大大简化对集合数据的处理。下面是一些主要的功能和操作:
-
函数式编程:Stream API基于函数式编程的思想,提供了丰富的函数式操作方法,如过滤、映射、排序、聚合等。这使得代码更具可读性和可维护性,并且可以通过组合不同的操作来构建复杂的数据处理流程。
-
链式调用:Stream API中的操作可以通过链式调用的方式连接起来,形成一个操作流。这样可以避免创建中间集合,减少内存消耗,并且代码更加简洁和易于理解。
-
懒执行:Stream API中的操作是延迟执行的,只有在终端操作(如forEach、collect、reduce等)被调用时,才会触发数据的处理。这种懒执行的特性可以提高性能,避免不必要的计算。
-
并行处理:Stream API支持并行处理,可以将一个流分成多个子流,在多个线程上并行执行操作。这对于处理大量数据集合可以提高处理速度,但需要注意线程安全和并发问题。
-
代码简化:Stream API提供了一些常用的操作方法,如forEach、filter、map、reduce等,可以用更简洁的方式完成常见的数据处理任务。相比传统的循环和条件判断,使用Stream API可以减少样板代码,使代码更加清晰和简洁。
总而言之,Java 8 Stream API提供了一种简洁、灵活且高效的方式来处理集合数据,使得代码更具可读性和可维护性,同时也提高了处理性能。它是Java开发中一个强大而重要的特性。
1. map()
.map()
是Java 8 Stream API中的一个操作方法,用于对流中的每个元素进行映射操作,并将映射结果返回为一个新的流。它接受一个函数式接口(Function
)作为参数,该函数接受流中的元素,并返回映射后的结果。
.map()
方法的语法如下:
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
其中,T
表示流中的元素类型,R
表示映射后的元素类型。mapper
是一个函数接口,它定义了如何将流中的元素映射为另一种类型的对象。
下面是一些使用.map()
方法的示例:
- 将字符串列表中的每个字符串转换为大写形式:
List<String> strings = Arrays.asList("apple", "banana", "orange");
Stream<String> uppercaseStream = strings.stream().map(String::toUpperCase);
在上述示例中,.map(String::toUpperCase)
将每个字符串转换为大写形式,并返回一个新的流。
- 将整数列表中的每个元素乘以2:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> multipliedStream = numbers.stream().map(n -> n * 2);
在上述示例中,.map(n -> n * 2)
将每个整数乘以2,并返回一个新的流。
.map()
方法可以用于对流中的元素进行各种映射操作,例如类型转换、属性提取、计算等。它提供了一种简洁而灵活的方式来对流中的元素进行转换,使得数据处理更加方便和高效。
2. reduce()
.reduce()
是Java 8 Stream API中的一个终端操作方法,用于将流中的元素进行聚合操作,并返回一个聚合结果。它接受一个初始值(identity)和一个二元操作符(accumulator)作为参数,将初始值与流中的元素逐个进行二元操作,最终得到一个聚合结果。
.reduce()
方法的语法如下:
Optional<T> reduce(BinaryOperator<T> accumulator)
其中,T
表示流中的元素类型,accumulator
是一个二元操作符,接受两个相同类型的参数,并返回一个结果。
下面是一个使用.reduce()
方法的示例:
- 求和:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> sum = numbers.stream().reduce((a, b) -> a + b);
在上述示例中,.reduce((a, b) -> a + b)
将流中的元素逐个相加,得到它们的总和。
.reduce()
方法还可以接受一个初始值作为参数,用于处理空流的情况:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int initialValue = 0;
int sum = numbers.stream().reduce(initialValue, (a, b) -> a + b);
在上述示例中,initialValue
是初始值,当流为空时,直接返回初始值。
.reduce()
方法还可以与.map()
方法结合使用,进行更复杂的聚合操作:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sumOfSquares = numbers.stream().map(n -> n * n).reduce(0, (a, b) -> a + b);
在上述示例中,.map(n -> n * n)
将流中的每个元素平方,然后使用.reduce(0, (a, b) -> a + b)
将它们逐个相加,得到它们的平方和。
.reduce()
方法可以用于各种聚合操作,如求和、求最大值、求最小值、字符串连接等。它提供了一种灵活而强大的方式来对流中的元素进行聚合,并返回最终的结果。
求最大值:
Optional<Integer> max=numbers.stream().reduce(Integer::max);
3. 原始类型流特化
Java 8引入了三个原始类型特化流接口来解决这个问题:IntStream、DoubleStream和LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。每个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到最大元素的max。此外还有在必要时再把它们转换回对象流的方法。要记住的是,这些特化的原因并不在于流的复杂性,而是装箱造成的复杂性——即类似int和Integer之间的效率差异。
① 映射到数值流
在Java 8 Stream API中,除了常规的.map()
方法之外,还提供了一些特定类型的映射方法:.mapToInt()
, .mapToDouble()
和 .mapToLong()
。这些方法允许将流中的元素映射为不同的原始类型(int、double、long)。这些方法和前面说的map方法的工作方式一样,只是它们返回的是一个特化流,而不是Stream。
.mapToInt()
:
.mapToInt()
方法将流中的元素映射为int类型,并返回一个IntStream。这在需要将流中的元素转换为整数,并进行整数操作时非常有用。
示例:
List<String> strings = Arrays.asList("1", "2", "3");
IntStream intStream = strings.stream().mapToInt(Integer::parseInt);
System.out.println(intStream.sum()); // 6
在上述示例中,.mapToInt(Integer::parseInt)
将字符串列表中的每个元素转换为int类型,并返回一个IntStream。
.mapToDouble()
:
.mapToDouble()
方法将流中的元素映射为double类型,并返回一个DoubleStream。这在需要将流中的元素转换为浮点数,并进行浮点数操作时非常有用。
示例:
List<Integer> numbers = Arrays.asList(1, 2, 3);
DoubleStream doubleStream = numbers.stream().mapToDouble(Double::valueOf);
在上述示例中,.mapToDouble(Double::valueOf)
将整数列表中的每个元素转换为double类型,并返回一个DoubleStream。
.mapToLong()
:
.mapToLong()
方法将流中的元素映射为long类型,并返回一个LongStream。这在需要将流中的元素转换为长整型,并进行长整型操作时非常有用。
示例:
List<String> strings = Arrays.asList("100", "200", "300");
LongStream longStream = strings.stream().mapToLong(Long::parseLong);
在上述示例中,.mapToLong(Long::parseLong)
将字符串列表中的每个元素转换为long类型,并返回一个LongStream。
这些特定类型的映射方法提供了更高效的映射和操作方式,避免了装箱和拆箱的开销。它们使得在处理需要特定原始类型的数据时更加方便和高效。
IntStream
是Java 8 Stream API中用于操作基本类型int的流。它提供了一系列用于对int元素进行处理和操作的方法。下面是一些常用的IntStream
方法:
-
聚合操作:
sum()
:计算流中所有元素的总和。average()
:计算流中所有元素的平均值。min()
:找到流中的最小值。max()
:找到流中的最大值。
-
转换操作:
map()
:将每个元素映射为另一个int值。mapToDouble()
:将每个元素映射为double值。mapToLong()
:将每个元素映射为long值。
-
过滤和筛选操作:
filter()
:根据指定的条件筛选出满足条件的元素。distinct()
:去除流中的重复元素。
-
排序和匹配操作:
sorted()
:对流中的元素进行排序。allMatch()
:检查是否所有元素都满足给定条件。anyMatch()
:检查是否有任意元素满足给定条件。
-
归约操作:
reduce()
:将流中的元素进行归约操作,例如求和、求积等。collect()
:将流中的元素收集到一个集合或其他数据结构中。
-
循环和打印操作:
forEach()
:对流中的每个元素执行指定的操作。toArray()
:将流中的元素转换为数组。
除了上述方法,IntStream
还提供了其他一些方法来支持更复杂的操作,如映射、分组、分区等。通过这些方法,可以方便地处理和操作基本类型int的数据。
② 转换回对象流
同样,一旦有了数值流,你可能会想把它转换回非特化流。例如,IntStream上的操作只能产生原始整数:IntStream的map操作接受的Lambda必须接受int并返回int(一个IntUnaryOperator)。但是你可能想要生成另一类值,比如Dish。为此,你需要访问Stream接口中定义的那些更广义的操作。要把原始流转换成一般流(每个int都会装箱成一个Integer),可以使用boxed方法,如下所示:
List<String> strings = Arrays.asList("1", "2", "3");
IntStream intStream = strings.stream().mapToInt(Integer::parseInt);//将Stream转成数值流
System.out.println(intStream.sum()); // 6
Stream<Integer> boxed = intStream.boxed();//将数值流转成Stream流
③ 默认值OptionalInt
求和的那个例子很容易,因为它有一个默认值:0。但是,如果你要计算IntStream中的最大元素,就得换个法子了,因为0是错误的结果。如何区分没有元素的流和最大值真的是0的流呢?前面我们介绍了Optional类,这是一个可以表示值存在或不存在的容器。Optional可以用Integer、String等参考类型来参数化。对于三种原始流特化,也分别有一个Optional原始类型特化版本:OptionalInt、OptionalDouble和OptionalLong。
例如,要找到IntStream中的最大元素,可以调用max方法,它会返回一个OptionalInt:
List<String> strings = Arrays.asList("1", "2", "3");
OptionalInt max = strings.stream().mapToInt(Integer::parseInt).max();
现在,如果没有最大值的话,你就可以显式处理OptionalInt去定义一个默认值了:
int max1 = max.orElse(1);//如果没有最大值了显式提供一个最大值
4.用流收集数据
Collectors类的静态工厂方法
5.Spliterator
Spliterator
(Split Iterator)是Java 8中引入的一个用于遍历和拆分元素的接口。它结合了Iterator
和Iterable
的功能,并提供了更强大的能力,特别适用于并行处理和分割数据源。
Spliterator
接口定义了一种用于遍历和拆分元素的抽象方式,用于支持并行处理和并行遍历数据源。它具有以下主要方法:
-
boolean tryAdvance(Consumer<? super T> action)
: 尝试对下一个元素执行给定的操作,如果存在下一个元素,则返回true
,否则返回false
。 -
Spliterator<T> trySplit()
: 将当前Spliterator
拆分为两个子Spliterator
,以支持并行处理。返回一个新的Spliterator
,其中包含当前元素的一部分,或者返回null
,表示无法进一步拆分。 -
long estimateSize()
: 估计剩余元素的数量。 -
int characteristics()
: 返回Spliterator
的特性,以便在处理中了解有关Spliterator
的信息,例如是否支持并行、是否保证元素顺序等。
使用Spliterator
可以轻松处理并行遍历和拆分数据源。它允许将数据源划分为较小的部分,并在多个线程上同时处理这些部分,以提高处理效率。
下面是一个示例,演示了使用Spliterator
进行并行遍历的操作:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Spliterator<Integer> spliterator = numbers.spliterator();
spliterator.forEachRemaining(System.out::println);
在上述示例中,numbers.spliterator()
获取了List
的Spliterator
。然后,通过forEachRemaining()
方法遍历并打印每个元素。
Spliterator
的使用不仅限于集合类,还可以应用于其他数据源,如数组、IO通道等。它为并行处理和拆分数据源提供了一个统一的接口。
总而言之,Spliterator
是Java 8中的一个接口,用于支持并行处理和拆分数据源。它提供了遍历元素、拆分数据源、估计剩余元素数量等功能,是实现并行处理的关键组件之一。
默认方法
Java的默认方法(Default Methods)是Java 8中引入的一项功能,用于在接口中定义具有默认实现的方法。默认方法允许在接口中添加新的方法,而不会破坏现有的实现类。
在Java之前的版本中,接口只能定义抽象方法,即只能声明方法的签名,而没有具体的实现。当需要在接口中添加新方法时,会导致现有的实现类无法编译通过,因为它们没有提供新方法的实现。
为了解决这个问题,Java 8引入了默认方法的概念。默认方法是在接口中定义的具有默认实现的方法。它们使用default
关键字进行修饰,并提供默认的方法体实现。
默认方法的主要特点包括:
-
默认实现:接口中的默认方法提供了一个默认的方法体实现,即使实现类没有提供该方法的具体实现,也能够使用默认方法的默认行为。
-
多继承冲突:默认方法的引入主要是为了解决多继承冲突的问题。当一个类实现多个接口,并且这些接口中有相同的默认方法时,实现类可以通过覆盖方法来解决冲突,或者显式地调用指定接口的方法。
-
可选择性实现:默认方法允许实现类选择是否覆盖默认方法。实现类可以选择保留默认实现,也可以根据自己的需求提供自定义的实现。
下面是一个示例,演示了默认方法的用法:
interface MyInterface {
default void defaultMethod() {
System.out.println("Default method");
}
}
class MyClass implements MyInterface {
// 可选:覆盖默认方法
@Override
public void defaultMethod() {
System.out.println("Custom method");
}
}
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.defaultMethod(); // 输出: Custom method
}
}
在上述示例中,MyInterface
接口定义了一个默认方法defaultMethod()
,它输出"Default method"。MyClass
类实现了MyInterface
接口,并提供了自定义的方法实现,输出"Custom method"。
默认方法在接口的演化和扩展方面提供了更大的灵活性,使得现有的接口能够添加新的方法,而不会破坏已有的实现类。它们在Java中的广泛应用包括集合框架中的迭代器、Iterable接口等。