函数式接口

有且仅有一个抽象方法的接口

@FunctionalInterface注解

一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注 意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

自定义函数式接口

对于刚刚定义好的 MyFunctionalInterface 函数式接口,典型使用场景就是作为方法的参数:

@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();     
} public class Demo09FunctionalInterface {    
// 使用自定义的函数式接口作为方法参数     
private static void doSomething(MyFunctionalInterface inter) {     
inter.myMethod(); // 调用自定义的函数式接口方法         
}          
public static void main(String[] args) {     
// 调用使用函数式接口的方法         
doSomething(() ‐> System.out.println("Lambda执行啦!"));         
}     
}

函数式编程

Lambda的延迟执行

性能浪费的日志案例

public class Demo01Logger {     
private static void log(int level, String msg) {         
if (level == 1) {           
System.out.println(msg);            
}     
}       
public static void main(String[] args) {         
String msgA = "Hello";         
String msgB = "World";         
String msgC = "Java";           
log(1, msgA + msgB + msgC);     
}
}

这段代码存在问题:无论级别是否满足要求,作为 log 方法的第二个参数,三个字符串一定会首先被拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费

备注:SLF4J是应用非常广泛的日志框架,它在记录日志时为了解决这种性能浪费的问题,并不推荐首先进行 字符串的拼接,而是将字符串的若干部分作为可变参数传入方法中,仅在日志级别满足要求的情况下才会进 行字符串拼接。例如: LOGGER.debug("变量{}的取值为{}。", "os", "macOS") ,其中的大括号 {} 为占位 符。如果满足日志级别要求,则会将“os”和“macOS”两个字符串依次拼接到大括号的位置;否则不会进行字 符串拼接。这也是一种可行解决方案,但Lambda可以做到更好。

体验Lambda的更优写法

使用Lambda必然需要一个函数式接口:

@FunctionalInterface
public interface MessageBuilder {      
String buildMessage();  
}

然后对 log 方法进行改造:

public class Demo02LoggerLambda {     
private static void log(int level, MessageBuilder builder) {         
if (level == 1) {           
System.out.println(builder.buildMessage());            
}     
} public static void main(String[] args) {         
String msgA = "Hello";         
String msgB = "World";         
String msgC = "Java";           
log(1, () ‐> msgA + msgB + msgC );    
 }
}

这样一来,只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接

证明Lambda的延迟

public class Demo03LoggerDelay {     
private static void log(int level, MessageBuilder builder) {         
if (level == 1) {           
System.out.println(builder.buildMessage());            
}     
}       
public static void main(String[] args) {         
String msgA = "Hello";         
String msgB = "World";         
String msgC = "Java";           
log(2, () ‐> {             
System.out.println("Lambda执行!");             
return msgA + msgB + msgC;         
});     
}
}

从结果中可以看出,在不符合级别要求的情况下,Lambda将不会执行。从而达到节省性能的效果。

扩展:实际上使用内部类也可以达到同样的效果,只是将代码操作延迟到了另外一个对象当中通过调用方法 来完成。而是否调用其所在方法是在条件判断之后才执行的。

使用Lambda作为参数和返回值

如果抛开实现原理不说,Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数 式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式 接口作为方法参数。

例如 java.lang.Runnable 接口就是一个函数式接口,假设有一个 startThread 方法使用该接口作为参数,那么就 可以使用Lambda进行传参。这种情况其实和 Thread 类的构造方法参数为 Runnable 没有本质区别。

public class Demo04Runnable {     
private static void startThread(Runnable task) {       
new Thread(task).start();        
}       
public static void main(String[] args) {       
startThread(() ‐> System.out.println("线程任务执行!"));        
}
}

类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。当需要通过一 个方法来获取一个 java.util.Comparator 接口类型的对象作为排序器时,就可以调该方法获取。

private static Comparator<String> newComparator() {       
return (a, b) ‐> b.length() ‐ a.length();        
}       
public static void main(String[] args) {         
String[] array = { "abc", "ab", "abcd" };         
System.out.println(Arrays.toString(array));         
Arrays.sort(array, newComparator());         
System.out.println(Arrays.toString(array));
}

其中直接return一个Lambda表达式即可。

常用函数式接口

JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在 java.util.function 包中被提供。 下面是简单的几个接口及使用示例。

Supplier接口

java.util.function.Supplier<T> 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对 象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。

