从零开始的java学习Day16----------基础篇(函数式编程思想、函数式接口)
函数式编程思想
在编程中,除了面向对象思想,还有函数式编程思想。面向对象强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。
Java中Lambda就是一种函数式编程思想的语法,例如我们之前使用的匿名内部类,就可以使用Lambda来替代。
Lambda标准格式:
(参数类型 参数名称) -> { 代码语句 }
格式说明:
- 小括号内的语法与传统方法(抽象方法)参数列表一致:无参数则留空;多个参数则用逗号分隔
- ->是新引入的语法格式,代表指向动作
- 大括号内的语法与传统方法体要求基本一致
Lambda省略格式(在标准格式的基础上):
- 参数类型可以省略
- 如果小括号内的参数有且只有一个,小括号可以省略
- 如果大括号内只有一个语句,无论是否有返回值,都可以省略(大括号、retunrn、分号)
示例1(无参):
//先定义一个接口,里面只有一个抽象方法
public interface JieK {
void a();
}
//在主方法中调用
public class Demo2 {
public static void main(String[] args) {
//这里a就是下面定义的方法,因为a需要传入JieK接口的时限类对象,所以可以使用Lambda
a(()->System.out.println("呵呵")); //运行程序会输入"呵呵"
}
//这里需要新建一个方法,这个方法需要传入的就上上面的接口
static void a(JieK j){
j.a();
}
}
示例2(有参):
用Lambda写comparator的比较器实现类方法(降序)
Integer[] arr ={2,5,7,8,9,1,3};
Arrays.sort((o1,o2)->o2-o1);
此时,arr数组里的数字,已经按照降序排序了
小结
- Lambda只能作为代替要传入的一个接口的实现对象时使用,且该接口只有一个需要重写的方法
- 当方法的形参,需要传入的是一个接口,且该接口有且只有一个抽象方法,才可以使用Lambda
函数式接口
函数式接口在java中指的就是有且仅有一个抽象方法的接口。(必须有一个抽象方法,其他方法或变量可以随意)
函数式接口,可以适用于函数式编程的接口,在Java中函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda的接口。
如何判断
判断一个接口是不是函数式接口,可以用@FunctionalInterface标识,能被它标识的接口就是函数式接口
使用场景
函数式接口的典型应用场景就是作为方法的参数和返回值
Lambda的延迟执行
我们在传递参数时,如果参数类型是函数式接口,等实际调用该方法时,我们用Lambda表达式当作这个接口对象传入进去,穿进去的时候,Lambda表达式不会立即执行,需要等到该方法调用函数式接口的方法时才会执行,这样就有避免掉了一些资源浪费(比如该参数实际不会被用到)
方法引用
在使用Lambda表达式的时候,我们实际上传进去的代码就是一种解决方案。那么考虑到一种情况,我们在Lambda中所指定的操作方案,已经有地方存在相同方案,那就没有必要再写重复逻辑了
方法引用符
双冒号::为引用运算符,而他所在的表达式被称为方法引用,是一种Lambda表达式的改进写法。条件是,Lambda表达式的函数方案已经存在于某个方法中,那么可以通过双冒号来引用该方法为Lambda的替代者
下面是几种使用格式
通过对象名引用成员方法
这是最常见的一种用法,当一个类中有某个方法是我们需要的函数方案,可以通过这个类的对象来引用
格式:
对象名::方法名
通过类名引用静态方法
常用于一些系统里已经有的类的静态方法,可以直接通过类名来引用
格式:
类名::方法名
例:Math::abs //abs是Math类的静态方法
通过super引用父类方法
当我们操作的类存在继承关系,我们在使用Lambda时,需要引用父类中的方法,可以使用super调用
格式:
super::方法名
通过this引用成员方法
当我们操作的时候,想要引用自己类中的方法,可以使用this调用
格式:
this::方法名
类的构造器的引用
当我们使用Lambda时,需要实现的功能是要创建某个类的对象,就可以直接引用该类的构造方法来创建
格式:
类名::new
数组构造器的引用
同样的,当我们想要使用Lambda完成一个创建数组的功能时,也可以直接引用数组的构造器,注意,数组的构造器是需要传入长度(所以需要原使用的Lambda有int参数)
格式:
数组类型[]::new
常用函数接式口
JDK提供了大量的常用函数式接口以丰富Lambda的典型使用场景,他们主要在java.util.function包中被提供。下面是最简单的几个接口及使用示例
Supplier接口
该接口负责生产数据(无参数,有返回值),又叫做生产接口
Supplier该接口仅包含一个无参的方法:T get(),通过作为形参时指定数据类型,执行方法,返回一个对应的数据类型对象。
示例(求数组的最大值):
public static void main(String[] args) {
int[] intArr =new int[]{1,3,4,5,6,22,3,4,19,-9};
maxarr(() -> {
int max = intArr[0];
for (int i : intArr)
if (i > max)
max = i;
return max;
});
}
static public int maxarr(Supplier<Integer> max){
return max.get();
}
该接口的方法只负责返回一个对应类型的对象,内部的方法还是要自己去书写
Consumer接口
Consumer此接口与Supplier 接口刚好相反,它负责消费指定类型数据(有参数,无返回值)
接口中包含一个抽象方法void accept(T t),意为消费一个指定泛型的数据
示例(输出打印传入的对象):
public static void main(String[] args) {
XiaoFei((str)->{
System.out.println(str);
},"呵呵");
}
public static void XiaoFei(Consumer<String> consu,String str){
consu.accept(str);
}
Consumer接口中还包含一个默认方法andThen,该方法可以实现一个数据消费两次
示例(输出打印传入的对象两次):
public static void main(String[] args) {
XiaoFei("呵呵",(str)->{
System.out.println(str);
},(str)->{
System.out.println(str+"1");
});
}
public static void XiaoFei(String str,Consumer<String> con1,Consumer<String> con2){
con1.andThen(con2).accept(str); //这里哪个con对象写在前面,就会先执行哪个对象对应的Lambda内的方法
}
Predicate接口
Predicate该接口内的方法是能一个能得到boolean值的结果,我们叫它判断接口,当我们需要对某种类型的数据进行判断就可以使用
boolean test(T t)方法,判断传入的数据,返回一个布尔值
示例:
public static void main(String[] args) {
String st = "cdcda";
// 判断字符串是否包含"dd"
boolean res = pand1(st, xx -> xx.contains("dd"));
System.out.println(res);
}
//对一个字符串进行判断
static boolean pand1(String str, Predicate<String> a) {
return a.test(str);
}
Predicate接口内包含三个默认方法,分别是and,or和negate
and和or,逻辑上的"与"和"或"
当我们要对同一个数据进行多次比较时可以使用,可以链式编程,执行流程是从左往右
and等价&&符号
or等价||符号
格式示例:
public static void main(String[] args) {
// 判断字符串是否包含"a"和"cn",或者包含"c"
String st = "ab";//用它执行下列方法为true
String st1 = "c";//用它执行下列方法也为true
boolean res = pand1(st, xx -> xx.contains("a"),xx -> xx.contains("b"),
xx -> xx.contains("c"));
System.out.println(res);
}
//对一个字符串进行3次判断
static boolean pand1(String str, Predicate<String> a,Predicate<String> b,
Predicate<String> c) {
//先执行a.test(str)&&b.test(str)的结果,然后拿其值 ||c.test(str)的结果
return a.and(b).or(c).test(str);
}
nagate,逻辑取反,可以取反执行了test()后的值,注意,一定要在调用test方法调用它
格式:
nagate.test();等价于!test();
链式编程示例:
//设p1的lambda是判读数是否是偶数,p2的Lambda是判断数是否小于0
p1.nagate.or(p2).test(-1) //该语句等价于:p1.nagate.test(-1)||p2.test(-1),结果为true
Function接口
Function<T,R>该接口的功能就是通过T,得到一个R类型的数据,我们通常称为转换接口
抽象方法:R apply(T t)
使用示例:
public static void main(String[] args) {
Integer i = 100;
boolean b =true;
System.out.println(deao(i, a -> a + ""));//输出100字符串
System.out.println(deao(b, a -> a + ""));//输出ture字符串
}
// 将任意数据转换成String
static <T> String deao(T a ,Function<T,String> c){
String a1 = c.apply(a);
return a1;
}
Function接口也内含默认方法andThen,和Consumer内的差不多,都是先执行前面的,再执行后面的,只是它里面的andThen,第二次操作的数是第一次返回的值
使用示例:
public static void main(String[] args) {
boolean flag =true;
char[] chars = deao2(flag, a -> a + "", b -> b.toCharArray());
for (char c : chars) {
System.out.println(c);
}
}
// 将任意数据转换成String,再转为字符数组
static <T,R> char[] deao2(T a ,Function<T,R> c,Function<R,char[]> d){
return c.andThen(d).apply(a);
}
上一篇: android 发送邮件意图
下一篇: Java常用类 -- API的使用