一、函数编程(lambda 表达式)
1.1 简介
面向对象编程是对数据进行抽象;函数式编程是对行为进行抽象。
核心思想: 使用不可变值和函数,函数对一个值进行处理,映射成另一个值。
对核心类库的改进主要包括集合类的API和新引入的流Stream。
流使程序员可以站在更高的抽象层次上对集合进行操作。
1.2 lambda 表达式
1.2.1 为什么使用 lambda 表达式
使用 Lambda 表达式可以对一个接口的方法进行非常简洁的实现
1.2.2 基本语法
(parameters) -> expression
或
(parameters) -> { statements; }
():用来描述参数列表
{}:用来描述方法体 有时可以省略
->: Lambda 运算符 读作goes to
1.2.3 语法实例
public class LambdaTest {
public static void main(String[] args) {
// 无参无返回
LambdaNoneReturnNoneParmeter lambda1=()->{
System.out.println("hello word");
};
lambda1.test();
// 无返回值 单个参数
LambdaNoneReturnSingleParmeter lambda2=(int n)->{
System.out.println("参数是:"+n);
};
lambda2.test(10);
// -> 2. 参数小括号精简 ,如果参数列表中,参数的数量只有一个 此时小括号可以省略
LambdaNoneReturnSingleParmeter lambda22= n->{
System.out.println("精简小括号后->参数是:" + n);
};
lambda22.test(10);
// -> 3. 方法大括号精简 ,如果方法体中只有一条语句,此时大括号可以省略
// -> 如果方法体中唯一的一条语句是一个返回语句,则省略大括号的同时 也必须省略return
LambdaNoneReturnSingleParmeter lambda23 = n -> System.out.println("方法大括号精简后->参数是:" + n);
lambda23.test(10);
// 无返回值 多个参数
LambdaNoneReturnMutipleParmeter lambda3=(int a,int b)->{
System.out.println("参数和是:"+(a+b));
};
lambda3.test(10,12);
// -> 1. 参数类型精简 ,由于在接口的抽象方法中,已经定义了参数的数量类型 所以在Lambda表达式中参数的类型可以省略
LambdaNoneReturnMutipleParmeter lambda33=(a,b)-> {
System.out.println("精简 参数类型 后->参数和是:"+(a+b));
};
lambda33.test(10,12);
// 有返回值 无参数
LambdaSingleReturnNoneParmeter lambda4=()->{
System.out.println("lambda4:");
return 100;
};
int ret=lambda4.test();
System.out.println("返回值是:"+ret);
// 有返回值 单个参数
LambdaSingleReturnSingleParmeter lambda5=(int a)->{
return a*2;
};
int ret2= lambda5.test(3);
System.out.println("单个参数,lambda5返回值是:"+ret2);
//有返回值 多个参数
LambdaSingleReturnMutipleParmeter lambda6=(int a,int b)->{
return a+b;
};
int ret3=lambda6.test(12,14);
System.out.println("多个参数,lambda6返回值是:"+ret3);
// -> 4.错参数精简
LambdaSingleReturnMutipleParmeter lambda61=(a,b)->a+b;
System.out.println("精简后->多个参数,lambda6返回值是:"+lambda61.test(12,14));
}
@FunctionalInterface
public interface LambdaNoneReturnNoneParmeter {
void test();
}
//无返回值有单个参数
@FunctionalInterface
public interface LambdaNoneReturnSingleParmeter {
void test(int n);
}
//无返回值 多个参数的接口
@FunctionalInterface
public interface LambdaNoneReturnMutipleParmeter {
void test(int a,int b);
}
//有返回值 无参数接口
@FunctionalInterface
public interface LambdaSingleReturnNoneParmeter {
int test();
}
//有返回值 有单个参数的接口
@FunctionalInterface
public interface LambdaSingleReturnSingleParmeter {
int test(int n);
}
//有返回值 有多个参数的接口
@FunctionalInterface
public interface LambdaSingleReturnMutipleParmeter {
int test(int a,int b);
}
}
@FunctionalInterface 用于标识一个接口是函数式接口
函数式接口 是指有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
二、Stream 流式编程
3.1 简介
流式编程(Stream流)是一种在计算机编程中用于处理数据集合的新型方法。
核心思想是以一种类似于流水线的方式来处理数据,通过一系列的操作来对数据进行筛选、转换、合并和归约,从而实现更简洁、更高效、更可读的代码编写。
3.2 Stream 流的工作图
3.3 stream & parallelStream
每个 Stream 都有两种模式: 顺序执行和并行执行
下面为性能测试对比:
long t0 = System.nanoTime();
//初始化一个范围100万整数流,求能被2整除的数字,toArray()是终点方法
int a[]=IntStream.range(0, 1_000_000).filter(p -> p % 2==0).toArray(); // 顺序流
long t1 = System.nanoTime();
//和上面功能一样,这里是用并行流来计算
int b[]=IntStream.range(0, 1_000_000).parallel().filter(p -> p % 2==0).toArray(); // 并行流
long t2 = System.nanoTime();
//我本机的结果是 serial: 0.08s, parallel 0.06s,证明并行流确实比顺序流快
System.out.printf("serial: %.2fs, parallel %.2fs%n", (t1 - t0) * 1e-9, (t2 - t1) * 1e-9);
3.4 Stream中常用方法
- stream(), parallelStream()
- filter()
- findAny()findFirst()
- sort
- forEach void
- map(), reduce()
- flatMap() - 将多个Stream连接成一个Stream
- collect(Collectors.toList())
- distinct, limit
- count
- min, max, summaryStatistics
3.4.1 Filter & Predicate
过滤(filter) :对Stream流中的数据进行过滤
filter 方法是用来对列表进行过滤操作的。它接受一个 Predicate 参数,根据这个参数定义的条件来过滤列表中的元素。
什么是 predicate 参数:在 Java 中,Predicate 是一个函数式接口,用于表示一个断言(即一个布尔类型的函数)。它接受一个输入参数,并返回一个布尔值结果,通常用于对输入参数进行判断或筛选。
public static void main(String[] args) {
// 声明语言列表的类型为 List<String>
List<String> languages = Arrays.asList("Java", "Scala", "C++", "Haskell", "Lisp");
System.out.println("Languages which start with J :");
languages.stream()
.filter(language -> language.startsWith("J"))
.forEach(
name -> System.out.println(name + " ")
);
System.out.println("Languages which start with J :");
filter(languages, str -> str.startsWith("J"));
System.out.println("Languages which end with a :");
filter(languages, str -> str.endsWith("a"));
System.out.println("Print all languages:");
filter(languages, str -> true);
System.out.println("Print no language:");
filter(languages, str -> false);
System.out.println("Print language whose length is greater than 4:");
filter(languages, str -> str.length() > 4);
// 多个Predicate组合filter
// 可以用and()、or()和xor()逻辑函数来合并Predicate,
// 例如要找到所有以J开始,长度为四个字母的名字,你可以合并两个Predicate并传入
Predicate<String> startsWithJ = (n) -> n.startsWith("J");
Predicate<String> fourLetterLong = (n) -> n.length() == 4;
languages.stream()
.filter(startsWithJ.and(fourLetterLong))
.forEach((n) -> System.out.print("nName, which starts with 'J' and four letter long is -> " + n + "\n"));
}
// 声明泛型方法以确保类型安全
public static <T> void filter(List<T> names, Predicate<T> condition) {
names.stream()
.filter(condition)
.forEach(name -> System.out.println(name + " "));
}
3.4.2 Map&Reduce
Map 和 Reduce 是函数式编程中常用的两种操作,它们通常与集合(如列表、数组)结合使用,用于对集合中的元素进行处理和聚合操作。
Map 操作用于将一个集合中的每个元素映射成另一个元素,通常是根据某种规则进行转换。
Reduce 操作用于将一个集合中的元素进行聚合操作,最终得到一个单一的值。
public static void main(String[] args) {
List<Integer> costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
// 将 Integer 列表转换为 Double 列表
costBeforeTax.stream()
.map(Double::valueOf)
.collect(Collectors.toList());
double bill = costBeforeTax.stream().
map((cost) -> cost * 1.12)
.reduce((sum, cost) -> sum + cost)
.get();
System.out.println("Total : " + bill);
}
3.4.3 Collectors
Collectors 是 Java 8 中引入的一个工具类,位于 java.util.stream 包中,它提供了一系列静态方法,用于在流式操作中对元素进行收集和归约。通过 Collectors 可以将流中的元素收集到各种数据结构中,比如集合、映射、字符串或者其他自定义的数据结构。
Collectors 提供了丰富的功能,可以简化对流的操作,例如将元素收集到列表、集合、映射等数据结构中,进行分组、分区、聚合运算等。
public static void main(String[] args) {
List<String> items = Arrays.asList("apple", "banana", "orange", "apple", "banana", "apple");
/**
* 基本收集器:
* 1. toList():将流中的元素收集到一个列表中。
* 2. toSet():将流中的元素收集到一个集合中。
* 3. toMap():将流中的元素收集到一个映射中。
* 4. joining():连接流中的字符串元素。
*/
// toList
List<String> list = items.stream().collect(Collectors.toList());
System.out.println("List: " + list); // List: [apple, banana, orange, apple, banana, apple]
// toSet
Set<String> set = items.stream().collect(Collectors.toSet());
System.out.println("Set: " + set); // Set: [banana, orange, apple]
// toMap
Map<String, Integer> map = items.stream().collect(Collectors.toMap(Function.identity(), String::length, (item1, item2) -> item1));
System.out.println("Map: " + map); // Map: {orange=6, banana=6, apple=5}
// joining
String joined = items.stream().collect(Collectors.joining(", "));
System.out.println("Joined: " + joined); // Joined: apple, banana, orange, apple, banana, apple
/**
* 聚合运算:
* 1. counting():计算流中元素的个数。
* 2. summarizingInt()、summarizingDouble()、summarizingLong():对流中数值元素进行摘要统计。快速计算流中元素的最小值、最大值、总和以及平均值
* 3. summingInt()、summingDouble()、summingLong():对流中的数值元素求和。
* 4. averagingInt()、averagingDouble()、averagingLong():计算流中数值元素的平均值。
* 5. maxBy()、minBy():根据提供的比较器找出流中的最大或最小元素。
*/
// counting
long count = items.stream().collect(Collectors.counting());
System.out.println("Count: " + count); // Count: 6
// summarizingInt
System.out.println("summarizingInt 开始");
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用 summarizingInt 计算流中元素的最小值、最大值、总和和平均值
IntSummaryStatistics stats = numbers.stream()
.collect(summarizingInt(Integer::intValue));
System.out.println("Min: " + stats.getMin());
System.out.println("Max: " + stats.getMax());
System.out.println("Sum: " + stats.getSum());
System.out.println("Average: " + stats.getAverage());
System.out.println("Count: " + stats.getCount());
System.out.println("summarizingInt 结束");
// summingInt
int totalLength = items.stream().collect(Collectors.summingInt(String::length));
System.out.println("Total Length: " + totalLength); // Total Length: 33
// averagingInt
double averageLength = items.stream().collect(Collectors.averagingInt(String::length));
System.out.println("Average Length: " + averageLength); // Average Length: 5.5
// maxBy
Optional<String> maxItem = items.stream().collect(Collectors.maxBy(Comparator.comparingInt(String::length)));
maxItem.ifPresent(item -> System.out.println("Max Item: " + item)); // Max Item: banana
/**
* 分组与分区:
* 1. groupingBy():根据分类函数对流中的元素进行分组。
* 2. partitioningBy():根据谓词将流中的元素分为两部分。
*/
// groupingBy
Map<Integer, List<String>> groupedByLength = items.stream().collect(Collectors.groupingBy(String::length));
System.out.println("Grouped by Length: " + groupedByLength); // Grouped by Length: {5=[apple, apple, apple], 6=[banana, orange, banana]}
// partitioningBy
Map<Boolean, List<String>> partitioned = items.stream().collect(Collectors.partitioningBy(s -> s.length() > 5));
System.out.println("Partitioned: " + partitioned); // Partitioned: {false=[apple, apple, apple], true=[banana, orange, banana]}
/**
* 归约操作
* reducing():通过累加器将流中的元素归约为一个值。
* collectingAndThen(): 执行收集操作后对结果进行进一步的转换。
* mapping(): 用于在收集过程中对元素进行转换。
*/
// reducing
Optional<String> concatenated = items.stream().collect(Collectors.reducing((s1, s2) -> s1 + ", " + s2));
concatenated.ifPresent(result -> System.out.println("Concatenated: " + result)); // Concatenated: apple, banana, orange, apple, banana, apple
// collectingAndThen
List<String> unmodifiableList = items.stream().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
System.out.println("Unmodifiable List: " + unmodifiableList); // Unmodifiable List: [apple, banana, orange, apple, banana, apple]
// mapping
List<Integer> lengths = items.stream().collect(Collectors.mapping(String::length, Collectors.toList()));
System.out.println("Lengths: " + lengths); // Lengths: [5, 6, 6, 5, 6, 5]
}
3.4.4 flatMap
用于处理嵌套的集合或将一个流中的每个元素映射到一个新的流,然后将这些新流合并成一个单一的流。
public static void main(String[] args) {
List<List<String>> listOfLists = Arrays.asList(
Arrays.asList("a", "b", "c"),
Arrays.asList("d", "e"),
Arrays.asList("f", "g", "h")
);
List<String> flattenedList = listOfLists.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println(flattenedList); // 输出: [a, b, c, d, e, f, g, h]
}
3.4.5 distinct
用于去除流中的重复元素,并返回一个新的流。它基于元素的 equals() 方法来判断是否重复。
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 3, 2, 1);
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(distinctNumbers); // 输出: [1, 2, 3, 4]
}
3.4.6 count
count 是一个终端操作,用于计算流中元素的数量。它返回一个 long 类型的结果,表示流中元素的总数。
public static void main(String[] args) {
List<String> items = Arrays.asList("apple", "banana", "orange", "kiwi", "blueberry");
long count = items.stream()
.filter(item -> item.length() > 5)
.count();
System.out.println(count); // 输出: 3
}
3.4.7 Match
- allMatch(Predicate<? super T> predicate): 检查是否所有元素都匹配给定的谓词。
- anyMatch(Predicate<? super T> predicate): 检查是否至少有一个元素匹配给定的谓词。
- noneMatch(Predicate<? super T> predicate): 检查是否没有元素匹配给定的谓词。
public static void main(String[] args) {
// allMatch 方法用于检查流中的所有元素是否都满足指定的条件。如果所有元素都满足条件,则返回 true,否则返回 false。
List<Integer> numbers1 = Arrays.asList(2, 4, 6, 8, 10);
boolean allEven = numbers1.stream().allMatch(n -> n % 2 == 0);
System.out.println(allEven); // 输出: true
// anyMatch 方法用于检查流中是否有至少一个元素满足指定的条件。如果有任意一个元素满足条件,则返回 true,否则返回 false。
List<Integer> numbers2 = Arrays.asList(1, 2, 3, 4, 5);
boolean anyEven = numbers2.stream().anyMatch(n -> n % 2 == 0);
System.out.println(anyEven); // 输出: true
// noneMatch 方法用于检查流中是否没有元素满足指定的条件。如果没有任何元素满足条件,则返回 true,否则返回 false。
List<Integer> numbers3 = Arrays.asList(1, 3, 5, 7, 9);
boolean noneEven = numbers3.stream().noneMatch(n -> n % 2 == 0);
System.out.println(noneEven); // 输出: true
}
3.4.8 peek
peek 一个中间操作,它可以在流的元素上执行某些操作,而不会改变流的内容。该操作通常用于调试、日志记录或观察流中的元素。
- peek 是一个中间操作,它返回与原始流相同类型的新流。
- peek 操作不会消费流,因此它可以与其他操作链式调用。
- peek 操作只用于调试、日志记录或观察流中的元素,不应修改元素。
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "orange", "kiwi");
fruits.stream()
.peek(fruit -> System.out.println("Processing: " + fruit))
.map(String::toUpperCase)
.forEach(System.out::println);
}
// 运行结果
Processing: apple
APPLE
Processing: banana
BANANA
Processing: orange
ORANGE
Processing: kiwi
KIWI
3.5 内置四大函数接口
- Predicate : 接受一个输入参数,返回一个布尔值结果。
- Function<T, R> : 接受一个输入参数,返回一个结果。
- Supplier : 不接受任何参数,返回一个结果。
- Consumer : 接受一个输入参数,不返回结果。
// 1. Predicate<T> 接口通常用于进行条件判断或过滤操作。它包含一个抽象方法 test,该方法接受一个输入参数并返回一个布尔值。
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // 输出: true
System.out.println(isEven.test(5)); // 输出: false
// 2. Function<T, R> 接口用于接受一个输入参数并返回一个结果。它包含一个抽象方法 apply。
Function<String, Integer> stringLength = s -> s.length();
System.out.println(stringLength.apply("Hello")); // 输出: 5
System.out.println(stringLength.apply("Java")); // 输出: 4
// 3. Supplier<T> 接口不接受任何参数,返回一个结果。它包含一个抽象方法 get。
Supplier<String> supplier = () -> "Hello, World!";
System.out.println(supplier.get()); // 输出: Hello, World!
// 4. Consumer<T> 接口接受一个输入参数,不返回结果。它包含一个抽象方法 accept。
Consumer<String> printer = s -> System.out.println(s);
printer.accept("Hello, World!"); // 输出: Hello, World!
printer.accept("Java"); // 输出: Java