文章目录
1. lambda表达式
java8的新特性,lambda可以作为方法的参数,lambda也可以作为一个返回。
2.可以做什么
简化接口实现
之前我们,可以使用1.使用具名接口实现类;2.匿名实现类
使用lambda,这两种方式都可以用了。
3.使用lambda的要求:
函数式接口(说人话就是接口只有一个抽象方法)
4.满足了函数式接口后,看一下lambda语法
(参数1,参数2)-> {方法体};
说明: 当多个参数的时候,需要用小括号包起来,当方法体是多行时,必须用花括号包起来。
参数1 -> {方法体};
说明: 当只有一个参数的时候,可以省略小括号
参数1 -> 参数1 + 12;
说明: 当方法体之后一句执行语句的时候,可以省略花括号;并且返回值时,可以省略return;
() -> a+b;
说明:当参数为空是,必须有小括号。
5. 方法引用
5.1 静态方法引用
//静态方法引用
public class Test05 {
public static void main(String[] args) {
Test1 test1 = Calculator::calculate; //静态方法引用
System.out.println(test1.test(4,5));
}
}
class Calculator{
public static int calculate(int a,int b ){
// 稍微复杂的逻辑:计算a和b的差值的绝对值
if (a > b) {
return a - b;
}
return b - a;
}
}
5.2 非静态方法引用
// 非静态方法引用
public class Test06 {
public static void main(String[] args) {
//对非静态方法的引用,需要使用对象来完成
Test2 test2 = new Calculator()::calculate;
System.out.println(test2.calculate(2, 3));
}
private static class Calculator{
public int calculate(int a, int b) {
return a > b ? a - b : b - a;
}
}
}
5.3 构造方法的引用
// 如果某⼀个函数式接口中定义的方法,仅仅是为了得到⼀个类的对象。此时我们就可以使用构造方法的引
// 用,简化这个方法的实现。
// 语法:类名::new
public class Test {
private static class Dog{
String name;
int age;
//无参构造
public Dog(){
System.out.println("一个Dog对象通过无参构造被实例化了");
}
//有参构造
public Dog(String name,int age){
System.out.println("一个Dog对象通过有参构造被实例化了");
this.name = name;
this.age = age;
}
}
//定义一个函数式接口,用以获取无参的对象
@FunctionalInterface
private interface GetDog{
//若此方法仅仅是为了获得一个Dog对象,而且通过无参构造去获取一个Dog对象作为返回值
Dog test();
}
//定义一个函数式接口,用以获取有参的对象
@FunctionalInterface
private interface GetDogWithParameter{
//若此方法仅仅是为了获得一个Dog对象,而且通过有参构造去获取一个Dog对象作为返回值
Dog test(String name,int age);
}
// 测试
public static void main(String[] args) {
//lambda表达式实现接口
GetDog lm = Dog::new; //引用到Dog类中的无参构造方法,获取到一个Dog对象
Dog dog = lm.test();
System.out.println("修狗的名字:"+dog.name+" 修狗的年龄:"+dog.age);
//修狗的名字:null 修狗的年龄:0
GetDogWithParameter lm2 = Dog::new;//引用到Dog类中的有参构造,来获取一个Dog对象
Dog dog1 = lm2.test("萨摩耶",2);
System.out.println("修狗的名字:"+dog1.name+" 修狗的年龄:"+dog1.age);
//修狗的名字:萨摩耶 修狗的年龄:2
}
}
6. Lambda与泛型结合之终极使用方案:
/**
* 说明: 我猜大概二毛看不懂了吧!那就对此进行个说明吧
* 首先定义了一个泛型方法processElements()
* 泛型的方法参数都是接口:
* ① Iterable: 可迭代的对象接口
* ② Predicate: 判断条件,返回布尔类型的函数式接口
* ③ Function: 传入一个参数,返回一个结果的函数式接口
* ④ Consumer:消费一个参数,返回值为void的函数式接口
* 程序的作用:给processElements方法传入一个可迭代的对象,一个用来判断的条件函数,一个函数式接口
* 实现,一个消费者函数式接口实现,最终通过遍历,使用传入的条件函数用来做判断,使用
* apply,消费一个参数,返回一个data;最后使用consumer直接消费掉!
*/
public static <X, Y> void processElements(
Iterable<X> source,
Predicate<X> tester,
Function <X, Y> mapper,
Consumer<Y> block) {
for (X p : source) {
if (tester.test(p)) {
Y data = mapper.apply(p);
block.accept(data);
}
}
}
7. jdk8里面规定的一些函数式接口:
- Consumer 消费型接口 ,返回void
- Supplier: 供给型接口,返回T类型
- Function<T,R> 函数型接口,返回R类型
- Predicate 断言型接口 ,返回boolean
8. jdk8对容器的操作 — 流stream:
说明:流是对容器操作的简洁方法,并且使用到了Lambda表达式,它能充分利用多核优势,以及配合Lambda表达式、链式结构对集合进行许多好用的操作。
8.1 创建流stream
8.1.1 从Collection和数组获得
// 1. 通过Collection系列提供stream()串行流和parallelStram()并行流
Collection.stream(); // 转换为串行流
Collection.parallelStream(); //转换为并行流
// 2. 通过Arrays中的静态方法stream()返回数据流
Arrays.stream(T array);
/* 注意:对于基本数据类型,目前提供了对应的包装类型流: IntStream、LongStream、DoubleStream。
当然也可以使用Stream<Integer>、Stream<Long>、Stream<Double>,但是会很耗时,所以特别为这三种提供
了数值型对应的Stream。
*/
8.1.2 Stream接口的静态工厂
// 1. of()方法:其生成的stream是有限长度的,stream的长度为其内的元素个数。
Stream.of(T ...values) //返回含有多个T元素的
Stream.of(T t) //返回含有一个T元素的Stream
// 示例:
Stream<Integer> integerStream = Stream.of(1, 2, 3);
Stream<String> stringStream = Stream.of("A");
// 2.generate()方法: 返回一个无限长度的Stream,其元素由Supplier接口的提供。在Supplier是一个函数式
接口,只封装了一个get()方法,其用来返回任何泛型的值,该结果在不同的时间内,返回的可能是不同的数据
类型,没有特殊的要求。
Stream.generate(Supplier<T> s) // 返回一个无限长度的Stream
/*
这种情形通常用于随机数、常量的 Stream,或者需要前后元素间维持着某种状态信息的 Stream。把 Supplier
实例传递给 Stream.generate() 生成的 Stream,默认是串行(相对 parallel 而言)但无序的(相对ordered
而言)。
*/
Stream<Double> generateA = Stream.generate(new Supplier<Double>() {
@Override
public Double get() {
return java.lang.Math.random();
}
});
Stream<Double> generateB = Stream.generate(()-> java.lang.Math.random());
Stream<Double> generateC = Stream.generate(java.lang.Math::random);
// 3. iterate()方法:
/*
iterate方法,其返回的也是一个无限长度的Stream,与generate方法不同的是,其是通过函数f迭代对给指定的
元素种子而产生无限连续有序Stream,其中包含的元素可以认为是:seed,f(seed),f(f(seed))无限循环。
*/
Stream.iterate(T seed, UnaryOperator<T> f)
// 示例:
Stream.iterate(1, item -> item + 1)
.limit(10)
.forEach(System.out::println);
// 打印结果:1,2,3,4,5,6,7,8,9,10
// 4. empty()方法
// empty方法返回一个空的顺序Stream,该Stream里面不包含元素项。
import java.util.stream.Stream;
public class StreamEmptyExample {
public static void main(String[] args) {
// 创建一个空的Stream
Stream<String> emptyStream = Stream.empty();
// 打印空的Stream
emptyStream.forEach(System.out::println);
}
}
/*
没有任何输出...
*/
// 5. 其他方法
Random.ints()
BitSet.stream()
Pattern.splitAsStream(java.lang.CharSequence)
JarFile.stream()
8.2 流的操作
把一个数据结构包装成Stream后,就要开始对里面的元素进行各类的操作了。常见的操作可以归类为如下:
- Intermediate操作
map(mapToInt、flatMap等)、filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered - Terminal操作
forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator - Short-circuiting操作
anyMatch、allMatch、noneMatch、findAny、limit
详细介绍各种操作哦 ~ ,宝贝们,你们学不好编程的话,可以添加vx:LeKu_yuan私人陪跑。
8.2.1 map/flatMap
/*
filter方法对原Stream按照指定条件过滤,在新建的Stream中,只包含满足条件的元素,将不满足条件的元素
过滤掉
*/
// 代码案例说明map
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileOperations {
public static void main(String[] args) {
String inputFile = "input.txt";
String outputFile = "output.txt";
try (BufferedReader reader = new BufferedReader(new FileReader(inputFile));
BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) {
// 使用流来读取文件内容,并将其处理后写入到另一个文件中
reader.lines()
.map(line -> line.toUpperCase()) // 将每一行转换为大写
.forEach(line -> {
try {
writer.write(line);
writer.newLine();
} catch (IOException e) {
e.printStackTrace();
}
});
System.out.println("文件处理完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 对flatMap说明:flatMap方法的作用是将流中的每个元素转换为一个流,并将这些流合并成一个流。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FlatMapExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("Hello", "World");
List<String> letters = words.stream()
.flatMap(word -> Arrays.stream(word.split(""))) // 将每个单词拆分成字母流
.collect(Collectors.toList());
System.out.println(letters);
}
}
/**
* 在flatMap的使用中,我们首先调用words.stream()来获取单词的流。然后,对于每个单词,
* 我们使用 *Arrays.stream(word.split(""))将单词拆分成字符数组,并将每个字符数组转换为
* 一个字母流。
*/
8.2.2 filter
// filter方法对原stream按照指定条件过滤,在新建的stream中,只包含满足条件的元素,将不满足条件
// 的元素过滤掉。
// 代码演示:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FilterExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 过滤出所有偶数
List<Integer> evenNumbers = numbers.stream()
.filter(number -> number % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers);
}
}
// 输出结果为:[2, 4, 6, 8, 10]
8.2.3 sorted
对Stream的排序通过sorted进行,它比数组的排序更强之处在于你可以首先对Stream进行各类map、filter、
limit、skip甚至distinct来减少元素数量后再排序,这能帮助程序明显缩短执行时间。
/* 无参: 无参形式的sorted方法会根据元素的自然顺序进行排序。如果流中的元素实现了Comparable接口,
那么它们的自然顺序就是由compareTo方法定义的。*/
import java.util.Arrays;
import java.util.List;
public class SortedExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3);
// 对流中的元素进行排序
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(sortedNumbers);
}
}
// 输出结果为:[1, 2, 3, 5, 8, 9]
// 有参: 有参形式的sorted方法接受一个Comparator函数式接口作为参数,用于定义元素的排序规则。
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class SortedExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("cat", "apple", "banana", "dog");
// 根据字符串长度对流中的元素进行排序
List<String> sortedWords = words.stream()
.sorted(Comparator.comparing(String::length))
.collect(Collectors.toList());
System.out.println(sortedWords);
}
}
// 输出结果为:[cat, dog, apple, banana]
8.2.4 distinct
distinct 方法以达到去除掉原 Stream 中重复的元素,生成的新 Stream 中没有没有重复的元素
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class DistinctExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 2, 4, 3, 5, 4, 6, 5);
// 去除重复的元素
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(distinctNumbers);
}
}
// 输出结果为:[1, 2, 3, 4, 5, 6],这是去除重复元素后的整数列表
8.2.5 limit/skip
limit返回Stream的前面n个元素;skip则是扔掉前n个元素(它是由一个叫 subStream的方法改名而来)。
// limit代码案例:
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用lambda表达式和Stream API来限制元素数量
List<Integer> limitedNumbers = numbers.stream()
.limit(3) // 限制元素数量为3
.collect(Collectors.toList());
System.out.println(limitedNumbers); // 输出 [1, 2, 3]
}
}
// skip代码案例:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用lambda表达式和Stream API来跳过指定数量的元素
List<Integer> skippedNumbers = numbers.stream()
.skip(2) // 跳过前2个元素
.collect(Collectors.toList());
System.out.println(skippedNumbers); // 输出 [3, 4, 5]
}
}
8.2.6 Terminal操作
1) forEach
forEach方法接收一个Lambda表达式,然后在Stream的每一个元素上执行该表达式。
注:forEach 不能修改自己包含的本地变量值,也不能用break/return之类的关键字提前结束循环。
2) findFirst
这是一个terminal兼short-circuiting操作,它总是返回Stream的第一个元素或者空。
3) reduce
这个方法的主要主要作用是把Stream元素组合起来。它提供一个起始值(种子),然后依照运算规则
(BinaryOperator),和前面Stream的第一个、第二个、第n个元素组合。从这个意义上说,字符串拼接、
数值的 sum、min、max、average都是特殊的reduce。
/* 代码案例说明reduce使用:*/
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用reduce方法求和
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println("Sum: " + sum);
// 使用reduce方法求乘积
int product = numbers.stream()
.reduce(1, (a, b) -> a * b);
System.out.println("Product: " + product);
// 使用reduce方法求最大值
int max = numbers.stream()
.reduce(Integer.MIN_VALUE, (a, b) -> a > b ? a : b);
System.out.println("Max: " + max);
}
}
/* 运行结果为:
Sum: 15
Product: 120
Max: 5
*/
4) min/max
min和max的功能也可以通过对Stream元素先排序,再findFirst来实现,但前者的性能会更好为O(n),而
sorted的成本是O(nlogN)。同时它们作为特殊的reduce方法被独立出来也是因为求最大最小值是很常见的
操作。
5) Match
(1).allMatch:Stream 中全部元素符合传入的 predicate,返回 true;
(2).anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true;
(3).noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true;
注:它们都不是要遍历全部元素才能返回结果。例如allMatch只要一个元素不满足条件,
就skip剩下的所有元素,返回false。
6)match
Stream有三个match方法,从语义上说:
(1) allMatch: Stream中全部元素符合传入的predicate,返回true
(2) anyMatch: Stream中只要有一个元素符合传入的predicate,返回true
(3) noneMatch: Stream中没有一个元素符合传入的predicate,返回true;
注:它们都不是要遍历全部元素才能返回结果。例如allMatch只要一个元素不满足条件,就skip剩下的所有元
素,返回false
/*代码演示:allMatch、anyMatch、noneMatch*/
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用allMatch方法判断是否所有元素都大于0
boolean allGreaterThanZero = numbers.stream()
.allMatch(n -> n > 0);
System.out.println("All greater than zero: " + allGreaterThanZero);
// 使用anyMatch方法判断是否存在元素大于3
boolean anyGreaterThanThree = numbers.stream()
.anyMatch(n -> n > 3);
System.out.println("Any greater than three: " + anyGreaterThanThree);
// 使用noneMatch方法判断是否所有元素都小于0
boolean noneLessThanZero = numbers.stream()
.noneMatch(n -> n < 0);
System.out.println("None less than zero: " + noneLessThanZero);
}
}
/*
All greater than zero: true
Any greater than three: true
None less than zero: true
*/
7) limit
limit方法将截取原stream,截取后stream的最大长度不能超过指定值N。如果原Stream的元素个数大于N,将
截取原Stream的前N个元素;如果原Stream的元素个数小于或等于N,将截取原Stream中的所有元素。
8) collect
将stream中的元素收集到一个集合或其他数据结构中
9) count: 返回stream的元素数量
10)toArray: 将stream中的元素转换为数组
11) findAny: 返回Stream中的任意一个元素。
/* java演示findAny代码 */
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用findAny方法获取流中的任意一个元素
Optional<Integer> anyNumber = numbers.stream()
.findAny();
System.out.println("Any number: " + anyNumber.orElse(null));
}
}
9. 补充optional接口的用法
说明:
- Optional接口是Java 8中引入的一个容器类,用于表示一个值存在或不存在的情况,它可以来解决空指针异常的问题,提供了一种更加优雅的方式处理可能为空的值。
- Optional类的主要作用是在某个值可能为空的情况下,提供一种简洁、安全的方式来处理这个值。它可以被看作是一个包装类,可以包装一个可能为空的值。通过使用Optional类,可以避免在代码中大量使用null检查和空指针异常处理。
// 常见的 api用法:
of(value) 创建一个包含指定值的Optional对象
empty(): 创建一个空的Optional对象
isPresent(): 判断Optional对象中是否包含值
get(): 获取Optional对象中的值,如果值不存在则会抛出NoSuchElementException异常
orElse(defaultValue): 获取Optional对象中的值,如果值不存在则返回默认值
orElseGet(Supplier): 获取Optional对象中的值,如果值不存在则通过提供的Supplier函数生成一个默认值
orElseThrow(ExceptionSupplier): 获取Optional对象中的值,如果值不存在则抛出指定的异常
》》》》》》》》》》》》》》》分》隔》行》》》》》》》》》》》》》》》》》》
ofNullable(T value): 将指定的值包装成Optional对象。如果value为null,则返回一个空的Optional对象;
否则返回一个包含value的Optional对象。
get(): 如果Optional对象中的值存在,则返回该值;否则抛出NoSuchElementException异常。
isPresent(): 判断Optional对象中是否存在值。如果存在则返回true,否则返回false。
ifPresent(Consumer<? super T> consumer): 如果Optional对象中的值存在,则执行指定的操作;
否则不执行任何操作。
// 获取值的操作,根据没有值,执行不同的操作
// orElse(T other):没有值返回指定的值other
orElse(T other): 如果Optional对象中的值存在,则返回该值;否则返回指定的默认值other。
//没有值的话使用supplier提供一个值
orElseGet(Supplier<? extends T> supplier): 如果Optional对象中的值存在,则返回该值;
否则通过指定的Supplier函数式接口提供一个默认值。
// 没有值的话抛出一个异常
orElseThrow(Supplier<? extends X> exceptionSupplier): 如果Optional对象中的值存在,则返回该值;
否则通过指定的Supplier函数式接口抛出一个异常。
map(Function<? super T, ? extends U> mapper): 如果Optional对象中的值存在,
则对其进行映射并返回一个包含映射结果的Optional对象;否则返回一个空的Optional对象。
flatMap(Function<? super T, Optional> mapper): 如果Optional对象中的值存在,则对其进行映射
并返回一个包含映射结果的Optional对象;否则返回一个空的Optional对象。与map()方法
不同的是,flatMap()方法的映射函数返回值必须是Optional对象。
filter(Predicate<? super T> predicate): 如果Optional对象中的值存在,并且满足指定的条件predicate,
则返回该Optional对象;否则返回一个空的Optional对象。
// 代码演示Optional:
import java.util.Optional;
public class Main {
public static void main(String[] args) {
String name = "John";
Optional<String> optionalName = Optional.of(name);
// 检查Optional对象是否包含值
if (optionalName.isPresent()) {
System.out.println("Name is present: " + optionalName.get());
} else {
System.out.println("Name is not present");
}
// 获取Optional对象中的值,如果值不存在则返回默认值
String defaultValue = "Unknown";
String value = optionalName.orElse(defaultValue);
System.out.println("Value: " + value);
// 获取Optional对象中的值,如果值不存在则通过提供的函数生成一个默认值
String generatedValue = optionalName.orElseGet(() -> generateDefaultValue());
System.out.println("Generated Value: " + generatedValue);
// 获取Optional对象中的值,如果值不存在则抛出指定的异常
try {
String result = optionalName.orElseThrow(() -> new IllegalArgumentException("Name is not
present"));
System.out.println("Result: " + result);
} catch (IllegalArgumentException e) {
System.out.println("Exception: " + e.getMessage());
}
}
private static String generateDefaultValue() {
System.out.println("Generating default value");
return "Default";
}
}
/*输出结果为:
Name is present: John
Value: John
Generating default value
Generated Value: John
Result: John
*/
10.总结流操作
1.可以使用filter、distinct、skip和limit对流进行筛选和切片;
2.可以使用map和flatMap提取或转换流中的元素;
3.可以使用findFirst和findAny方法查找流中的元素,你可以使用allMatch、noneMatch和anyMatch方法让流匹配给定的谓语;
4.上述方法都利用 了短路:找到结果就立刻停止计算,并没有处理整个流
;
5.可以使用reduce方法将流中的所有元素迭代合并成为一个结果,例如求和或查询最大的元素;
6.filter和map等操作都是无状态的,它们并没有存储任何状态。reduce等操作要存储状态才能计算出一个值,sorted和distinct等操作也要存储状态,因为他们需要把流中的所有元素缓存起来才能返回一个新的流,这种操作叫做有状态操作;
7.流有三种基本原始类型特化:intStream、doubleStream和LongStream,他们的操作也有相应的特化
8.流不仅可以从集合创建,也可以从值、数组、文件以及iterate与generate等特定方法创建。
11. 小结一下
Stream的特性归纳如下:
- 不是数据结构;
- 它
没有存储结构,它只是用操作管道从source(数据结构、数组、generator、function、IO channel)抓取数据
- 它也绝不修改自己所封装的底层数据结构的数据。例如Stream的filter操作会产生一个不包含被过滤元素的新Stream,而不是从source删除那些元素。
- 所有stream的操作必须以lambda表达式为参数;
- 不支持索引访问
- 你可以请求第一个元素,但无法请求第二个、第三个,或最后一个。
- 很容易生成数组或者list;
- 惰性化;
- 很多Stream操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始。
- Intermediate操作永远是惰性化的
- 并行能力
- 当一个stream是并行的,就不需要在写多线程代码,所有对它的操作会自动并行进行的;
- 可以是无限的。集合有固定大小、stream则不必,limit(n)和findFirst()这类的short-circuiting操作可以对无限的stream进行运算并很快完成。