private static String getString(Supplier<String> function) {       
return function.get();        
}       
public static void main(String[] args) {         
String msgA = "Hello";         
String msgB = "World";         
System.out.println(getString(() ‐> msgA + msgB));     
}

练习:求数组元素最大值

题目

使用 Supplier 接口作为方法参数类型,通过Lambda表达式求出int数组中的大值。提示:接口的泛型请使用 java.lang.Integer 类。

解答

//定一个方法,方法的参数传递Supplier,泛型使用
Integer     public static int getMax(Supplier<Integer> sup){         
return sup.get();     
}       
public static void main(String[] args) {         
int arr[] = {2,3,4,52,333,23};           
//调用getMax方法,参数传递Lambda         
int maxNum = getMax(()‐>{            
//计算数组的大值            
int max = arr[0];            
for(int i : arr){                
if(i>max){                    
max = i;                
}            
}            
return max;         
});         
System.out.println(maxNum);     
}

Consumer接口

java.util.function.Consumer<T> 接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据, 其数据类型由泛型决定

抽象方法:accept

Consumer 接口中包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据。基本使用如:

private static void consumeString(Consumer<String> function) {       
function.accept("Hello");        
}       
public static void main(String[] args) {         
consumeString(s ‐> System.out.println(s));     
}

更好的写法是使用方法引用。

默认方法:andThen

如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作, 然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。下面是JDK的源代码

default Consumer<T> andThen(Consumer<? super T> after) {     
Objects.requireNonNull(after);     
return (T t) ‐> { accept(t); after.accept(t); };
}

备注: java.util.Objects 的 requireNonNull 静态方法将会在参数为null时主动抛出 NullPointerException 异常。这省去了重复编写if语句和抛出空指针异常的麻烦。

要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是“一步接一步”操作。例如两个步骤组 合的情况:

private static void consumeString(Consumer<String> one, Consumer<String> two) {       
one.andThen(two).accept("Hello");        
}       
public static void main(String[] args) {         
consumeString(             
s ‐> System.out.println(s.toUpperCase()),             
s ‐> System.out.println(s.toLowerCase()));     
}

运行结果将会首先打印完全大写的HELLO,然后打印完全小写的hello。当然,通过链式写法可以实现更多步骤的 组合。

练习:格式化打印信息

题目

下面的字符串数组当中存有多条信息,请按照格式“ 姓名:XX。性别:XX。 ”的格式将信息打印出来。要求将打印姓 名的动作作为第一个 Consumer 接口的Lambda实例,将打印性别的动作作为第二个 Consumer 接口的Lambda实 例,将两个 Consumer 接口按照顺序“拼接”到一起。

public static void main(String[] args) {   
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };   
}

解答

public static void main(String[] args) {         
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };         
printInfo(s ‐> System.out.print("姓名:" + s.split(",")[0]),                   
s ‐> System.out.println("。性别:" + s.split(",")[1] + "。"),                   
array);     
}       
private static void printInfo(Consumer<String> one, Consumer<String> two, String[] array) {         
for (String info : array) {             
one.andThen(two).accept(info); // 姓名:迪丽热巴。性别:女。         
}     
}

Predicate接口

有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用 java.util.function.Predicate<T> 接口。

抽象方法:test

Predicate 接口中包含一个抽象方法: boolean test(T t) 。用于条件判断的场景:

private static void method(Predicate<String> predicate) {         
boolean veryLong = predicate.test("HelloWorld");         
System.out.println("字符串很长吗:" + veryLong);     
}       
public static void main(String[] args) {         
method(s ‐> s.length() > 5);     
}

条件判断的标准是传入的Lambda表达式逻辑,只要字符串长度大于5则认为很长。

默认方法:and

既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用“与”逻辑连接起来实 现“并且”的效果时,可以使用default方法 and 。其JDK源码为:

default Predicate<T> and(Predicate<? super T> other) {     
Objects.requireNonNull(other);     
return (t) ‐> test(t) && other.test(t);
}

如果要判断一个字符串既要包含大写“H”,又要包含大写“W”,那么:

private static void method(Predicate<String> one, Predicate<String> two) {         
boolean isValid = one.and(two).test("Helloworld");         
System.out.println("字符串符合要求吗:" + isValid);     
}       
public static void main(String[] args) {         
method(s ‐> s.contains("H"), s ‐> s.contains("W"));     
}

默认方法:or

and 的“与”类似,默认方法 or 实现逻辑关系中的“或”。JDK源码为:

