📕概述
在Java 8中引入了Lambda表达式作为一项重要的语言特性,可以堪称是一种语法糖。Lambda表达式使得以函数式编程的方式解决问题变得更加简洁和便捷。
Lambda表达式的语法如下:
(parameters) -> expression
(参数) -> {代码}
其中,parameters
是Lambda表达式的参数列表,可以是零个或多个参数,如果有多个参数,用逗号分隔;expression
是Lambda表达式的函数体,可以是一个表达式或一段语句块。
❗使用Lambda表达式的条件
-
函数式接口:Lambda表达式只能用于函数式接口(Functional Interface)的实现。函数式接口是指只包含一个抽象方法的接口。函数式接口可以使用@FunctionalInterface注解显式声明,但这并非必要,只要满足函数式接口的特征即可。
-
上下文匹配:Lambda表达式必须与上下文(Context)相匹配,即Lambda表达式的参数和返回类型必须与目标上下文的参数和返回类型相匹配。例如,Lambda表达式可以作为方法的参数或赋值给一个接口类型的变量。
📕Lambda表达式
以下是一个简单的Lambda表达式的示例:
//Lambda表达式作为匿名函数实现两个数相加
MathOperation addition = (int a,int b) -> a+b;
int result = addition.operation(2,3);//结果为5
在这个示例中,我们定义了一个MathOperation
接口,并使用Lambda表达式作为接口的实现。Lambda表达式接受两个整数参数a
和b
,并返回它们的和。
🏷例1
public class LambdaTest {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {System.out.println("线程开始"); }}).start();}
}
使用Lambda表达式实现就是这样子的:
public class LambdaTest {public static void main(String[] args) {new Thread(() -> System.out.println("线程开始")).start();}
}
🏷例2
public class LambdaTest {public static void main(String[] args) {int b = calculateNum(new IntBinaryOperator() {@Overridepublic int applyAsInt(int left, int right) {return left + right;}});System.out.println(b);}public static int calculateNum(IntBinaryOperator operator){int a = 10;int b = 20;return operator.applyAsInt(a,b);}
}
使用Lambda表达式就是这个样子的:
public class LambdaTest {public static void main(String[] args) {int b = calculateNum((left, right) -> left + right);System.out.println(b);}public static int calculateNum(IntBinaryOperator operator){int a = 10;int b = 20;return operator.applyAsInt(a,b);}
}
还差那么点意思是吧🤭,因为啥,因为return是吧,为啥掉了呢,其实没有掉,你把left+right改成{return left+right}试试,那肯定是不会报错的,left+right是表达式,加个return就是语句了,所以需要加上{}包裹着。你再看,和左边去掉之后有没有哪里不一样。
这里说明:注意使用Lambda表达式的前提吼,Lambda表达它不需要知道你接口名,也不需要知道你的接口内的方法名,但是哦,可得记住,这个接口内只能有一个抽象方法,那么我们就可以直接把不需要的东西去掉,简化后的代码就是Lambda表达式,但是这个时候你如果安装了idea的一些插件,他可能会提示你说还可以转换成Lambda更简化的表达式,不要着急,咱们慢慢来来!
🏷例3
public class LambdaTest {public static void main(String[] args) {printNum(new IntPredicate() {@Overridepublic boolean test(int value) {return value%2==0;}});}public static void printNum(IntPredicate intPredicate){int[] arr = {1,2,3,4,5,6,7,8,9};for (int i : arr) {if(intPredicate.test(i)){System.out.println(i);}}}
}
改为Lambda表达式:
public class LambdaTest2 {public static void main(String[] args) {printNum((int value) ->{return value%2==0;});}public static void printNum(IntPredicate intPredicate){int[] arr = {1,2,3,4,5,6,7,8,9};for (int i : arr) {if(intPredicate.test(i)){System.out.println(i);}}}
}
就这么简单,不用怀疑,就是这么简单,但是这还不是最简化的形式。
🏷例4
🆗,例子到此结束,这四个例子可以看出,转换Lambda表达式,接口名和方法名我们no care,我们只关注这个参数。咱们继续 👇
📕转换Lambda表达式简化规则
- 参数类型可以省略
- 方法体只有一句代码时,大括号return和唯一一句代码的分号可以省略
- 方法只有一个参数时小括号可以省略
- 以上这些规则都记不住也可以省略不记(因为IDEA有快捷键可以实现)Alt+Enter
我们看上面说到的最后一个例子:简化的前提是,先转换成Lambda表达式再简化
这其实就和右边的一样了 。
当然还有更简化的方式啊,就是简化Integer.valueOf(str)为Integer::valueOf,这个是为什么呢?
在Java中,Integer.valueof
是调用静态方法的一种方式。它表示调用Integer
类的valueof
方法,将一个基本类型的整数值转换为对应的Integer
对象。
而Integer::valueof
则是Java 8引入的方法引用(Method Reference)的语法。它也表示调用Integer
类的valueof
方法,并且可以作为Lambda表达式的替代形式使用。相当于把方法引用包装成一个函数接口。
因此,Integer.valueof
和Integer::valueof
的作用是完全相同的,只是语法上的不同而已。它们都用于将基本类型的整数值转换为对应的Integer
对象。
那么类似这样的,都可以转换:
byte by = 10;
Byte byteObj = Byte.valueOf(by); // 将byte类型的值转换为Byte对象short sh = 100;
Short shortObj = Short.valueOf(sh); // 将short类型的值转换为Short对象int i = 1000;
Integer intObj = Integer.valueOf(i); // 将int类型的值转换为Integer对象long l = 1000000L;
Long longObj = Long.valueOf(l); // 将long类型的值转换为Long对象float f = 3.14f;
Float floatObj = Float.valueOf(f); // 将float类型的值转换为Float对象double d = 1.4142135;
Double doubleObj = Double.valueOf(d); // 将double类型的值转换为Double对象
如果这种不会转换,那就用快捷键吧。
📕Stream流
🏷准备工作
/*** @author 小白程序员* @Classname Author* @Description TODO干啥呢* @date 2023/9/3 17:39*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode //后期去重的使用
public class Author {private Long id;private String name;private Integer age;private String intro;private List<Book> books;
}/*** @author 小白程序员* @Classname Book* @Description TODO干啥呢* @date 2023/9/3 17:41*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Book {private Long id;private String name;private String category;private Integer score;private String intro;
}/*** @author 小白程序员* @Classname BookService* @Description TODO干啥呢* @date 2023/9/3 17:43*/
public class BookService {public static void main(String[] args) {//后面的代码都写在这一块的来测试}private static List<Author> getAuthors() {Author author = new Author(1L, "蒙多", 17, "一个祖安人", null);Author author2 = new Author(2L, "亚拉索", 18, "艾欧尼亚", null);Author author3 = new Author(3L, "易大师", 19, "黑色玫瑰", null);Author author4 = new Author(3L, "易大师", 19, "黑色玫瑰", null);List<Book> book1 = new ArrayList<>();List<Book> book2 = new ArrayList<>();List<Book> book3 = new ArrayList<>();List<Book> book4 = new ArrayList<>();book1.add(new Book(1L,"*","哲学,爱情", 80, "*"));book1.add(new Book(2L,"**","爱情,个人成长", 80, "**"));book2.add(new Book(3L,"***","爱情,传记", 70, "***"));book2.add(new Book(3L,"****","个人成长,传记", 70, "****"));book2.add(new Book(4L,"*****","哲学", 70, "*****"));book3.add(new Book(5L,"******","个人成长", 60, "******"));book3.add(new Book(6L,"*******","传记", 60, "*******"));book3.add(new Book(6L,"********","爱情", 60, "********"));book4.add(new Book(5L,"******","个人成长", 60, "******"));book4.add(new Book(6L,"*******","个人成长,传记,爱情", 60, "*******"));book4.add(new Book(6L,"********","哲学,爱情,个人成长", 60, "********"));author.setBooks(book1);author2.setBooks(book2);author3.setBooks(book3);author4.setBooks(book4);List<Author> authors = new ArrayList<>(Arrays.asList(author,author2,author3,author4));return authors;}
}
🏷案例分析
案例:获取年龄小于18的作家
public static void main(String[] args) {//查询出年龄小于18的作家的名字List<Author> authors = getAuthors();authors.stream().distinct().filter(new Predicate<Author>() {@Overridepublic boolean test(Author author) {return author.getAge() < 18;}}).forEach(new Consumer<Author>() {@Overridepublic void accept(Author author) {System.out.println(author.getName());}});
}//Lambda表达式优化
public static void main(String[] args) {//查询出年龄小于18的作家的名字List<Author> authors = getAuthors();authors.stream().distinct().filter(author -> author.getAge() < 18).forEach(author -> System.out.println(author.getName()));}
解释说明一下:authors.stream()是把集合转换成流形式,.distinct()是去重,.filter()是过滤,.forEach()是循环输出。
🏷常用操作
🔖创建流
①单例集合:集合对象.stream()
List<Integer> list = new ArrayList<>();
Stream<Integer> stream = list.stream();
②数组:Arrays.stream(数组)或Stream.of(数组)来创建
Integer[] arr = {1,2,3,4};
Stream<Integer> stream1 = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr);
③双例集合:转换成单例集合再创建
Map<String, String> map = new HashMap<>();
Stream<Map.Entry<String, String>> stream3 = map.entrySet().stream();
🔖中间操作
①filter
对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中。
public static void main(String[] args) {//查询出年龄小于18的作家的名字List<Author> authors = getAuthors();authors.stream().distinct().filter(author -> author.getAge() < 18)//中间操作.forEach(author -> System.out.println(author.getName()));//终结操作}
②map
可以把流中的元素进行计算或转换。
public static void main(String[] args) {List<Author> authors = getAuthors();authors.stream().map(new Function<Author, String>() {//泛型中,第一个参数为方法的参数类型(流中的类型),第二个参数为方法的返回值类型@Overridepublic String apply(Author author) {return author.getName();}}).forEach(new Consumer<String>() {@Overridepublic void accept(String name) {System.out.println(name);}});
}//Lambda表达式优化public static void main(String[] args) {List<Author> authors = getAuthors();//泛型中,第一个参数为方法的参数类型(流中的类型),第二个参数为方法的返回值类型authors.stream().map(author -> author.getName()).forEach(name -> System.out.println(name));}
由此可以看出,Lambda表达式是有多么简化代码。
计算使用
public static void test() {List<Author> authors = getAuthors();//泛型中,第一个参数为方法的参数类型,第二个参数为方法的返回值类型authors.stream().map(author -> author.getAge()).map(age -> age+10).forEach(age -> System.out.println(age));
}
③distinct
去除流中重复的元素。
❗distinct方法是依赖Object类中的equals方法来判断是否是相同对象,所以需要重写equals方法
④sorted
对流中的元素进行排序
👍调用sorted()空参方法
在比较的实体类上要实现Comparable接口,不然会报类型不匹配的异常。
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode //后期去重的使用
public class Author implements Comparable<Author>{private Long id;private String name;private Integer age;private String intro;private List<Book> books;@Overridepublic int compareTo(Author o) {return this.getAge()-o.getAge();}
}
public static void test2(){List<Author> authors = getAuthors();authors.stream().distinct().sorted().forEach(author -> System.out.println(author.getName()));}
这里sorted内没有参数是因为在实体类中定义了比较的方式,所以不用传递参数。
👍在sorted方法中实现Comparator接口
首先把Author中实现的东西去掉,用Lambda表达式方式写比较条件。sorted()方法中的new Comparator<T>是一个函数式接口,内部只有一个和compare抽象方法。
public static void test3() {List<Author> authors = getAuthors();authors.stream().distinct().sorted(new Comparator<Author>() {@Overridepublic int compare(Author o1, Author o2) {return o1.getAge()-o2.getAge();}}).forEach(author -> System.out.println(author.getAge()));
}
优化
public static void test3(){List<Author> authors = getAuthors();authors.stream().distinct().sorted(((o1, o2) -> o2.getAge()- o1.getAge())).forEach(author -> System.out.println(author.getAge()));}
⑤limit
可以设置流的最大长度,超出的部分将被抛弃。
public static void test4(){List<Author> authors = getAuthors();authors.stream().distinct().sorted(((o1, o2) -> o2.getAge()- o1.getAge())).limit(2).forEach(System.out::println);}
⑥skip
跳过流中的前N个元素,返回剩下的元素。
⑦flatMap
map方式:map只能把一个对象转换成另一个对象来作为流中的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素。
案例:打印所有书籍,要求对重复的元素进行去重。
map方式:Author对象的books属性是集合类型,使用原来map转换对象,要使用嵌套循环进行打印。
public static void test5() {List<Author> authors = getAuthors();authors.stream().map(Author::getBooks).forEach(books -> {books.forEach(System.out::println);});}
flatMap方式:
public static void test6() {List<Author> authors = getAuthors();authors.stream().flatMap(author -> author.getBooks().stream()).forEach(System.out::println);}
案例二:打印现有数据的所有分类,要求对分类进行去重。不能出现这种格式:哲学,爱情,要将它们拆开输出。
public static void test7(){List<Author> authors = getAuthors();authors.stream().flatMap(author -> author.getBooks().stream()).distinct().flatMap(book -> Arrays.stream(book.getCategory().split(","))).distinct().forEach(System.out::println);}
🔖终结操作
①forEach
对流中的元素进行遍历操作,我们通过传入的参数去指定对遍历到的元素进行什么具体的操作。
②count
获取当前流中的元素个数。
public static void test8(){List<Author> authors = getAuthors();Long count = authors.stream().flatMap(author -> author.getBooks().stream()).distinct().count();System.out.println(count);}
③max&min
获取流中的最值
public static void test9(){List<Author> authors = getAuthors();Optional<Integer> max = authors.stream().flatMap(author -> author.getBooks().stream()).map(Book::getScore).max(((o1, o2) -> o2-o1));Optional<Integer> min = authors.stream().flatMap(author -> author.getBooks().stream()).map(Book::getScore).max(((o1, o2) -> o1-o2));System.out.println(max);System.out.println(min);}
④collect
把当前流的元素转换成一个集合
//获取一个存放所有作者名字的list集合
public static void test5() {List<Author> authors = getAuthors();List<String> nameList = authors.stream().map(author -> author.getName()).collect(Collectors.toList());System.out.println(nameList);
}
list集合、set集合、map集合都是类似的
注意map的是两个参数哦:collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()))
⑤查找与匹配
(1)anyMatch:判断是否有任意符合匹配条件的元素,结果为Boolean类型。
public static void test9(){List<Author> authors = getAuthors();boolean flag = authors.stream().anyMatch(author -> author.getAge()>18);System.out.println(flag);}
(2)allMatch:判断是否都符合条件,如果都符合返回true,否则返回false
public static void test10(){List<Author> authors = getAuthors();boolean b = authors.stream().allMatch(author -> author.getAge() > 18);System.out.println(b);}
(3)noneMatch:判断流中的元素是否都不符合匹配条件,如果都不符合结果为true,否则为false
(4)findAny:获取流中的任意一个元素,该方法没有办法保证获取到的一定是流中的第一个元素。
public static void test11(){//获取任意一个年龄大于18的作家List<Author> authors = getAuthors();Optional<Author> any = authors.stream().filter(author -> author.getAge() > 50).findAny();//如果这个Optional中有元素,则执行方法,没有就不执行any.ifPresent(System.out::println);}
(5)findFirst:获取流中的第一个元素
public static void test12(){//获取一个年龄最小的作家List<Author> authors = getAuthors();Optional<Author> firstOne = authors.stream().sorted((o1, o2) -> o1.getAge() - o2.getAge()).findFirst();firstOne.ifPresent(System.out::println);}
(6)reduce归并:对流中的数据按照指定的计算方式计算出一个结果
reduce的作用是把Stream中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式一次拿流中的元素和初始值进行计算,计算结果再和后面的元素计算。
它内部的计算方式如下:
T result = identity;
for (T element : this stream)result = accumulator.apply(result, element)
return result;
其中identity就是我们可以通过方法参数传入的初始值,accumulator的apply具体进行什么计算也是我们通过方法参数来确定的。
案例1:使用reduce求所有作者年龄的和
public static void test13(){List<Author> authors = getAuthors();Integer sum = authors.stream().distinct().map(Author::getAge).reduce(0,(result,element) -> result + element);System.out.println(sum);}
案例2:使用reduce求所有作者中年龄的最大值
public static void test14(){List<Author> authors = getAuthors();Integer max = authors.stream().map(Author::getAge).reduce(Integer.MIN_VALUE,(result,element) -> result > element ? result : element);System.out.println(max);}
reduce有个重载形式,内部代码如下:
boolean foundAny = false;
T result = null;
for (T element : this stream) {if(!foundAny) {foundAny = true;result = element;} else {result = accumulator.apply(result, element);}
}
return foundAny ? Optional.of(result) : Optional.empty();
利用这个重载形式,求作者年龄的最大值,不用传递初始值了。
public static void test8() {List<Author> authors = getAuthors();Optional<Integer> max = authors.stream().map(author -> author.getAge()).reduce((result, element) -> result > element ? result : element);System.out.println(max.get());
}
❗惰性求值:如果没有终结操作,中间操作是不会得到执行的。
❗流是一次性的:一旦一个流对象经过一个终结操作后,这个流就不能在被使用了,只能重新创建流对象再使用。
❗不会影响原数据:我们在流中可以对数据做很多处理,但正常情况下是不会影响原来集合中的元素的。