Java系列笔记第八章:函数式编程
本文目录
第八章 函数式编程
1. 函数式接口简介
1.1 概念
有且只有一个抽象方法的接口,叫函数式接口。
因为Java中函数式编程的体现就是Lambda,所以函数式接口就是可以用于lambda使用的接口。
接口中只能有一个抽象方法,可以有其他类型的方法,比如普通私有方法、静态私有方法、默认方法等等。
语法糖:
指的是形式上使用更加简便,但是底层原理不变的代码语法。比如for-each写起来简洁,底层其实还是迭代器。
从应用层面来讲,可以说lambda是匿名内部类的语法糖,但是底层原理并不同。
匿名内部类会在编译时生成一个class文件,而lambda表达式不会,所以会降低开销。
1.2 格式
函数式接口格式:
package FunctionInterface;
public interface MyInterface {
void method();
}
1.3 @FunctionalInterface
注解
加上这个注解后,接口就强制只能有一个抽象方法。如果多写了一个抽象方法或者没抽象方法,编译会失败。
package FunctionInterface;
@FunctionalInterface
public interface MyInterface {
void method();
}
1.4 lambda 的小例子
package FunctionInterface;
public class Demo {
//参数使用函数式接口
public static void fun(MyInterface mi) {
mi.method();
}
public static void main(String[] args) {
//调用fun方法,可以传递接口的实现类方法或者匿名内部类。
fun(new MyInterface() {
@Override
public void method() {
System.out.println("匿名内部类重写接口的抽象方法。");
}
});
//方法的参数是一个函数式接口,所以可以传递进去lambda表达式
fun(
() -> System.out.println("lambda表达式重写接口的抽象方法。")
);
}
}
//===============输出===============//
匿名内部类重写接口的抽象方法。
lambda表达式重写接口的抽象方法。
2. 函数式编程
2.1 lambda的延迟执行
有的场景代码执行完毕后,结果不一定会被使用,从而造成性能浪费。而lambda是延迟执行你的,所以可以提升性能。
2.1.1 性能浪费的日志案例
日志记录代码如下:
package FunctionInterface.logDemo;
public class Logger {
public static void showLog(int level, String msg) {
if (level == 1) {
System.out.println(msg);
}
}
public static void main(String[] args) {
int level = 2;
String s1 = "a";
String s2 = "b";
String s3 = "c";
showLog(level, s1 + s2 + s3);
}
}
代码的问题:
代码中,如果level
不是1,字符串的拼接就是无用的,会浪费性能。
使用lambda来优化:
lambda的特点是延迟加载,所以先定义一个函数式接口。
lambda仅仅是传参数进去,如果level
不是1
,就不会调用接口去拼接字符串。
package FunctionInterface.logDemo;
public class Logger {
//传递函数式接口
public static void showLog(int level, LogBuilderInterface logBuilderInterface) {
//判断日志等级
if (level == 1) {
System.out.println(logBuilderInterface.buildMsg());
}
}
public static void main(String[] args) {
int level = 1;
String s1 = "a";
String s2 = "b";
String s3 = "c";
showLog(level,
() -> s1 + s2 + s3
);
}
}
2.2 函数式接口的使用
2.2.1作为方法的参数。
java.lang.Runnable
是一个函数式接口,假设有一个startThread
方法使用Runnable
接口作为参数,就可以使用lambda进行传参,效果等同与new Thread(//lambda表达式).start();
package FunctionInterface.InterAsParams;
public class Demo {
public static void startThread(Runnable runnable) {
new Thread(runnable).start();
}
public static void main(String[] args) {
//使用Thread的构造方法启动线程。
new Thread(
() -> System.out.println(Thread.currentThread().getName() + "线程启动!")
).start();
//startThread的参数是一个接口,可以使用这个接口的匿名内部类。
startThread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程启动!");
}
});
//Runnable接口是函数式接口,作为参数,使用lambda表达式启动线程。
startThread(
() -> System.out.println(Thread.currentThread().getName() + "线程启动!")
);
}
}
//===============输出===============//
Thread-1线程启动!
Thread-2线程启动!
Thread-0线程启动!
2.2.2 作为方法的返回值
最常见的应用就是比较器java.util.Comparator
。
如果一个方法的返回值类型是一个接口,那么就可以返回一个lambda表达式。
package FunctionInterface.InterAsReturn;
import java.util.Comparator;
public class Demo {
public static Comparator<Integer> CompareInt(){
return ((o1, o2) -> o1-o2);
}
}
3. 常用的函数式接口
java.util.function
包
3.1 Supplier接口
被称为生产型接口,指定的泛型是什么,接口的get方法就生产什么类型的数据。
package FunctionInterface;
import java.util.function.Supplier;
public class SupplierDemo {
public static String getMsg(Supplier<String> sup){
return sup.get();
}
public static void main(String[] args) {
System.out.println(getMsg(()->"a"+"b"));
}
}
应用:求数组最大值。
package FunctionInterface;
import java.util.function.Supplier;
public class SupplierDemo {
public static Integer getMax(Supplier<Integer> sup) {
return sup.get();
}
public static void main(String[] args) {
int[] ints = {1, 2, 3, 4, 5};
System.out.println(getMax(
() -> {
int max = ints[0];
for (int i : ints) {
max = Math.max(i, max);
}
return max;
}));
}
}
3.2 Consumer接口
消费一个数据,类型由泛型决定。
void accept(T t)
方法。
具体怎么使用,需要自定义。
package FunctionInterface;
import java.util.function.Consumer;
public class ConsumerDemo {
public static void fun(String str, Consumer<String> con) {
con.accept(str);
}
public static void main(String[] args) {
String s = "abc";
fun(s,
(str) -> System.out.println("传递进来的字符串是:" + str)
);
}
}
消费者接口的另一种写法:方法引用。
fun(s, System.out::println);
Consumer
接口有一个默认方法:andThen
。
需要两个Consumer
接口,可以把两个Consumer
接口组合在一起。
谁写在前面谁先消费。
例子:定义一个方法,传进去一个字符串和两个 Consumer
接口。
-
要么调用两次
accept
方法。 -
要么使用
andThen
方法。
package FunctionInterface.logDemo;
import java.util.function.Consumer;
public class ConsumerAndThenDemo {
public static void fun(String str, Consumer<String> consumer1, Consumer<String> consumer2) {
// consumer1.accept(str);
// consumer2.accept(str);
consumer1.andThen(consumer2).accept(str);
}
public static void main(String[] args) {
String s = "Hello World!";
fun(s,
(str) -> System.out.println(str.toLowerCase()),
(str) -> System.out.println(str.toUpperCase())
);
}
}
练习:格式化打印信息。
package FunctionInterface.logDemo;
import java.util.ArrayList;
import java.util.function.Consumer;
public class ConsumerAndThenDemo {
public static void printInfo(Consumer<String> c1, Consumer<String> c2, String[] array) {
for (String str : array) {
c1.andThen(c2).accept(str);
}
}
public static void main(String[] args) {
String[] strings = {"张三,22", "李四,21"};
printInfo(
(s -> System.out.println("姓名:" + s.split(",")[0])),
(s -> System.out.println("年龄:" + s.split(",")[1])),
strings
);
}
}
//===============输出===============//
姓名:张三
年龄:22
姓名:李四
年龄:21
3.3 Predicate
接口
3.3.1 接口的使用
对某种类型的数据进行判断,返回一个boolean
值。boolean test(T t)
package FunctionInterface;
import java.util.function.Predicate;
public class PredicateDemo {
public static boolean fun(String s, Predicate<String> predicate) {
return predicate.test(s);
}
public static void main(String[] args) {
String s = "abc";
boolean b = fun(s,
(o) -> o.contains("A")
);
System.out.println("字符串中含有A吗?:" + b);
}
}
3.3.2 接口中的三个默认方法
-
and
方法。
传入两个Predicate
接口。
package FunctionInterface.PredicateDemo;
import java.util.function.Predicate;
public class PredicateAndDemo {
public static boolean check(String str, Predicate<String> p1, Predicate<String> p2) {
return p1.and(p2).test(str);
}
public static void main(String[] args) {
String s = "Abc";
boolean b = check(s,
(o -> s.contains("A")),
(o -> s.length() > 5)
);
System.out.println("字符串中既有A,长度又大于5吗?:" + b);
}
}
-
or
方法。
return p1.or(p2).test(str);
-
negate
非操作
public static boolean check(String str, Predicate<String> p) {
return p.negate().test(str);
}
3.4 Function接口
转换类型的接口。把一个类型的数据转换为另一个类型。
Function<T, R>
,把T类型转换为R类型。
package FunctionInterface.FunctionDemo;
import java.util.function.Function;
public class Demo {
public static void fun(String s, Function<String, Integer> fun) {
System.out.println(fun.apply(s));
}
public static void main(String[] args) {
String s = "12";
fun(s,
(str) -> Integer.parseInt(str)
);
//方法引用
fun(s,
Integer::parseInt
);
}
}
Function
接口的默认方法:andThen
。用来进行组合操作。
示例:将传进来的字符串转成数字,加上10,再转为字符串返回。
package FunctionInterface.FunctionDemo;
import java.util.function.Function;
public class andThenDemo {
public static String fun(String str, Function<String, Integer> f1, Function<Integer, String> f2) {
return f1.andThen(f2).apply(str);
}
public static void main(String[] args) {
String s1 = "123";
String s2 = fun(s1,
(s -> Integer.parseInt(s) + 10),
(i -> String.valueOf(i))
);
System.out.println(s2);
}
}
(i -> String.valueOf(i))
这行代码可以使用方法引用改写为:(String::valueOf)