一:Lambda 表达式
😀Lambda表达式的标准格式:由三部分组成
- 1 : 一些参数
- 2 : 一个箭头
- 3 : 一段代码
格式:
( 参数列表 ) -> { 一些重写方法的代码 }
说明:
- () : 接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数使用逗号分隔
- -> : 传递的意思,把参数传递给方法体 { }
- { } : 重写接口的抽象方法的方法体
简写Lambda表达式格式:
- 1:(参数列表):括号中参数列表的数据类型 可以省略不写
- 2:(参数列表):括号中的参数如果只有一个,那么类型 和 ( ) 都可以省略
- 3:{代码}:如果 { }中的代码只有一行,无论是否有返回值,都可以省略 { }, return ,分号
- 注意事项:如果要省略 { }, return ,分号 ,必须一起省略,否则都不能省略
🤭光说不练 假把式 上代码
使用Lambda表达式创建线程
之前我们使用匿名内部类创建线程是这样子滴~

使用Lambda表达式替换匿名内部类实现 创建多线程

还可以再简写

Lambda表达式的优化:
😀1、方法引用
定义一个打印的函数式接口
//定义一个打印的函数式接口
@FunctionalInterface
interface Printable{
//打印字符串的抽象方法
public abstract void print(String s);
}
Main方法
public class Demo1Print {
/*方法参数传递Printable接口,对字符串打印输出
* */
public static void printString(Printable p) {
p.print("I Love Java");
}
public static void main(String[] args) {
printString(s->System.out.println(s));
/*分析;Lambda表达式的目的,打印参数传递的字符串
* 把s传给了System.out对象,调用out中的方法pritnln(),对字符串进行输出
* 注意:
* 此时的 System.out对象是存在的,println()方法也是存在的
* 我们可以使用方法引用来优化Lambda表达式,使用System.out对象直接调用println()方法
* 格式: 对象::方法
* 双冒号为 引用运算符,它所在的表达式被称为方法引用
* */
printString(System.out::println);
}
}
😀2、通过对象名 引用成员方法
使用前提:对象名 和 成员方法 已经存在 才可以使用 对象名 来引用 成员方法
注意:如果类中的方法是静态的 就无法使用对象调用 直接用类名 引用 静态成员方法
练习
public class Demo2_ObjectMethodReference {
public static void printString(Printable p) {
p.print("java");
}
public static void main(String[] args) {
//调用printString方法,方法的参数Printable是一个函数式接口,所以可以传递Lambda表达式
printString((s)->{
//创建MethodFer对象
MethodFer mf = new MethodFer();
//使用MethodFer创建的对象 调用 该类中的方法printUpperCase
mf.printUpperCase(s); //输出JAVA
});
//方法引用(优化)
MethodFer mf2 = new MethodFer();
printString(mf2::printUpperCase); //输出JAVA
//或者
printString(new MethodFer()::printUpperCase); //输出JAVA
/*
* printString(MethodFer::printUpperCase);
* Cannot make a static reference to the non-static method
* printUpperCase(String) from the type MethodFer
*/
//通过类 引用 静态成员方法
printString(MethodFer::printLowerCase); //输出java
}
}
class MethodFer{
public void printUpperCase(String str) {
System.out.println(str.toUpperCase());
}
public static void printLowerCase(String str) {
System.out.println(str.toLowerCase());
}
}
😀3、通过类名引用静态成员方法
比如:类Math 中的abs方法是静态方法
前提:
类已经存在,静态成员方法也存在, 就可以直接通过类型引用静态成员方法
测试
public class Demo3_StaticMethodReference {
public static int calcator(int num,Calc c) {
return c.calcABS(num);
}
public static void main(String[] args) {
int num = calcator(-10,n->Math.abs(n));
System.out.println(num);
/*使用方法引用来优化Lambda表达式
* Math类是存在的 abs方法也是存在的
* */
int num2 = calcator(-20,Math::abs);
System.out.println(num2);
}
}
@FunctionalInterface
interface Calc{
public abstract int calcABS(int num);
}
😀4、通过super 引用父类的成员方法,通过this 引用本类的成员方法
public class Demo4_Super_this_MethodReference {
public static void main(String[] args) {
Man man = new Man();
man.show1();
man.show2();
}
}
//定义一个父类
class Human{
//定义sayHello方法
public void Say() {
System.out.println("Say Hello! I am Father");
}
}
//定义一个子类
class Man extends Human{
//重写父类sayHello方法
public void Say() {
System.out.println("Say Hi! I am son");
}
//定义一个方法 参数传递Greetable接口
public void method(Greetable g) {
g.greet();
}
//
public void show1() {
//调用method方法,方法的参数Greetable是一个函数式接口,所以可以传递Lambda表达式
// method(()->{
// //创建父类对象
// Human h = new Human();
// //调用父类的sayHello方法
// h.Say();
// });
//因为有子父类关系 所以存在关键字super 代表父类 可以直接使用super调用父类的方法
// method(()->{
// super.Say();
// });
/*继续优化 既然 可以直接使用super调用父类的方法
* 那么就可以使用super引用类的成员方法
* 因为super是已经存在的 Say方法是已经存在的
* 所以可以直接使用super引用父类的Say方法
*/ method(super::Say);
}
public void show2() {
//调用method方法,方法的参数Greetable是一个函数式接口,所以可以传递Lambda表达式
// method(()->{
// //创建本类对象
// Man man = new Man();
// //通过本类对象 调用本类的成员方法
// man.Say();
// });
//关键字this 代表本类 可以直接使用this调用本类的方法
// method(()->{
// this.Say();
// });
//继续优化 使用this 引用本类方法
method(this::Say);
}
}
//定义一个见面的函数式接口
@FunctionalInterface
interface Greetable{
public abstract void greet();
}
😀5、通过构造器 引用成员方法
public class Demo5_ConstructorMethodReference {
//定义一个方法 参数传递 姓名 和create接口
public static Person getName(String n,Create c) {
return c.create(n);
}
public static void main(String[] args) {
//调用getName方法
// Person name = getName("神厨小福贵",(s)->{
// return new Person(s);
// });
// System.out.println(name);
/*使用方法引用优化Lambda表达式
* 构造方法 new Person(String name); 已知
* 创建对象 new 已知
* 就可以使用Person 引用new 创建对象
* */
Person name = getName("比卡丘",Person::new);
System.out.println(name);
}
}
//定义一个函数式接口
@FunctionalInterface
interface Create{
//方法返回值为Person 因为要返回创建的对象
public abstract Person create(String name);
}
//定义一个类
class Person{
private String name;
public Person(String name) {
super();
this.name = name;
}
public Person() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person [name=" + name + "]";
}
}
😀6、数组的构造器引用
public class Demo6_ArrayMethodReference {
//定义一个返回数组的方法 参数传递 数组的长度 和接口
public static int[] getArr(int len,CreateArr c) {
return c.create(len);
}
public static void main(String[] args) {
//调用getArr方法
int[] arr1 = getArr(10,L->new int[L]);
System.out.println(Arrays.toString(arr1));
System.out.println(arr1.length);
/*使用方法引用优化 Lambda表达式
* 已知 数组类型 和数组创建方式
* int[]引用new,根据参数传递的长度来创建数组
* */
int[] arr2 = getArr(20,int[]::new);
System.out.println(Arrays.toString(arr2));
System.out.println(arr2.length);
}
}
//定义一个函数式接口
@FunctionalInterface
interface CreateArr{
//定义一个创建int类型数组的抽象方法 返回值为int[]
public abstract int[] create(int len);
}
二:链式编程
思想:
是将多个操作(多行代码)通过点号. 链接在一起成为一句代码,使代码可读性好。a(1).b(2).c(3)
下面的函数式编程就是用的这种风格
list.stream().filter(str->str.startsWith("李")).forEach(str->System.out.println(str));
三:常用的函数式接口
定义:有且只有一个抽象方法的接口,称之为函数式接口
当然接口中可以包含其他的方法( 默认,静态,私有)
函数式接口的使用:
一般可以作为方法的参数和返回值类型
函数式接口作为 方法的参数 的实例
java.lang.Runnable接口就是一个函数式接口
- 假设有一个startThread方法使用该接口作为参数,那么就可以使用Lambda表达式进行传参
- 这种情况其实跟Thread类的构造方法参数为Runnable没有本质区别
public class RunnableDemo {
//定义一个方法startThread 方法的参数使用 函数式接口Runnable
public static void startThread(Runnable run) {
//开启多线程
new Thread(run) {}.start();
}
public static void main(String[] args) {
//调用startThread方法,方法的参数是一个接口,那么我们可以传递这个接口的匿名内部类(或实现类)
startThread(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+" --> "+"线程开启");
}
});
//调用startThread方法,方法的参数是一个函数式接口,使用Lambda表达式进行传参
startThread( ()->{
System.out.println(Thread.currentThread().getName()+" --> "+"线程开启");
});
//Lambda表达式优化版本 省略 {} 和 ;
startThread( ()->System.out.println(Thread.currentThread().getName()+" --> "+"线程开启"));
}
}
函数式接口作为 返回值类型 的实例
- 当一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式
- 当需要通过一个方法来获取一个java.util.Comparator接口类型的对象作为排序器时,就可以调用该方法获取
public class ComparatorDemo {
//定义一个方法,方法的返回值类型使用函数式接口Comparator
public static Comparator<String> getComparator1(){
//方法的返回值类型是一个接口,那么我们可以返回这个接口的匿名内部类
return new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
//按照字符串的降序排序
return o2.length()-o1.length();
}
};
}
//改进版本 方法的返回值类型是一个函数式接口,所以我们可以返回一个Lambda表达式
public static Comparator<String> getComparator2(){
return (String o1,String o2)->{
//使用字符串的降序排序
return o2.length()-o1.length();
};
}
//Lambda表达式优化版本
public static Comparator<String> getComparator3(){
return (o1, o2)-> o2.length()-o1.length();
}
public static void main(String[] args) {
//创建一个字符串数组
String[] arr = {"aaa","ccccc","bbbb"};
//排序前
System.out.println("排序前的数组:");
System.out.println(Arrays.toString(arr));
//进行升序排序
Arrays.sort(arr);
System.out.println("默认升序排序后的数组:");
System.out.println(Arrays.toString(arr));
//进行降序排序
Arrays.sort(arr,getComparator3());
System.out.println("降序排序后的数组:");
System.out.println(Arrays.toString(arr));
}
}
运行结果
这个包就包含了所有的函数式接口

