函数式接口+Stream流+方法引用
一、函数式接口
- 函数式接口:一个接口中仅有一个抽象方法的接口。
格式:
修饰符 interface MyFun{
public abstract func1(参数类型 参数);
}
可以通过在接口上方加@FunctionalInterface
来帮助检查是不是函数式接口。
注释的作用仅仅只是帮助检查是否符合函数式接口的要求,即便不加注释,只要满足要求依然是函数式接口。
二、函数式编程
函数式接口结合Lamba表达式,提供了一种较为清爽的编程方式。
2.1Lambda表达式的延迟执行
Lambda表达式具有延迟执行的特性:
public class Day10 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
String s1 ="dassad";
String s2 = "dsanioa";
add(3,()-> s1+s2);
}
public static void add(int num,str s) {
if(num == 1) {
System.out.println("Lmaba准备执行啦!");
System.out.println(s.pinjie());
}
}
}
interface str{
abstract String pinjie();
}
以字符串拼接为例子,Lambda的延迟,实际上是把Lambda要执行的操作,延缓到了接口对象调用方法的时候来实现,如果不满足执行条件,就不执行Lambda表达式,节省了资源。
如果采用传统的方法:
- 先拼接好字符串,传递数字,那么不管数字是否满足判断,字符串都被拼接,消耗资源
- 传递所有要拼接的字符串+数字,验证数字之后再拼接,这样较为繁琐,而且字符串数量不能灵活的变化。
2.2Lmbda表达式作参数和返回值的情况
-
Lambda表达式作为参数:
Lambda表达式做参数实际上就是接口对象作参数,接口对象执行操作的时候要实现接口,这时使用Lambda表达式来完成。
public class Day12 { public static void main(String[] args) throws IOException, ClassNotFoundException { //调用的时候确定计算的方法为输入的数字*10 System.out.println(add(10,(a)->a*10)); } //add方法,参数是一个Cal类的对象以及一个数字 public static int add(int num,Cal c) { return c.calculate(num); } } //定义计算接口,内含一个抽象方法calculate(int num) interface Cal{ abstract Integer calculate(int num); }
-
Lambda表达式作返回值
public class Day12 { public static void main(String[] args) throws IOException, ClassNotFoundException { Integer [] a = {1,2,3,4,88,11,66,99,10000,55}; Arrays.sort(a,myCompare()); for(Integer i :a) { System.out.println(i); } } //返回一个Lambda表达式,作为Comparator<Integer>接口的实现,用于比较 public static Comparator<Integer> myCompare() { return (a,b)->a - b; } }
三、常用的函数式接口
函数式接口大部分位于java.util.function
包中被提供。
3.1Supplier接口
java.util.Supplier<T>
接口仅包含一个无参的方法:
-
T get()
用来返回一个待定类型的对象- 举例:求数组最大元素
public class Day12 { public static void main(String[] args) throws IOException, ClassNotFoundException { int [] a = {1,5,3,7,9,10,15,66,97,88,15}; int maxnum =getMax(a,()->{ int max = a[0]; for(int j : a) { if(j>max) max=j; } return max; }); System.out.println(maxnum); } //传递数组n,以及Supperlier<T>的一个对象(Lmbda表达式) public static int getMax(int [] n,Supplier<Integer> sup) { return sup.get(); } }
3.2Consumer接口
java.util.Consumer<T>
接口,与Supplier接口不同,它内部的抽象方法
-
void accept(T t)
消费一个泛型数据,是一个有参方法public class Day10 { public static void main(String[] args) throws IOException, ClassNotFoundException { String s = "Hello"; //在使用时,定义accept的使用方式为:将字符串打印输出 useConsumer(s,(str)->System.out.println(str)); } public static void useConsumer(String str,Consumer<String> con) { con.accept(str); } }
-
该接口中还有一个默认方法
andThen
default Consumer<T> andThen(Consumer<? super T> after)
参数与返回值都是一个Consumer的对象,该方法和它的语义相同,先…再…。具体做什么,可以根据accept(T t)来进行定义
public class Day10 { public static void main(String[] args) throws IOException, ClassNotFoundException { Integer i = 100; //第一次处理i值乘以10倍输出,第二次i值+50输出。但是两次i都是最开始给的100,中间并不会传递 useConsumer(i,(k)->{k*=10;System.out.println(k);},(k)->{k+=50;System.out.println(k);}); } public static void useConsumer(Integer k,Consumer<Integer> con1,Consumer<Integer> con2) { con1.andThen(con2).accept(k); } }
练习 格式化打印信息:
字符串中存储了姓名+性别,中间用","隔开,将它们拼接后输出。
public static void main(String[] args) {
String [] array = {"第一个,男" , "第二个,女" , "第三个,外星人"};
for(String ss :array) {
consumeString(
(s)->System.out.print(s.split(",")[0]),
(s)->System.out.println(s.split(",")[1]),
ss);
}
private static void consumeString(Consumer<String> function1,Consumer<String> function2,String ss) {
function1.andThen(function2).accept(ss);
3.3 Predicate接口
java.util.function.Predicate<T>
接口的主要作用是判断,得到一个boolean值的结果。
-
抽象方法:
boolean test(T t)
用于条件判断public class Day10 { public static void main(String[] args) throws IOException, ClassNotFoundException { useJudge(100,k->k>10); } //判断k是否大于10 public static void useJudge(Integer k,Predicate<Integer> p) { if(p.test(k)) System.out.println("数字k大于10");; } }
-
默认方法:
-
default Predicate<T> and(Predicate<? super T> other)
用法与逻辑"与"相同default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) ‐> test(t) && other.test(t); }
-
default Predicate<T> negate()
用法与逻辑"非"相同,对目前的test取反default Predicate<T> negate() { return (t) ‐> !test(t); }//从源码中可以看出,它要在test之前调用,对当前的test值取反
-
default Predicate<T> or(Predicate<? super T> other)
用法与逻辑"或"相同default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) ‐> test(t) || other.test(t); }
-
练习:集合信息筛选
数组当中有多条“姓名 +性别”的信息如下,请通过 Predicate 接口的 拼装将符 合要求的字符 串筛选到集合
ArrayList 中,需要 同时满足 两 个条件:
-
必须是女生
-
名字长度为4
public class RealDay12 { public static void main(String[] args) { String [] str = {"按时到会 女","时候 女","点搜 男","都不爱沙石堡 男","打谁哦阿斯顿 女","四个字符 女"}; ArrayList<String> list = new ArrayList<String>(); method( (s)->s.split(" ")[0].length()==4, (s)->s.split(" ")[1].equals("女"), str,list); for(String s :list) { System.out.println(s); } } private static void method(Predicate<String> pre1,Predicate<String> pre2,String[] str,ArrayList<String> list) { for(String s :str) { if(pre1.and(pre2).test(s)) list.add(s);} }
3.4 Function接口
java.util.function.Function<T,R>
接口的主要作用是“映射”,即根据T类型的对象,转换成R类型的对象。
且T与R可以是同一类型。
- 抽象方法:
R apply(T t)
- 默认方法:
andThen
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) ‐> after.apply(apply(t));
}
从源码中可以看出,Function中的andThen
方法与Consumer中的andThen
方法的区别是,前者的数据是传递下去的,而后者只是对一个数据独立的处理两次。
练习 自定义函数拼接
请使用 Function 进 行函数模 型 的拼接,按照顺序 需要执行 的多个 函数操 作为:
String str = “赵丽颖,20”
- 将字符串截取数 字年龄部分, 得到字符串;
- 将上一步的字符 串转换成为int类型的数字;
- 将上一步的int数 字累加100,得到结果int数字。
public class RealDay12 {
public static void main(String[] args) {
String str = "赵丽颖,20";
add(
k->k.split(",")[1],//第一步,分割字符串为字符串数组,返回代表年龄部分的字符串
k->Integer.parseInt(k)+100,//第二部,将年龄字符串转换为数字,再+100
str);
}
private static void add(Function<String,String> function1,Function<String,Integer> function2,String str) {
int num = function1.andThen(function2).apply(str);
System.out.println(num);
}
}
四、Stream流
java.util.stream.Stream<T>
,是java8中新加入的常用流接口。
Stream流和Lambda表达式有许多相似的地方,最关键的一点就是,注重做什么,不在意怎么做。
-
Stram流的基本特征
-
Pipelining:流的中间操作都会返回一个新流,可以不断进行下一步操作
-
内部迭代,不用再利用循环,具有直接的迭代方法
在对流进行操作后,流不能再次被使用!所以每次对流操作都会返回一个新流。
-
-
流的主要获取方式
-
由Collection获取(以List为例)
List<String> list = new ArrayList<String>(); Stream<String> stream = list.stream();
-
由数组获取流(比较特殊,是用Stream的静态方法获取)
Integer [] a = {1,2,3,4,5,6}; Stream<Integer> stream = Stream.of(a);
-
由Map获取流(由于是双列数据,分为两种情况)
-
值/键的流
-
Entry<K,V>流
Map<String,String> map = new HashMap<String, String>(); Stream<String> stream1 = map.keySet().stream(); Stream<String> stream2 = map.values().stream(); Stream<Entry<String,String>> stream3 = map.entrySet().stream();
-
-
-
流的基本方法
-
forEach(逐一处理)
void forEach(Consumer<? super T> action);
,内涵了一个Consumer接口,通过接口内部的accept(T t)方法来对数据进行逐一处理 -
fliter(过滤)
Stream<T> filter(Predicate<? super T> predicate);
,根据test的条件进行过滤 -
map(映射)
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
,根据apply
方法对数据进行转换 -
concat(组合),组合两个流,形成一个新流
Stream<String> stream3 = Stream.concat(stream1,stream2);
-
集合元素处理练习:
分别采用传统循环方法,和流方法对集合元素进行如下处理:
现在有两个 ArrayList 集合存 储 队伍当中的多个成员姓 名,要 求使用传 统的f o r 循环(或增强f o r 循环 )依次进行以
下若干操作步骤:
- 第一个队伍只要名字为3 个字的成员姓名;存储到 一个新 集合中。
- 第一个队伍筛选之后只要前3 个人;存储到一个新集合中 。
- 第二个队伍只要姓张的成员姓名;存储到一个新集合中 。
- 第二个队伍筛选之后不要前2个人;存储到一个新集合中 。
- 将两个队伍合并为一个队伍存储到一个新集合中。
- 根据姓名创建Person对象;存储到一个新集合中。
- 打印整个队伍的Person对象信 息。
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<String>();
ArrayList<String> listnew = new ArrayList<String>();
ArrayList<String> list2 = new ArrayList<String>();
ArrayList<String> list2new = new ArrayList<String>();
Collections.addAll(list1, "实践队是","刘是海屏","上档的次","的赛道的","张三丰");
Collections.addAll(list2, "张践队","刘大声道","又突然","大萨大傻傻的达","你你你","张憨憨","张志超","张大炮");
//采用传统方式处理
for(int i = 0; i<list1.size();i++) {
if(list1.get(i).length()==3) {
listnew.add(list1.get(i));
}
}
for(int i = 0; i <list2.size();i++) {
if(list2.get(i).startsWith("张")) {
list2new.add(list2.get(i));
}
}
ArrayList<String> finalist1 = new ArrayList<String>();
if(listnew.size()>=3) {
for(int i = 0;i<3;i++) {
finalist1.add(listnew.get(i));
}
}else if(listnew.size()<3&&listnew.size()>0){
for(int i = 0 ; i <listnew.size();i++) {
finalist1.add(listnew.get(i));
}
}
if(list2new.size()>2) {
list2new.remove(0);
list2new.remove(0);
}
ArrayList<String> list3 = new ArrayList<String>();
for(String s :list2new) {
list3.add(s);
}
for(String s :finalist1) {
list3.add(s);
}
ArrayList<Person> personlist = new ArrayList<Person>();
for(String s :list3) {
personlist.add(new Person(s));
}
for(Person person : personlist) {
System.out.println(person.toString());
}
//——————————————————————————————————————————————————————————————————————
//采用Stream方式处理
Stream<String> stream1 = list1.stream();
Stream<String> stream2 = list2.stream();
Stream<String> stream1last=stream1.filter((name)-> name.length()==3).limit(3);
Stream<String> stream2last=stream2.filter((name)->name.startsWith("张")).skip(2);
Stream<String> finalstream = Stream.concat(stream1last, stream2last);
ArrayList<Person> personlist2 = new ArrayList<Person>();
finalstream.forEach((name)->personlist2.add(new Person(name)));
System.out.println("Srteam流的优势");
for(Person person : personlist) {
System.out.println(person.toString());
}
//最后的输出结果相同,但是Stream流要更加轻便简洁
五、方法引用
Lambda表达式相对于实现一个内部类,或者新建一个类的方式来的更加简洁,可是在某些情况下,Lambda表达式也会显的不是很“清爽”:
public class Day13 {
public static void main(String[] args) {
String s = "asdno";
print(s,(str)->System.out.println(str));
}
private static void print(String a , Printable b) {
b.print(a);
}
}
interface Printable{
void print(String str);
}
如上述所示情况,Lambda表达式实现了print方法。可是在System.out中已经有了print的方法,采用方法引用的方式,可以更加简便:print(s,System.out::print);
这与print(s,(str)->System.out.println(str));
的作用是一样的。
::
双冒号的形式就称为方法引用。它的意义是,用print(s,System.out::print);
来取代Lambda表达式
方法引用也同样具有可推导可省略
的特点,以上述例子为例:将print方法的输出改为Int类型后,会自动匹配print中输出打印int类型的方法。
public class Day13 {
public static void main(String[] args) {
int i =10;
print(i,(str)->System.out.println(str));
}
private static void print(int a , Printable b) {
b.print(a);
}
}
interface Printable{
void print(int str);
}
通过对象名引用方法
当类中已经存在某个成员方法,可以直接引用来替代Lambda。还是这个接口,但是这次想要打出若干个*
符号,在MyFunction中就已经有现成的方法可以引用。
public class Day13 {
public static void main(String[] args) {
//通过建立该类的一个对象,引用对象中的方法来实现打印
MyFunction m = new MyFunction();
print(10,m::print);
}
private static void print( int num ,Printable b) {
b.print(num);
}
}
interface Printable{
void print(int str);
}
class MyFunction{
public void print(int num) {
for(int i =1 ; i<num;i++) {
System.out.print("*");
}
}
}
通过类名引用静态方法
public class Day13 {
public static void main(String[] args) {
CC(Math::abs,-10);
}
private static void CC(Cac lambda,int a) {
lambda.calc(a);
}
}
interface Cac{
int calc(int num);
}
直接引用Math类中的abs方法替代Lambda表达式实现calc(int num)
接口的方法,对-10进行绝对值处理。
通过super引用成员方法
interface Greetable{
void greet();
}
class Human{
public void sayHello() {
System.out.println("hello");
}
}
class Man extends Human{
@Override
public void sayHello() {
System.out.println("我是Man");
}
public void method(Greetable g) {
g.greet();
}
public void show() {
method(()->super.sayHello());//Lambda方法
method(super::sayHello);//方法引用
}
}
通过this引用成员方法
与上述super引用的情况相似
//......
class Man extends Human{
@Override
public void sayHello() {
System.out.println("我是Man");
}
public void method(Greetable g) {
g.greet();
}
public void show() {
method(()->this.sayHello());//Lambda方法
method(this::sayHello);//this引用自身的方法
}
}
引用类的构造器
//成员类
class Person{
public void printUpperCase(String i) {
}
private String name;
public Person() {}
@Override
public String toString() {
// TODO Auto-generated method stub
return name;
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
};
//函数接口
interface personBuilder{
Person buildPerson(String name);
}
public class Day13 {
public static void main(String[] args) {
//采用Lambda表达式
printName("xiaoming",(str)->new Person(str));
//引用Person类的构造方法,会自动将xiaoming作为参数构造一个新的Person
printName("xiaoming",Person::new);
}
public static void printName(String name,personBuilder builder) {
System.out.println(builder.buildPerson(name).getName());
}
}
引用数组构造器
interface ArrayBuilder{
int [] buildArray(int length);
}
public class Day13 {
public static void main(String[] args) {
buildArray(10,(i)->new int [i]);//Lambda表达式
buildArray(10,int []::new);//数组构造器的引用
}
private static void buildArray(int k ,ArrayBuilder g) {
int [] b= g.buildArray(k);
}
}