default Predicate<T> or(Predicate<? super T> other) {     
Objects.requireNonNull(other);     
return (t) ‐> test(t) || other.test(t);
}

如果希望实现逻辑“字符串包含大写H或者包含大写W”,那么代码只需要将“and”修改为“or”名称即可,其他都不 变:

private static void method(Predicate<String> one, Predicate<String> two) {         
boolean isValid = one.or(two).test("Helloworld");         
System.out.println("字符串符合要求吗:" + isValid);     
}       
public static void main(String[] args) {         
method(s ‐> s.contains("H"), s ‐> s.contains("W"));     
}

默认方法:negate

“与”、“或”已经了解了,剩下的“非”(取反)也会简单。默认方法 negate 的JDK源代码为:

default Predicate<T> negate() {     
return (t) ‐> !test(t);
}

从实现中很容易看出,它是执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在 test 方法调用之前 调用 negate 方法,正如 andor 方法一样:

private static void method(Predicate<String> predicate) {         
boolean veryLong = predicate.negate().test("HelloWorld");         
System.out.println("字符串很长吗:" + veryLong);     
}       
public static void main(String[] args) {       
method(s ‐> s.length() < 5);        
}

练习:集合信息筛选

题目

数组当中有多条“姓名+性别”的信息如下,请通过 Predicate 接口的拼装将符合要求的字符串筛选到集合 ArrayList 中,需要同时满足两个条件:

  1. 必须为女生;
  2. 姓名为4个字。

解答

public static void main(String[] args) {         
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };         
List<String> list = filter(array,                                    
s ‐> "女".equals(s.split(",")[1]),                                    
s ‐> s.split(",")[0].length() == 4);         
System.out.println(list);     
}       
private static List<String> filter(String[] array, Predicate<String> one,                                         Predicate<String> two) {         
List<String> list = new ArrayList<>();         
for (String info : array) {             
if (one.and(two).test(info)) {                 
list.add(info);             
}         
}         
return list;     
}

Function接口

java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件, 后者称为后置条件。

抽象方法:apply

Function 接口中主要的抽象方法为: R apply(T t) ,根据类型T的参数获取类型R的结果。

使用的场景例如:将 String 类型转换为 Integer 类型。

private static void method(Function<String, Integer> function) {         
int num = function.apply("10");         
System.out.println(num + 20);     
}       
public static void main(String[] args) {         
method(s ‐> Integer.parseInt(s));     
}

,好是通过方法引用的写法。

默认方法:andThen

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {     
Objects.requireNonNull(after);     
return (T t) ‐> after.apply(apply(t));
}

该方法同样用于“先做什么,再做什么”的场景,和 Consumer 中的 andThen 差不多:

private static void method(Function<String, Integer> one, Function<Integer, Integer> two) {         
int num = one.andThen(two).apply("10");         
System.out.println(num + 20);     }       
public static void main(String[] args) {         
method(str‐>Integer.parseInt(str)+10, i ‐> i *= 10);     
}

第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作通过 andThen 按照前后顺序组合到了一 起。

请注意,Function的前置条件泛型和后置条件泛型可以相同。

练习:自定义函数模型拼接

题目

请使用 Function 进行函数模型的拼接,按照顺序需要执行的多个函数操作为:

String str = "赵丽颖,20";

  1. 将字符串截取数字年龄部分,得到字符串;
  2. 将上一步的字符串转换成为int类型的数字;
  3. 将上一步的int数字累加100,得到结果int数字。

解答

public static void main(String[] args) {         
String str = "赵丽颖,20";         
int age = getAgeNum(str, s ‐> s.split(",")[1],                             
s ‐>Integer.parseInt(s),                             
n ‐> n += 100);         
System.out.println(age);     
}       
private static int getAgeNum(String str, Function<String, String> one,                                  
Function<String, Integer> two,                                  
Function<Integer, Integer> three) {         
return one.andThen(two).andThen(three).apply(str);     
}