该包下有许多接口,其中 主要的是这四个接口

1、java.util.function.Function<T , R>接口


根据一个类型的数据得到另一个类型的数据,前者 为前置条件 后者 为后置条件
Function接口中最主要的抽象方法为:
R apply(T t),根据类型T的参数获取类型R的结果,andThen():用来进行组合操作

题目:将String 转换为Integer
方法:
/*方法参数传递一个字符串类型的整数,和一个Function接口,泛型使用<String,Integer>
* 使用Function接口中的方法apply,把字符串类型的整数,转换为Integer类型的整数
* */
public static void method(String str,Function<String,Integer> fun) {
int num = fun.apply(str);
num+=100;
System.out.println(num+0);
}
main:
// 输出 234
method("123",new Function<String,Integer>(){
@Override
public Integer apply(String t) {
return Integer.parseInt(t);
}
});
//使用Lambda表达式简化
method("234",str-> Integer.parseInt(str));
题目:把String类型的“123” 转换为Integer类型的整数,再加10 ,再转为String 使用 andThen
方法:
public static void method(String str,Function<String,Integer> fun1,Function<Integer,String> fun2) {
/* 分两步操作
int num = fun1.apply(str);
str = fun2.apply(num);
System.out.println(str);
*/
//使用andThen合成一步
String app = fun1.andThen(fun2).apply(str);
System.out.println(app+0);
}
main:
method("123",str->Integer.parseInt(str)+10,num->num.toString());
2、java.util.function.Supplier 接口
仅包含一个无参方法


