Java 8 新特征——Lambda表达式和函数式接口

Lambda 表达式语法

在java8中引入了一个新的操作符,“->”,这个操作符箭头操作符或者Lambda操作符,并且箭头操作符将Lambda表达式拆分为两部分。
a:箭头左侧为Lambda表达式的参数列表。
b:箭头右侧为Lambda表达式的Lambda体,就是表达式中所需要执行的功能。

Lambda表达式的标准格式为:

(参数类型 参数名称) -> { 代码语句 }

格式说明:

  • 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
  • ->是新引入的语法格式,代表指向动作。
  • 大括号内的语法与传统方法体要求基本一致。

使用Lambda标准格式(无参无返回)

public interface Cook {
    void makeFood();
}
public class Demo05InvokeCook {
    public static void main(String[] args) {
    invokeCook(() -> {
      	System.out.println("吃饭啦!");
    });
}
    private static void invokeCook(Cook cook) {
        cook.makeFood();
    }
}

使用Lambda标准格式(有参有返回)

public interface Calculator {
    int calc(int a, int b);
}
public class Demo08InvokeCalc {
   public static void main(String[] args) {
    invokeCalc(120, 130, (int a, int b) -> {
      	return a + b;
    });
}

    private static void invokeCalc(int a, int b, Calculator calculator) {
        int result = calculator.calc(a, b);
        System.out.println("结果是:" + result);
    }
}

Lambda省略格式

省略规则

在Lambda标准格式的基础上,使用省略写法的规则为:

  1. 小括号内参数的类型可以省略;
  2. 如果小括号内有且仅有一个参,则小括号可以省略;
  3. 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。

Lambda的使用前提

Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:

  1. 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法
    无论是JDK内置的RunnableComparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。
  2. 使用Lambda必须具有上下文推断
    也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
  3. 可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。

备注:有且仅有一个抽象方法的接口,称为“函数式接口”。

函数式接口

概念

函数式接口在Java中是指:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。

备注:“语法糖”是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实 底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java中的Lambda可以被当做是匿名内部 类的“语法糖”,但是二者在原理上是不同的。

格式

只要确保接口中有且仅有一个抽象方法即可:

修饰符 interface 接口名称 { 
public abstract 返回值类型 方法名称(可选参数信息); 
// 其他非抽象方法内容 
}

由于接口当中抽象方法的 public abstract 是可以省略的,所以定义一个函数式接口很简单:

@FunctionalInterface注解

与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注 解可用于一个接口的定义上:

@FunctionalInterface 
public interface MyFunctionalInterface {
 void myMethod(); 
 }

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

作用:

方便直接用Lambda表达式构造出实例,让代码更加简洁。

Java 内置四大核心函数式接口

函数式接口参数类型返回类型用途
Consumer 消费型接口Tvoid对类型为T的对象应用操 作,包含方法: void accept(T t)
Supplier 供给型接口T返回类型为T的对象,包 含方法:T get();
Function<T, R> 函数型接口TR对类型为T的对象应用操 作,并返回结果。结果 是R类型的对象。包含方 法:R apply(T t);
Predicate 断言型接口Tboolean确定类型为T的对象是否 满足某约束,并返回 boolean 值。包含方法 boolean test(T t);

1. 消费型接口 Consumer :

抽象方法:

    void accept(T t):接收一个参数进行消费,但无需返回结果。

默认方法:
为了下面能更好地解释,这里先假设有两个Consumer实例:c1,c2
default Consumer andThen(Consumer<? super T> after):c1.andThen(c2).apply(arg),c1消费完arg后,再将arg传给c2消费。

例子:

public class ConsumerDemo {
 
    @Test
    public void cutHand() {
        Goods goods = new Goods("口红", 288);
 
        //土豪
        spentMoney(goods, (g) -> System.out.println("消费" + g.getCost() + "元"));
 
        System.out.println("-------------------贫富分割线--------------------");
 
        //屌丝
        spentMoneyAndLog(goods, (g) -> System.out.println("消费" + g.getCost() + "元"));
    }
    
    //任性地花
    public void spentMoney(Goods goods, Consumer<Goods> consumer) {
        consumer.accept(goods);
    }
 
    //花一笔记一笔
    public void spentMoneyAndLog(Goods goods, Consumer<Goods> consumer) {
        Consumer<Goods> logConsumer = (g) -> System.out.println("买" + g.getGoodsName() + "用了" + g.getCost() + "元!");
        consumer.andThen(logConsumer).accept(goods);
    }
}
 
运行结果:
消费288.0-------------------贫富分割线--------------------
消费288.0元
买口红用了288.0元!

2. 供给型接口 Supplier :

抽象方法:

    T get():返回一个自定义数据

例子:

public class SupplierDemo {
    @Test
    public void luckDay() {
        Supplier<String> girlWish = () -> "美女";
        Supplier<String> moneyWish = () -> "钱";
 
        String girl = magicLamp(girlWish);
        String money = magicLamp(moneyWish);
 
        System.out.println(girl + "---" + money);
    }
 