JavaSE复习(六)函数式接口的更多相关文章

  1. [六] 函数式接口的复合方法示例 predicate 谓词逻辑运算 Function接口 组合运算 比较器 逆序 比较链

    复合的方法 有些函数式接口提供了允许复合的方法 也就是可以将Lambda表达式复合成为一个更加复杂的方法 之前的章节中有说到: 接口中的compose, andThen, and, or, negat ...

  2. Java开发笔记(六十二)如何定义函数式接口

    前面介绍了Lambda表达式的用法,从实践中发现它确实极大地方便了开发者,然而不管是匿名内部类还是Lambda表达式,所举的例子都离不开各类数组的排序方法,倘使Lambda表达式仅能用于sort方法, ...

  3. java 基本语法(十六)Lambda (三)函数式接口

    方法引用1.理解:方法引用可以看做是Lambda表达式深层次的表达.换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法. 2.使用情境:当要传递给Lam ...

  4. Effective Java 第三版——44. 优先使用标准的函数式接口

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  5. Java8新特性(一)——Lambda表达式与函数式接口

    一.Java8新特性概述 1.Lambda 表达式 2. 函数式接口 3. 方法引用与构造器引用 4. Stream API 5. 接口中的默认方法与静态方法 6. 新时间日期 API 7. 其他新特 ...

  6. JavaSE复习(七)Stream流和方法引用

    Stream流 全新的Stream概念,用于解决已有集合类库既有的弊端. 传统集合的多步遍历代码 几乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或间接的遍历操作.而当我们 ...

  7. 函数式接口与Stream流

    lambda表达式是jdk8的特性.lambda表达式的准则是:可推断,可省略. 常规代码写一个多线程 public class Main { public static void main(Stri ...

  8. java8新特性1:lambda表达式和函数式接口

    1.lambda的介绍: 1.1.为什么java语言需要引入lambda表达式? java语言诞生于1995年,历史时间已经相对较长了.在其后的各种新型编程语言中,都有着lambda表达式的内容,并且 ...

  9. javaSE复习之——线程

    线程其实就是程序执行的一条路径,一个进程中可以包含多条线程,多线程并发执行可以提高程序效率,可以同使完成多项任务 多线程的应用场景 迅雷多线程一起下载 服务器同时处理多个客户请求 多线程原理(单核CP ...

随机推荐

  1. CSS实战2

    1. 鼠标样式 Cursor: pointer   鼠标变成小手 Cursor: default;   小白 Cursor : move;   移动 Cursor : text ;    文本输入 网 ...

  2. 由inline-block小例子引申出的一些问题,及IE6、IE7兼容性解决方案

    使用场景分析: 常见的对块与块之间的横向排列处理 对同级所有元素使用display:inline-block; , 之后块与块直接会产生间隙问题 解决办法: 给父级设 font-size:0; 别高兴 ...

  3. 为什么有IP还需要硬件地址,或者说为什么有硬件地址还需要IP

    只用MAC 虽然每个设备都有唯一的硬件地址,但不都是MAC格式. 只用MAC的话理论上是可行的,但是其中 兼容不同的硬件地址,处理起来是非常困难的.而且数据链路层也没有必要处理网络层的逻辑. 只用IP ...

  4. Java实现非递归归并排序

    public class nonRecursiveMergeSort { public static void main(String[] args) { int[] list = {8,4,3,6, ...

  5. JavaScript文本框焦点事件

    效果图如下: <!-- 当文本框获得焦点时候,如果文本框内容是 请输入搜索关键字 清空文本框,输入内容变黑色 --> <!-- 当文本框失去焦点时候,如果文本框无内容,则添加灰色的 ...

  6. connect() to unix:/var/run/php-fpm.sock failed (11: Resource temporarily unavailable)

    nginx + php做服务,在高并发的时候会出现一些错误  connect() to unix:/var/run/php-fpm.sock failed (11: Resource temporar ...

  7. [BZOJ2809][Apio2012]dispatching(左偏树)

    首先对于一个节点以及它的子树,它的最优方案显然是子树下选最小的几个 用左偏树维护出每棵子树最优方案的节点,记录答案 然后它的这棵树可以向上转移给父节点,将所有子节点的左偏树合并再维护就是父节点的最优方 ...

  8. html中显示指数、底数

    在web前端开发中,经常要显示指数.底数,比如x2,loga,我们可以使用span标签,通过控制标签内字体大小,对齐方式来实现想要的效果.代码如下 <table> <tr> & ...

  9. 43-Identity MVC:UI

    1-打开之前写的MvcCookieAuthSample项目, 在AccountController新加Register,Login方法 public class AccountController : ...

  10. ABAP CDS ON HANA-(10)項目結合して一つ項目として表示

    Numeric Functions ABS(arg)  CEIL(arg) DIV(arg1, arg2) DIVISION(arg1, arg2, dec) FLOOR(arg) MOD(arg1, ...