- T get() 用来获取一个泛型参数指定类型的对象数据
- Supplier 接口被称之为生产型接口(供应商接口)
- 指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据
练习
public class SupplierDemo1 {
//定义一个方法,方法的参数传递Supplier<T>接口,泛型执行String,get方法就会返回一个String
public static String getString(Supplier<String> sup) {
return sup.get();
}
public static void main(String[] args) {
System.out.println(getString( ()->{ return "光头强";}));
System.out.println(getString( ()-> "天才威"));
}
}
题目:求数组元素的最大值:
使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组的最大值
public class SupplierDemo_GetArrMax {
public static int getMax(Supplier<Integer> sup) {
return sup.get();
}
public static void main(String[] args) {
//定义一个整型数组
int[] arr = {1,8,5,9,2,55,11,77,66};
//调用getMax方法,使用Supplier接口作为方法参数类型
//通过Lambda表达式求出int数组的最大值
int maxNum = getMax( ()->{
//定义一个变量存放数组中第一个数
int num = arr[0];
//循环查找
for (int i : arr) {
if(i>num)
num = i;
}
return num;
});
System.out.println("数组中最大值为:"+maxNum);
}
}
3、java.util.function.Predicate 接口

作用:对某种数据类型的数据进行判断,结果返回一个boolean值
Predicate接口中包含一个抽象方法:

