一.函数式接口的概念
函数式接口:接口中只有一个抽象方法.
函数式接口分为4个常用接口
1.Supplier供应者
2.Consumer消费者
3.Predicate判断
4.Function功能(多用于类型转换)
1.Supplier函数接口
概念:需要用到Lambda表达式.定义一个方法,方法里有一个已经存在的类作为其参数和返回值类型.
每当你调用这个方法时,都需要用Lambda表达式去重写,并用return语句返回其已存在类的new对象.包括但不限于retrun的new对象.
目的:目的在于对某个类的功能实现修改,这不同于普通的重写和内部匿名类.而是调用者是谁,谁就重写某个已存在的类对象.
格式:格式: 权限修饰符 动静修饰符 已知类/基本类的返回值类型 自定义方法名(Supplier<泛型> 自定义参数名){ 自定义参数名.get(); }
列如:
public static ArrayList sp(Supplier<ArrayList> sp) {
return sp.get();
}
代码:
public class SupplierClass {
public static void main(String[] args) {
ArrayList array = sp(()->{
return new ArrayList();
});
System.out.println(array);
}
public static ArrayList sp(Supplier<ArrayList> sp) {
return sp.get();
}
}
2.Predicate函数接口
Predicate函数接口通常用于判断参数是否满足指定的条件
而[参数判断本身的代码]通常都需要自己通过lambda表达式去面向过程来实现.
方法:
boolean test(T t) 对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值 多用!!!
default Predicate negate() 返回一个逻辑的否定,对应逻辑非
default Predicate and(Predicateother) 返回一个组合判断,对应短路与
default Predicate or(Predicateother) 返回一个组合判断,对应短路或
创建:
格式形式1:
权限修饰符 static boolean 自定义方法名(参数判断本身的类型 自定义参数判断名,Predicate<参数判断本身的泛型> 自定义Predicate参数名){
// 自定义Predicate参数名.test(自定义参数判断名);
// ..negate() ... and().... or()...
}
列如:
public static boolean getResult(String str, Predicate<String> pr) {
return pr.test(str); //pr的方法之一 test
}
格式形式2:
权限修饰符 static boolean 自定义方法名(参数判断本身的类型 自定义参数判断名,Predicate<参数判断本身的泛型> 自定义Predicate参数名1,Predicate<参数判断本身的泛型> 自定义Predicate参数名2){
// 自定义Predicate参数名.test(自定义参数判断名);
// ..negate() ... and().... or()...
}
列如:
public static ArrayList<String> getResultOfArrayList(String[] stringparam, Predicate<String> pr1, Predicate<String> pr2) {
ArrayList<String> newArray = new ArrayList<>();
for (String s : stringparam) {
if (pr1.and(pr2).test(s)) {//'and' 如果都为true
newArray.add(s);
}
}
return newArray;
}
**
3.Function函数接口
注意:Function转换和Consumer消费者都可以通过andThen方法来连续对某个数据进行操作,但Funtion的连续操作之意义重在[转换],即Lambda表达式/方法引用表达式的内容是将其数据进行类型转换的,且结果是被后来表达式所引用的.
而Consumer注重消费,即[拿来用/拿来实现],前一个表达式处理后的数据,后一个表达式并不会将其拿来用,而是该干嘛干嘛.
**Function:前一个的Lambda表达式/方法引用表达式将数据处理得出结果后,返回给后一个Lambda表达式/引用方法表达式会将引用前一个表达式所得出的结果拿来用.
比如 表达式1: 把100数值的int变量减去50 表达式2:得到还剩下50数值的int变量,进行其它操作.
Consumer:前一个Lambda表达式/方法引用表达式将数据处理后,不会将结果返回给下一个表达式,而下一个表达式拿到的数据则是全新的数据.
比如 表达式1:把一个100数值的int变量减去50,结果为50. 表达式2:得到了全新的,数值为100的int变量.
**
Function<T,R>接口通常用于对参数进行处理,转换(处理逻辑由Lambda表达式实现),然后返回一个新的值
创建
格式:
格式:权限修饰符 动静态修饰符 void 自定义方法名(被转换的参数类型 形参名, Function<泛型1,泛型2> 自定义Function参数1名,Function<泛型1,泛型2> 自定义Function参数名2,Function<泛型1,泛型2> 自定义Function参数名3){
//方法体.
}
列如:
public static void getAgeResult(String str, Function<String,String> fun1,Function<String,Integer> fun2,Function<Integer,Integer> fun3){
int result = fun1.andThen(fun2).andThen(fun3).apply(str);
}
方法
andThen
返回一个组合函数,首先将该函数应用于其输入,然后将 after函数应用于结果。
apply
将此函数应用于给定的参数。
compose
返回一个组合函数,首先将 before函数应用于其输入,然后将此函数应用于结果。
Function
返回一个总是返回其输入参数的函数。
:System.out.println(result); //R apply(T t) 将此函数应用于给定的参数 andThen(Function after)
代码:
public class FunctionTest {
public static void main(String[] args) {
String nameAndAge = "刘亦菲,39";
getAgeResult(nameAndAge, (ss)->{
return ss.split(",")[1];
}, (ss)->{
return Integer.parseInt(ss);
}, (ss)->{
return ss+80;
});
}
public static void getAgeResult(String str, Function<String,String> fun1,Function<String,Integer> fun2,Function<Integer,Integer> fun3){
int result = fun1.andThen(fun2).andThen(fun3).apply(str);
System.out.println(result);
//R apply(T t) 将此函数应用于给定的参数
/*
andThen(Function after)
返回一个组合函数,首先将该函数应用于输入,然后将after函
数应用于结果
*/
}
}
4.Consumer函数接口
注意: Consumer注重消费,即[拿来用/拿来实现],前一个表达式处理后的数据,后一个表达式并不会将其拿来用,而是该干嘛干嘛.
而Function转换和Consumer消费者都可以通过andThen方法来连续对某个数据进行操作,但**Funtion的连续操作之意义重在[转换],**即Lambda表达式/方法引用表达式的内容是将其数据进行类型转换的,且结果是被后来表达式所引用的.
Consumer:前一个Lambda表达式/方法引用表达式将数据处理后,不会将结果返回给下一个表达式,而下一个表达式拿到的数据则是全新的数据.
比如 表达式1:把一个100数值的int变量减去50,结果为50. 表达式2:得到了全新的,数值为100的int变量.
Function:前一个的Lambda表达式/方法引用表达式将数据处理得出结果后,返回给后一个Lambda表达式/引用方法表达式会将引用前一个表达式所得出的结果拿来用.
比如 表达式1: 把100数值的int变量减去50 表达式2:得到还剩下50数值的int变量,进行其它操作.
Consumer函数接口也被称为消费型函数接口,它消费的数据的数据类型由泛型指定.
Consumer只有参数没有返回值.
创建:
作用1/ 用唯一的方式消费参数一次.
格式:
权限修饰符 动静态修饰符 void 自定义方法名(被消费的参数类型 自定义消费参名,Consumer<被消费参数类型之泛型> 自定义Consumer参名){
//Consumer参名.accept(自定义消费参名);
}
列如:
public static void getResult(ArrayList<String> ar, Consumer<ArrayList<String>> cr){
cr.accept(ar);
}
作用2/ 用多种不同的方式消费参数多次. 这里有两种不同的方式,用这两种方式分别消费参数一次.
格式:
权限修饰符 动静态修饰符 void 自定义方法名(被消费的参数类型 自定义消费参名,Consumer<被消费参数类型之泛型> 自定义Consumer参名1,Consumer<被消费参数类型之泛型> 自定义Consumer参名2){
//自定义Consumer参名1.andThen(自定义Consumer参名2).accept(自定义消费参名);
}
列如:
private static void operatorString(String name, Consumer<String> con1, Consumer<String> con2) {
con1.andThen(con2).accept(name);
}
代码:
public class ConsumerA {
public static void main(String[] args) {
ArrayList<String> a = new ArrayList<String>();
a.add("东");
a.add("西");
a.add("南");
a.add("北");
getResult(a,(BianLi)->{
for (String s : BianLi) {
System.out.println(s);
}
});
}
public static void getResult(ArrayList<String> ar, Consumer<ArrayList<String>> cr){
cr.accept(ar);
}
}
Stream流
Stream流的概念基于前四者函数接口和Lambda以及方法引用之上所建立的,它将多个Lambda或方法引用的表达式拼成一组,从而达到一连串的的效果.这就是链式编程.
当然,将多个Lambda表达式或方法引用的表达式拼成一起,也是需要Stream流自有的方法去实现的.
如实现一个功能,对ArrayList集合中的元素进行过滤和遍历,那么用Stream流将会是如下样子.
要求:获取所有集合元素中韩姓的人,并且过滤掉名字在2个以上的,然后打印输出.
ArrayList<String> ar = new ArrayList<>();
ar.add("张曼玉");
ar.add("张康");
ar.add("李国立");
ar.add("韩国瑜");
ar.add("韩丽敏");
ar.add("韩静");
ar.add("韩冰");
ar.add("习可");
接着,在Stream流使用了[没有简化的Lambda表达式]情况下,代码简化了很多,不过没看出来有’链式编程’的效果.
//Stream流
ar.stream().filter((ss)->{
return ss.startsWith("韩");
}).filter((ss)->{
return ss.length()==2;
}).forEach(System.out::println);
那么,在使用了[简化了的Lambda表达式]情况下,则会是链式编程的效果了.
这里用Lambda表达式的简化特性简化了return语句和大括号.完全达到了链式编程的效果
ar.stream().filter(ss-> ss.startsWith("韩")).filter(ss -> ss.length()==0).forEach(System.out::println);
1.Stream的三个大致步骤
Stream的三个大致步骤分为 开启Stream流 —>中间操作 —>终结Stream流.
其中,'中间操作’和’终结Stream’在对待不同体系的对象类或基本类时,是没区别的.
但是,'Stream开启’流则因对象/类的体系不同而不同,这主要体现在’如何在这个体系的对象/类下开启Stream’流这一点,下面的篇章将会讲解,此处仅叙述三个步骤.
三个步骤的方法名与作用如下:
1/ stream() 作用:开启流
注意:同一个目标(如对象名)下,Stream流不能终结两次,而以下两行代码则都在最后用到了.forEsch()终结语句.
2/ filter() 作用:过滤流,也称之为[中间操作] 当用于集合数组时,会隐形地对元素遍历,再去根据规则过滤
3/ forEach() 终结流,执行括号内的代码,并将过滤后的结果返回给调用处.
count() 终结流,返回此流中的元素数给调用处
2.流的开启格式之Collection集合体系
所谓Collection体系,就是实现或继承自Collection的集合,如ArrayList集合.
他们在使用到Stream方法时,以对象名.Stream()
格式:Collection体系的对象名.Stream
列如: ar.stream()
.filter((ss)->{
return ss.startsWith(“韩”);
}).forEach(System.out::println);
参考代码:
public class StreamListTest {
public static void main(String[] args) {
ArrayList<String> ar = new ArrayList<>();
ar.add("张曼玉");
ar.add("张康");
ar.add("李国立");
ar.add("韩国瑜");
ar.add("韩丽敏");
ar.add("韩静");
ar.add("韩冰");
ar.add("习可");
//通过Stream流对韩姓开头的元素过滤,然后打印
ar.stream().filter((ss)->{
return ss.startsWith("韩");
}).forEach(System.out::println);
}
}
3.流的开启之数组体系
格式:System.of(数组名)
列如:Stream.of(intArray).
forEach(System.out::println);
代码:
public class StreamArray {
public static void main(String[] args) {
int[] intArray = {123,21312,5234,312,12,34,121,432,12,213,212,421,21};
Stream.of(intArray).forEach(System.out::println);
}
}
4.流的开启之Map集合体系
把Map转成Set集合,间接的生成流
步骤1/ 先创建定义一个Map集合体系,此处以HashMap举例
格式:Map<键泛型,值泛型> 自定义Map集合对象名 = new HashMap<>();
列如:Map<Integer, String> hashmapObj = new HashMap<>();
步骤2/ 将Map集合转为Set集合
格式:Set<泛型> 自定义Set集合对象名 = 自定义Map集合对象名.相关方法();
列如:Set<Map.Entry<Integer, String>> se = hashmapObj.entrySet();
步骤3 利用此Set集合生成Stream流
格式:自定义Set集合对象名.stream()
列如:Map<Integer,String>a = se.stream()
.limit(3).collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue));
参考代码:
//创建Set集合对象
Map<Integer, String> hashmapObj = new HashMap<>();
hashmapObj.put(25, "刘亦菲");
hashmapObj.put(24, "杨颖");
hashmapObj.put(21, "刘涛");
hashmapObj.put(27, "杨紫");
hashmapObj.put(22, "张梦珂");
//需求1:只要前三个人的信息.
//间接转换为set,然后间接Stream使用它
Set<Map.Entry<Integer, String>> se = hashmapObj.entrySet();
Stream<String> streamObj;
//然后再收集到Map集合中,也就是转到,赋值到Map集合中.
Map<Integer,String>a = se.stream().limit(3).collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue));
5.方法
1/ 过滤元素或数据
格式:对象名
.stream.filter(Lambda表达式/方法引用)
列如:ar.stream().filter(ss-> ss.startsWith("韩")).filter(ss -> ss.length()==0).forEach(System.out::println);
2/ 从上到下数,取前几行指定元素.如limit(4),则为取前4行的元素.
格式:对象名
.stream().limit(数值)
列如:Stream limitS = ar.stream().limit(4);
3 略过某几行的元素,数值表示从第一行往最后一行数,要略过几行.
格式:对象名
.stream().skip(数值)
列如:ar.stream().skip(2).forEach(System.out::println);
4 将A流和B流的数据(或者说是元素)合并,成为一个新的流
格式:Stream.concat(Stream流A, Stream流B)
列如:Stream.concat(limitS, skipS).forEach(System.out::println);
5 去除重复元素
格式:Stream.concat(Stream流A,Stream流B).distinct();
列如:Stream.concat(limitS, skipS).distinct();
6 根据英文字母,返回元素的升序排序.
格式:对象名
.stream().sorted()
列如:ar.stream().sorted().forEach(System.out::println);
7 通过Comparator比较器接口进行自定义排序(可以是重写)
格式:
对象名.stream().sorted(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
//规则体
}
})
列如:
ar.stream().sorted(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int num = o1.length()-o2.length();
//谁的值越小,谁靠前,如果都为0,则随意.
return num;
}
}).forEach(System.out::println);
8 Map扩展方法,在Map方法参数内能够不过滤元素就做其它事情,用于弥补和实现其它功能,如将’元素保存到对象中’,或’元素<----->对象’之间的一些操作
格式:对象名
.stream().map(lambda表达式/方法引用)
列如:
team.map(Student::new).map(Student::toString).forEach(System.out::println);
9 让流获得的元素能够进行运算,如可以通过该方法下的’sum’子方法来进行计算.
应该还有更多的方法,尚未挖掘.
格式:对象名
.stream().mapToInt(Integer::parseInt)
列如:ar.stream().mapToInt(Integer::parseInt).forEach(System.out::println);
10 把元素收集到List集合中
public static Collector toList()
尚未尝试.
11 把元素转换到Set集合中
public static Collector toSet()
尚未尝试.
12 把元素转换到Map集合
格式: Map<泛型1泛型2> 自定义map转换对象名 = 原集合对象名
.stream().collect(Collectors.toMap(
Map.Entry::getKey,Map.Entry::getValue))
;
列如:
Map<Integer,String>a = se.stream().limit(3).collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue));
Function函数接口案例
【编号:2306】 使用lambda表达式分别将以下功能封装到Function对象中:
1、将Map<String, Integer>中所有的值存到ArrayList中
2、求存储元素为Integer类型的ArrayList中所有元素的平均数 已知,学生成绩如下:
张三 85
李四 80
王五 90
赵六 95
田七 70
请以学生姓名为key,成绩为value创建集合并存储数据。然后分别使用步骤1和2中所创建的Function对象求出学生的平均成绩。
自己做得!虽然与原答案有点出入,但逻辑相同,完美应用到了Function的使用规范.
public class FunctionAvg {
public static void main(String[] args) {
Map<String,Integer> hashObj = new HashMap<>();
hashObj.put("张三",85 );
hashObj.put("李四",80);
hashObj.put("王五",90 );
hashObj.put("赵六",95 );
hashObj.put("田七",70 );
functionMethod(hashObj, (s)->{
ArrayList<Integer> al = new ArrayList<>();
for (String s1 : s.keySet()) {
String keyString = s1;
al.add(s.get(keyString));
System.out.println("键和值分别为"+keyString+" "+s.get(keyString));
}
return al;
},(s)->{
Integer sum=0;
int count=0;
for (Integer integer : s) {
count++;
sum +=integer;
}
sum= sum/count;
System.out.println("平均分为"+sum);
return sum;
});
}
public static Integer functionMethod(Map<String,Integer> mp,Function<Map<String,Integer>,ArrayList<Integer>> fn,Function<ArrayList<Integer>,Integer> fu2){
return fn.andThen(fu2).apply(mp);
}
}
Predicate函数接口案例
【编号:2304】 在Java内置的函数式接口中,Predicate是一个断言型接口,提供了对输入的参数进行断定并返回boolean类型的功能。请阅读如下材料:
在类中,已经提供了filterStr方法,用于过滤集合中的数据,请在主方法中调用该方法,并使用Lambda表达式实现注释中的需求。反馈该题
public class PredicateTwo {
public static void main(String[] args) {
List<String> strings = Arrays.asList("123", "123456", "abcdef", "abc123", "ab123cd", "7654321", "123abc", "123s456", "123456789123");
// 请调用filterStr方法,将其中符合条件的元素保存到新集合
// 条件一:元素长度为[5-10],包含5和10
// 条件二:元素只能包含数字字符
List<String> listObj = filterStr(strings, s -> {
return s.length() > 4 && s.length() < 11;
}, s -> {
int num = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i)>=48&&s.charAt(i)<=57){
num++;
}
}
return num==s.length();
});
// 遍历新集合
for (String s : listObj) {
System.out.println(s);
}
}
public static List<String> filterStr(List<String> strs, Predicate<String> pre1, Predicate<String> pre2) {
List<String> list = new ArrayList<>();
for (String str : strs) {
if (pre1.and(pre2).test(str))
list.add(str);
}
return list;
}
}
Consumer函数接口案例
【编号:2302】 在Java内置的函数式接口中,Consumer是一个消费型接口,其中的抽象方法
void accept(T t) // 对给定的参数执行操作,没有返回值
另外,接口中还提供了andThen方法,返回一个组合的Consumer,依次执行操作。请观察如下代码:
public class ConsumerDemo {
public static void main(String[] args) {
// 对姓名sayHello,即将参数前拼接一个 “hello:”,输出
// 对姓名sayHello输出,并打印姓名的长度
}
//accetp方法
//andThen方法
}
}
public class ConsumerTest {
public static void main(String[] args) {
String name = "sayHeelo";
// 对姓名sayHello,即将参数前拼接一个 "hello:",输出
strMethodOne(name, (s)->{
System.out.println("hello:"+s);
});
// 对姓名sayHello输出,并打印姓名的长度
strMethodTwo(name, (s1)->{
System.out.println(s1);
}, (s2)->{
System.out.println(" "+s2.length());
});
}
public static void strMethodOne(String str, Consumer<String> cn){
cn.accept(str);
}
public static void strMethodTwo(String str,Consumer<String> cn1,Consumer<String> cn2){
cn1.andThen(cn2).accept(str);
}
}
Supplier函数接口案例
【编号:2301】 在Java内置的函数式接口中,Supplier是一个供给型接口,其中的抽象方法:
T get() // 获得结果。每次调用get()方法返回一个T类型对象,没有参数。
请观察如下代码:
public class Test {
public static void main(String[] args) {
// 获取一个32位的UUID(参见java.util.UUID)
// 获取一个字符串形式的当前系统时间毫秒值的id
}
/**
* 可自义的id生成器
* @param supplier
* @return
*/
public static String getId(Supplier supplier) {
return supplier.get();
}
}
很多时候,我们需要获取到一个无重复的id作为某个对象或某条数据的唯一标识。类中,提供了getId方法,请在主方法中调用该方法,分别按要求获取两种id并打印。
public class Test {
public static void main(String[] args) {
// 获取一个32位UUID
String uuid = getId(() -> UUID.randomUUID().toString().replaceAll("-", ""));
System.out.println(uuid);
// 获取一个以当前系统时间毫秒值为字符串的id
String timeId = getId(() -> System.currentTimeMillis() + "");
System.out.println(timeId);
}
/**
* 可自义的id生成器
* @param supplier
* @return
*/
public static String getId(Supplier<String> supplier) {
return supplier.get();
}
}