lambda,很基础的技术,这里做下回顾,查漏补缺。
函数式接口
写法,一般2用的比较多:
@FunctionalInterface
interface Interface1{
int countNum(int i);
}
public class LambdaDemo {
public static void main(String[] args) {
Interface1 i1 = (i) -> i * 2;
Interface1 i2 = i -> i * 2;
Interface1 i3 = (int i) -> i * 2;
Interface1 i4 = (int i) -> {
return i * 2;
};
}
}
当然,接口里面的方法只能有一个,如果多的话,也就不知道要实现哪一个了。
注解@FunctionalInterface,更多的是编译器的校验,不加也可以,表示是函数式接口,如果写多个接口会报错,所以能加就加。
而如果是下一个情况:
@FunctionalInterface
interface Interface1{
int countNum(int i);
default int add(int x , int y){
return x + y;
}
}
jdk8新增了带默认实现的方法,这个情况也是不会报错的。
看下面一个例子:
@FunctionalInterface
interface IMoneyFormat{
String format(int i);
}
class MyMoney{
private final int money;
public MyMoney(int money) {
this.money = money;
}
public void printMoney(IMoneyFormat moneyFormat){
System.out.println("存款:" + moneyFormat.format(this.money));
}
}
public class MoneyDemo {
public static void main(String[] args) {
MyMoney my = new MyMoney(9999999);
my.printMoney(i -> new DecimalFormat("#,###").format(i));
}
}
结果:
从上面可以知道,lambda不关心接口名字,实现方法是什么,只需要知道输入输出是什么格式就可以了。
所以我们可以不用写上面的接口,改造下:
class MyMoney{
private final int money;
public MyMoney(int money) {
this.money = money;
}
public void printMoney(Function<Integer , String> moneyFormat){
System.out.println("存款:" + moneyFormat.apply(this.money));
}
}
public class MoneyDemo {
public static void main(String[] args) {
MyMoney my = new MyMoney(9999999);
my.printMoney(i -> new DecimalFormat("#,###").format(i));
}
}
源码:
T:输入,R:输出。
所以我们用函数接口,就可以不用定义那么多接口了,这里再用andThen做加工:
class MyMoney{
private final int money;
public MyMoney(int money) {
this.money = money;
}
public void printMoney(Function<Integer , String> moneyFormat){
System.out.println("存款:" + moneyFormat.apply(this.money));
}
}
public class MoneyDemo {
public static void main(String[] args) {
MyMoney my = new MyMoney(9999999);
Function<Integer , String> moneyFormat = i -> new DecimalFormat("#,###").format(i);
my.printMoney(moneyFormat.andThen(s -> "人民币" + s));
}
}
结果:
接下来看下jdk8自带的函数式接口:
接口 | 输入参数 | 返回类型 | 说明 |
---|---|---|---|
Predicate<T> | T | boolean | 断言 |
Consumer<T> | T | / | 消费一个数据 |
Function<T,R> | T | R | 输入T,输出R的函数 |
Supplier<T> | / | T | 提供一个数据 |
UnaryOperator<T> | T | T | 一元函数(输入输出类型相同) |
BiFunction<T,U,R> | (T,U) | R | 2个输入的函数 |
BinaryOperator<T> | (T,T) | T | 二元函数(输入输出类型相同) |
举几个例子:
public class FunctionDemo {
public static void main(String[] args) {
//断言
Predicate<Integer> predicate = i -> i > 0;
System.out.println(predicate.test(-9));
//消费函数
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("耗子肉");
//服务函数
Supplier<String> supplier = () -> "耗子肉";
System.out.println(supplier.get());
}
}
还要注意的是,基础数据类型还有特定的函数式接口,例如:
这样就不用写泛型了,更加方便。
方法引用
正常写法:
Consumer<String> consumer = s -> System.out.println(s);
当执行体里面只有一条函数,且调用的(s)跟左边是一样的,就可以缩写成:
Consumer<String> consumer = System.out::println;
对于方法引用,也有各种形式:
1.静态方法引用(类名::方法名):
class Game{
private String name = "魔兽";
public static void start(Game game){
System.out.println(game + "启动");
}
@Override
public String toString() {
return this.name;
}
}
public class MethodRefrenceDemo {
public static void main(String[] args) {
Consumer<Game> consumer = Game::start;
Game game = new Game();
consumer.accept(game);
}
}
2.非静态方法引用(使用对象实例的方法):
class Game{
//默认10小时
private int time = 10;
public int time(int cost){
System.out.println("玩了" + cost + "小时");
this.time -= cost;
return this.time;
}
}
public class MethodRefrenceDemo {
public static void main(String[] args) {
Game game = new Game();
Function<Integer , Integer> function = game::time;
System.out.println("剩余时间:" + function.apply(3) + "小时");
}
}
结果:
当然,Function出入参都是一样的,根据上面表格,我们可以改写为(当然还可以改成IntUnaryOperator):
UnaryOperator<Integer> function = game::time;
3.使用类名::方法名引用非静态方法:
public class MethodRefrenceDemo {
public static void main(String[] args) {
Game game = new Game();
BiFunction<Game , Integer , Integer> function = Game::time;
System.out.println("剩余时间:" + function.apply(game ,3) + "小时");
}
}
因为对于非静态方法,jdk会默认把this对象传过去,所以这里我们需要传对象。
4.构造函数:
public class MethodRefrenceDemo {
public static void main(String[] args) {
Supplier<Game> supplier = Game::new;
System.out.println("新对象:" + supplier.get());
}
}
结果:
5.有参构造器:
class Game{
private String name = "魔兽";
public Game(String name) {
this.name = name;
}
@Override
public String toString() {
return "Game{" +
"name='" + name + '\'' +
'}';
}
}
public class MethodRefrenceDemo {
public static void main(String[] args) {
Function<String , Game> function = Game::new;
System.out.println("创建了新对象:" + function.apply("炉石"));
}
}
结果:
类型推断
我们知道lambda是匿名函数,返回实现指定接口的对象,所以我们需要告诉他我们具体实现哪个接口,这就是类型推断。
有如下方式:
@FunctionalInterface
interface ICount{
int add(int x , int y);
}
public class TypeDemo {
public static void main(String[] args) {
//变量类型定义
ICount lambda = (x , y) -> x + y;
//数组里
ICount[] lambdas = {(x , y) -> x + y};
//强转
Object lambda2 = (ICount)(x , y) -> x + y;
//通过返回类型
ICount lambda3 = createLambda();
}
public static ICount createLambda(){
return (x , y) -> x + y;
}
}
变量引用
先看代码
public class VarDemo {
public static void main(String[] args) {
String str = "我叫";
Consumer<String> consumer = s -> System.out.println(str + s);
consumer.accept("耗子肉");
}
}
其中,如果把str改成别的:
也就是说,虽然str没有加final,但实际上jdk8默认加了final。
级联表达式和柯里化
级联表达式:下面代码可以看到,有多个箭头,这就是级联表达式
public class CurryDemo {
public static void main(String[] args) {
Function<Integer , Function<Integer , Integer>> function =
x -> y -> x + y;
System.out.println(function.apply(2).apply(3));
}
}
柯里化:把多个参数的函数转换为只有一个参数的函数。他可以让函数标准化。
public class CurryDemo {
public static void main(String[] args) {
Function<Integer , Function<Integer , Function<Integer , Integer>>> function =
x -> y -> z -> x + y + z;
System.out.println(function.apply(2).apply(3).apply(4));
//也可以写成
int[] nums = {2 , 3 , 4};
Function function2 = function;
for (int i = 0 ; i < nums.length ; i ++){
if (function2 instanceof Function){
Object obj = function2.apply(nums[i]);
if (obj instanceof Function){
function2 = (Function) obj;
}else {
System.out.println("调用结束:" + obj);
}
}
}
}
}