boolean test(T t):用来对指定数据类型数据进行判断的方法
- 结果:
- 符合条件:返回true
- 不符合条件:返回false
练习
public class PredicateDemo {
/*
* 定义一个方法
* 参数传递一个String类型的字符串
* 传递一个Predicate接口,泛型使用String
* 使用Predicate中的方法test对字符串进行判断,并把判断结果返回
* */
public static boolean method(String str,Predicate<String> pre) {
return pre.test(str);
}
public static void main(String[] args) {
//如果字符串长度大于五则 返回true
System.out.println(method("absafjsdf",(str)->{ return str.length()>5;}));
System.out.println(method("abc",str-> str.length()>5 ));
}
}
其他方法测试
public class PredicateDemo {
//and实例 条件必须都满足 相当于&&
public static boolean checkingAnd(String str,Predicate<String> p1,Predicate<String> p2) {
// return p1.test(str) && p2.test(str);
return p1.and(p2).test(str);
}
//or实例 条件满足一个即可 相当于 ||
public static boolean checkingOr(String str,Predicate<String> p1,Predicate<String> p2) {
// return p1.test(str) || p2.test(str);
return p1.or(p2).test(str);
}
//negate表示取反 即为!
public static boolean checkingNegate(String str,Predicate<String> p1) {
// return !p1.test(str);
return p1.negate().test(str);
}
public static void main(String[] args) {
//And实例 true
System.out.println(checkingAnd("ABFDFD",str->str.length()>5 , str->str.contains("A")));
//Or实例 true
System.out.println(checkingOr("ABFDFD",str->str.length()>10 , str->str.contains("A")));
//Negate实例 false
System.out.println(checkingNegate("adadad",str->str.length()>5));
}
}
4、java.util.function.Consumer 接口

正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定
- Consumer接口中包含抽象方法 void accept( T t),意为消费一个指定泛型的数据
- Consumer接口是一个消费型接口,泛型指定什么类型,就可以使用accpet方法消费什么类型的数据
- 至于具体如何消费( 使用 ),需要我们自定义( 输出,计算。。。)