    //你想要什么神灯就给你什么
    public String magicLamp(Supplier<String> wish) {
        return wish.get();
    }
}
 
运行结果:
美女---

3. 函数型接口 Function<T,R> :

抽象方法:

    R apply(T t):传入一个参数,返回想要的结果。

默认方法:

为了下面能更好地解释,这里先假设有两个Function实例:f1,f2

default Function<V, R> compose(Function<? super V, ? extends T> before):f1.compose(f2).apply(arg),表示先执行f2,然后将得到的结果传给f1执行。

default Function<T,V> andThen(Function<? super R,? extends V> after):f1.andThen(f2).apply(arg),表示先执行f1,然后将得到的结果传给f2执行。

静态方法:

static Function<T, T> identity():获取到一个输入参数和返回结果一样的Function实例

例子:

public class FunctionDemo {
 
    @Test
    public void life() {
        //第一次,妈妈给小明10元去买酱油
        double tips = firstBuy(10, (m) -> 10 - getSoy().getCost());
        System.out.println("小明得到的小费:" + tips);
 
        //第二次,妈妈还是给小明10元买酱油,小明思考了一下,拒绝了
        System.out.println("小明将妈妈给的" + Function.identity().apply(10) + "元还了回去");
 
        //妈妈在了解完情况后,给了小明20元去买,小明当然很愉快去了
        double tips2 = secondBuy(20, (m) -> {
            System.out.println("买酱油前有" + m + "元");
            double v2 = m - getSoy().getCost();
            System.out.println("买完酱油后剩下" + v2 + "元");
            return v2;
        });
        System.out.println("小明剩下的小费:" + tips2);
 
    }
 
    public double firstBuy(double money, Function<Double, Double> buy) {
        return buy.apply(money);
    }
 
    public double secondBuy(double money, Function<Double, Double> buy) {
        //在去的路上小明先买了冰淇淋
        Function<Double, Double> beforeBuy = (m) -> {
            System.out.println("第一次买冰淇淋前有" + m + "元");
            double v1 = m - getIceCream().getCost();
            System.out.println("买完冰淇淋后剩下" + v1 + "元");
            return v1;
        };
 
        //回来的路上小明又买了冰淇淋
        Function<Double, Double> afterBuy = (m) -> {
            System.out.println("第二次买冰淇淋前有" + m + "元");
            double v3 = m - getIceCream().getCost();
            System.out.println("买完冰淇淋后剩下" + v3 + "元");
            return v3;
        };
 
        return buy.compose(beforeBuy).andThen(afterBuy).apply(money);
    }
 
    public Goods getSoy() {
        return new Goods("酱油", 10);
    }
 
    public Goods getIceCream() {
        return new Goods("冰淇淋", 5);
    }
}
 
运行结果:
小明得到的小费:0.0
小明将妈妈给的10元还了回去
第一次买冰淇淋前有20.0元
买完冰淇淋后剩下15.0元
买酱油前有15.0元
买完酱油后剩下5.0元
第二次买冰淇淋前有5.0元
买完冰淇淋后剩下0.0元
小明剩下的小费:0.0

4. 断言型接口 Predicate :

抽象方法:

    boolean test(T t):传入一个参数,返回一个布尔值

默认方法:

为了下面能更好地解释,这里先假设有两个Predicate实例:p1,p2

default Predicate negate():表示 ! p1.test()

default Predicate and(Predicate<? super T> other):p1.and(p2).test(arg),表示p1.test(arg) && p2.test(arg)

default Predicate or(Predicate<? super T> other):p1.or(f2).test(arg),表示p1.test(arg) || p2.test(arg)

静态方法:

static Predicate isEqual(Object targetRef):获取到一个Predicate实例p,p.test(arg) 表示targetRef 是否等于arg

例子:

public class PredicateDemo {
 
    @Test
    public void test(){
        Predicate<String> p1=(t)->t.equals("nice");
        Predicate<String> p2=(t)->t.endsWith("e");
 
        boolean result1 = p1.test("nice");
        System.out.println(result1);
 
        boolean result2 = p1.negate().test("nice");
        System.out.println(result2);
 
        boolean result3 = p1.and(p2).test("nice");
        System.out.println(result3);
 
        boolean result4 = p1.or(p2).test("good");
        System.out.println(result4);
 
        Predicate<String> p = Predicate.isEqual("当这个参数为null,使用==判断,否则使用equal方法判断");
        boolean result5 = p.test("end");
        System.out.println(result5);
    }
}
 
运行结果:
true
false
true
false
false

5. 其他类似接口

UnaryOperator:一元操作符,Function的子类,只是该接口的输入参数和返回结果必须是同一类型。

BiFunction<T, U, R>:比Function高级一点,可以接收两个参数,应用后也还是返回一个结果。

BiConsumer<T,U>:同样比Consumer高级一点,可以接收两个参数进行消费而不需返回结果。

BiPredicate<T,U>:同样比Predicate高级一点,可以接收两个参数,判断后也还是返回一个bool值。

总体的,可以参考网上这张图,图中绿色表示主要引入的新接口,其他接口基本上都是为了支持基本类型而添加的接口。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值