练习
public class ConsumerDemo {
/*定义一个方法 方法的参数传递一个字符串的姓名
* 方法的参数传递Consumer接口,泛型使用String
* 可以使用Consumer接口消费字符串的姓名
* */
public static void consumerName(String name,Consumer<String> con) {
con.accept(name);
}
public static void main(String[] args) {
//使用匿名内部类
consumerName("光头强",new Consumer<String>() {
public void accept(String t) {
System.out.println(t);
}
});
//因为此接口为 函数式接口 所以可以使用Lambda表达式
// (String t )->{ System.out.println(t); }
consumerName("天才威", t->System.out.println(t));
//实现字符串反转输出
consumerName("翠花",t-> System.out.println(new StringBuffer(t).reverse()) );
}
}
Consumer接口的默认方法addThen
作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,再对数据进行消费
例如:
Consumer con1
Consumer con2
String s = “Hello”;
con1.accpet(s);
con2.accpet(s);
- 改善:连接两个Consumer接口,再进行消费
- con1.addThen(con2).accpet(s); 谁写前边谁先消费
public class ConsumerDemo {
//定义一个方法,方法的参数传递一个字符串和两个Consumer接口,接口泛型使用字符串类型
public static void method(String str,Consumer<String> con1,Consumer<String> con2) {
con1.accept(str);
con2.accept(str);
}
public static void method2(String str,Consumer<String> con1,Consumer<String> con2) {
con1.andThen(con2).accept(str);
}
public static void main(String[] args) {
//调用method方法,传递一个字符串,两个Lambda表达式
method("I am CSNZ",t->System.out.println(t.toUpperCase()),t->System.out.println(t.toLowerCase()));
method2("I am CSNZ",t->System.out.println(t.toUpperCase()),t->System.out.println(t.toLowerCase()));
}
}
题目:字符串数组中存有多条信息,按照格式:“姓名:xx。性别:xx。”打印
使用两个Consumer接口 最后andThen
public static void print(String[] a,Consumer<String> c1,Consumer<String> c2) {
for (String str : a) {
c1.andThen(c2).accept(str);
}
}
public static void main(String[] args) {
//定义一个字符串数组
String[] arr = {"迪丽热巴,女","古力娜扎,女","马儿扎哈,男"};
print(arr,(str)->{
System.out.print("姓名:"+str.split(",")[0]+"。");
},(str)->{
System.out.print("性别:"+str.split(",")[1]+"。\n");
});
}
}
执行结果:
四:Stream流式计算

java.util.stream.Stream 是java 1.8 新加入的最常用接口。(这并不是一个函数式接口,里面不止一个抽象方法)






获取一个流的方式:
所有的Collection集合都可以通过stream的默认方法获取流

Stream接口的静态方法of可以获取可变长参数(底层即数组)对应的流

参数是一个可变参数,那么我们就可以传递一个数组
练习
public class StreamDemo {
public static void main(String[] args) {
//把集合转换为stream
List<String> arrayList = new ArrayList<String>();
Stream<String> stream1 = arrayList.stream();
Set<String> hashset = new HashSet<>();
Stream<String> stream2 = hashset.stream();
//如果是Map集合的话(双列集合)
Map<String,String> hashMap = new HashMap<String,String>();
//获取键,存储到一个set集合中,再转换成流
Set<String> keySet = hashMap.keySet();
Stream<String> stream3 = keySet.stream();
//获取值,存储到一个Collection集合中,再转换成流
Collection<String> values = hashMap.values();
Stream<String> stream4 = values.stream();
//获取键值对(键与值的映射关系 entrySet)
Set<Entry<String, String>> entrySet = hashMap.entrySet();
Stream<Entry<String, String>> stream5 = entrySet.stream();
//把数组转换为Stream流
Stream<Integer> stream6 = Stream.of(1,2,3,4,5);
//可变长参数可传递数组
int[] arr1 = {666,7,888};
Stream<int[]> stream7 = Stream.of(arr1);
Integer[] arr2 = {666,7,888};
Stream<Integer> stream8 = Stream.of(arr2);
String[] arr3 = {"a","bb","ccc"};
Stream<String> stream9 = Stream.of(arr3);
}
}
Stream流中常用方法 forEach
- void forEach( Consumer <? super T> action);
- 该方法接收一个Consumer接口函数,会将每一个流元素交给该函数处理
- Consumer接口是一个消费型的函数式接口,可以传递Lambda表达式进行消费
- Consumer中的抽象方法
- void accept(T t);
- forEach:即遍历流中的数据 是一个终结方法 遍历之后就不能调用stream流中的其他方法
public static void method1() {
//创建一个Stream流
Stream<Integer> stream1 = Stream.of(1,6,4,2,8);
//使用Stream流中的方法forEach对流中的数据进行遍历
stream1.forEach( str->System.out.println(str));
}
Stream流中的常用方法filter:用于对Stream流中的数据进行过滤
- Stream filter( Predicate<? super T> pre);
- filter方法的参数是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤
- Predicate中的抽象方法
- boolean test(T t);
public static void method2() {
//创建一个流
Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6);
//对流中的数据进行过滤 只要偶数
Stream<Integer> stream3 = stream2.filter(num->num%2==0);
//对新的流进行遍历
stream3.forEach(num->System.out.println(num));
}
如果需要将流中数据映射到另一个流中,可以使用map方法
- Stream map(Function<? super T, ? extends R> mapper);
- 该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的数据
- Function中的抽象方法
- R apply(T t);
public static void method3() {
//创建一个流
Stream<String> stream1 = Stream.of("1","2","3","4","5");
//将字符串转换为整数类型
Stream<Integer> stream2 = stream1.map(str->Integer.parseInt(str));
stream2.forEach(num->System.out.println(num));
}
Stream流中的常用方法:count:用于统计 流中元素个数
- 是一个终结方法,返回值是一个long类型的整数,所以在这之后无法调用其他的方法
public static void method4() {
//创建一个流
Stream<String> stream1 = Stream.of("1","2","3","4","5");
long count = stream1.count();
System.out.println(count);
}
Stream流中的常用方法:limit:用于截取流中的元素,只取前n个
- Stream limit(long maxSize):
- 参数是一个long型,如果集合当前长度大于参数则进行截取,否则不进行操作
- limit方法是一个延迟方法,对流进行截取,返回一个新的流,可以继续调用流中的其他方法
public static void method5() {
//创建一个流
Stream<String> stream1 = Stream.of("火花","水蓝篮","喵喵","少林呱呱","邪恶玄武");
//对流中的元素进行截取
Stream<String> stream2 = stream1.limit(3);
//对截取的流进行遍历
stream2.forEach(str->System.out.println(str));
}
Stream流中的常用方法:skip:用于跳过元素
- Stream skip(long n);
- 如果流的当前长度大于n,则跳过前n个,否则会得到一个长度为0的流
public static void method6() {
//创建一个流
Stream<String> stream1 = Stream.of("火花","水蓝篮","喵喵","少林呱呱","邪恶玄武");
//对流进行跳过截取
Stream<String> stream2 = stream1.skip(3);
//对截取的流进行遍历
stream2.forEach(str->System.out.println(str));
}
Stream流中的常用方法:concat:用于把流组合到一起
- static Stream concat(Stream <? extends T> a , Stream<? extends T> b)
public static void method7() {
//创建一个流
Stream<String> stream1 = Stream.of("火花","水蓝篮","喵喵","少林呱呱","邪恶玄武");
//创建另一个流
Stream<String> stream2 = Stream.of("1","2","3","4","5");
//将两个流进行合并
Stream<String> stream3 = Stream.concat(stream1, stream2);
//遍历合并的流
stream3.forEach(str->System.out.println(str));
}
执行结果:
题目:使用stream流对数组中的元素进行筛选遍历
public class Stream_List_demo {
public static void main(String[] args) {
//新建一个存储元素的集合
ArrayList<String> list = new ArrayList<>();
list.add("李白");
list.add("李黑");
list.add("白黑");
list.add("黑白");
list.add("李黑白");
/*普通方法筛选和遍历
ArrayList<String> list2 = new ArrayList<>();
for(String s:list) {
if(s.startsWith("李") && s.length()==2) {
list2.add(s);
}
}
for (String str : list2) {
System.out.println(str);
}
*/
//使用stream流筛选遍历 结合函数式编程
list.stream().filter(str->str.startsWith("李"))
.filter(str->str.length()==2)
.forEach(str->System.out.println(str));
}
}
Stream流的特点:
- 它属于管道流,只能被消费(使用)一次
- 第一个Stream流调用完毕后,数据就会传到下一个Stream上
- 此时第一个流 就会自动关闭 无法再进行使用了
本文介绍了Lambda表达式的标准格式和使用,包括创建线程、方法引用、成员方法引用、静态方法引用、构造器引用和数组构造器引用。同时,讲解了链式编程的思想和在Java中的应用,如Stream流式计算及其常见操作。



1309

被折叠的 条评论
为什么被折叠?



