Java 8(二):Lambda 表达式

1、认识Lambda表达式

Lambda 表达式可以理解为一种匿名函数,它没有名称,但它有参数列表、方法主体、返回类型等

1.1、Lambda表达式语法

lambda 表达式的语法格式如下:

(parameters) -> expression
或
(parameters) ->{ statements; }

在这里插入图片描述

lambda 表达式的语法由参数列表、箭头符号 -> 和函数体组成。函数体既可以是一个表达式,也可以是一个语句块:

表达式: 表达式会被执行然后返回执行结果。

语句块: 语句块中的语句会被依次执行,就像方法中的语句一样

  • return 语句会把控制权交给匿名方法的调用者
  • break 和 continue 只能在循环中使用
  • 如果函数体有返回值,那么函数体内部的每一条路径都必须返回值

表达式函数体 适合小型 lambda 表达式,它消除了 return 关键字,使得语法更加简洁。

以下是lambda表达式的重要特征:

可选类型声明: 不需要声明参数类型,编译器可以统一识别参数值。

可选的参数圆括号: 一个参数无需定义圆括号,但多个参数需要定义圆括号。

可选的大括号: 如果主体包含了一个语句,就不需要使用大括号。

可选的返回关键字: 如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

1.2、Lambda 表达式示例

// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值 
(String s) -> System.out.print(s)

2、函数式接口(Functional interfaces)

在哪里可以使用lambda表达式呢?答案是:在函数式接口上使用lambda表达式。是否还记得在上一节 Java 8(一):行为参数化中的示例吗?其中 Predicate 接口就是一个函数式接口,因为 Predicate 仅仅定义了一个抽象方法:

interface Predicate<T>{
    boolean test(T t);
}

函数式接口: 就是只定义了一个抽象方法的接口。例如,Java API 中的Comparator,Runnable,Callable 等。

Java SE 7 中已经存在的函数式接口:

Java SE 8中增加了一个新的包:java.util.function,它里面包含了常用的函数式接口:

  • Predicate——接收 T 并返回 boolean
  • Consumer——接收 T,不返回值
  • Function<T, R>——接收 T,返回 R
  • Supplier——提供 T 对象(例如工厂),不接收值
  • UnaryOperator——接收 T 对象,返回 T
  • BinaryOperator——接收两个 T,返回 T

2.1、Predicate

java.util.function.Predicate接口定义了一个名叫test的抽象方法,它接受一个泛型T对象,并返回一个boolean

@FunctionalInterface
public interface Predicate<T> {
     boolean test(T t);
}

使用示例如下:

public static void main(String[] args) {
        //非空字符串判断,lambda表达式
        Predicate<String> nonEmptyStringPredicate = s -> !s.isEmpty();
        //过滤非空字符串,lambda表达式作为参数传递
        List<String> nonEmptyStringLis = filter(Arrays.asList("hello", "world", ""), nonEmptyStringPredicate);
        System.out.println(nonEmptyStringLis);
    }

    public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
        List<T> results = new ArrayList<>();
        for (T s : list) {
            if (predicate.test(s)) {
                results.add(s);
            }
        }
        return results;
    }

2.2、Consumer

java.util.function.Consumer定义了一个叫accept的抽象方法,它接受一个泛型T的对象,并没有返回(void)

@FunctionalInterface
public interface Consumer<T> {
     void accept(T t);
}

使用示例:

/**
 * java.util.function.Consumer<T>定义了一个叫accept的抽象方法,它接受一个泛型T的对象,并没有返回(void)
 */
public class ConsumerDemo {
    public static void main(String[] args) {
        //遍历对象集合,并打印每一个元素
        forEach(Arrays.asList(2, 4, 9), System.out::println);
    }

    /**
     * 根据 consumer 遍历对象
     */
    public static <T> void forEach(List<T> list, Consumer<T> consumer) {
        for (T t : list) {
            consumer.accept(t);
        }
    }
}

2.3、Function

java.util.function.Function<T, R>定义了一个叫apply的抽象方法,它接受一个泛型T的对象,并返回一个泛型R对象

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

使用示例:

/**
 * java.util.function.Function<T, R>定义了一个叫apply的抽象方法,它接受一个泛型T的对象,并返回一个泛型R对象
 */
public class FunctionDemo {
    public static void main(String[] args) {
        //计算集合中的每一个元素的长度
        List<Integer> strLength = map(Arrays.asList("hello,world", "lambda"), s -> s.length());
        System.out.println(strLength);

    }

    /**
     * 遍历 list 并转换每一个元素,返回转换后的集合
     *
     * @param list     被操作对象集合
     * @param function 转换表达式
     */
    public static <T, R> List<R> map(List<T> list, Function<T, R> function) {
        List<R> result = new ArrayList<>();
        for (T t : list) {
            result.add(function.apply(t));
        }
        return result;
    }
}

3、类型检查

Lambda 的类型是从Lambda 的上下文推断出来的。上下文中Lambda表达式需要的类型称为***目标类型***。

在这里插入图片描述

4、类型推断

还可以进一步简化代码。java编译器会从上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名,因为函数描述符可以通过目标类型来获取。这样就可以在Lambda语法中省去参数类型的声明。

       //需要类型推断,仅有一个参数需要类型推断时,参数两边的括号可以省略
        List<Apple> greenApple = appleList.stream().filter(a -> a.getColor().equals("green")).collect(Collectors.toList());
        //需要类型推断
        Comparator<Apple> apple1 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
        //不需要类型推断
        Comparator<Apple> apple2 = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());

5、变量作用域

Lamdba 可以没有限制的使用实例变量和静态变量。但是局部变量必须显示声明为final或者事实上是final(即一个变量虽然没被final修饰,但是他在后面也没被修改)。

下面的代码将会编译错误,因为portNumber变量被赋值了两次

int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
//编译错误
portNumber = 31337;

5.1、Lambda使用局部变量的限制

Lambda表达式对值封闭,不是对变量封闭。Lambda表达式引用的局部变量必须是final的,只能有一次赋值。

5.2、为什么对局部变量限制

实例变量是储存在堆中,而局部变量是储存在栈上。如果Lambda可以直接访问局部变量,而Lambda是在一个线程中使用的,则使用Lambda的线程可能会在分配该变量的线程将这个变量收回之后,去访问该变量。因此,Java在访问自由局部变量时,实际上是在访问他的副本而不是访问原始变量。

6、方法引用

方法引用就是Lambda的快捷写法。目标引用放在分隔符::前面,方法的名称放在后面。例如,Apple::getWeight 就是引用了Apple类中一个的getWeight方法。这里时不需要括号的,因为并没有实际调用该方法。

使用方法引用之前:

Comparator<Apple> apple2 = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());

使用方法引用之后:

Comparator<Apple> apple3 = Comparator.comparing(Apple::getWeight);

Lambda表达式及其等价方法引用:

Lambda表达式等效的方法引用
(Apple a) -> a.getWeight()Apple::getWeight
() -> Thread.currentThread().dumpStack()Thread.currentThread()::dumpStack
(str, i) -> str.substring(i)String::substring
(String s) -> System.out.println(s)System.out::println

6.1、如何构建方法引用

方法引用主要有三类:

(1)指向静态方法的方法引用

例如:Integer 的 parseInt 方法,等价于 Integer::parseInt

(2)指向任意类型实例方法的引用

例如:String 的 length 方法,等价于 String::length

(3)指向现有对象的实例方法的引用

例如:局部变量 apple 用来存放 Apple 类型的对象,它的实例方法是 getWeight,那么方法引用就可以这样写:apple::getWeight

6.2、Lambda构造函数引用

对于一个现有构造函数,可以利用它的名称和关键字new来创建它的一个引用:ClassName::new。它的功能与静态方法的引用类似。

Lambda表达式等价构造函数引用Lambda表达式

Supplier<Apple> c1 = Apple::new;

Apple a1 = c1.get();

Supplier<Apple> c1 = () -> new Apple();

Apple a1 = c1.get();

Function<Integer, Apple> c2 = Apple::new;

Apple a2 = c2.apply(110);

Function<Integer, Apple> c2 = (weight) -> new Apple(weight);

Apple a2 = c2.apply(110);

BiFunction<String, Integer, Apple> c2 = Apple::new;

Apple a2 = c2.apply("green", 110);

BiFunction<String, Integer, Apple> c2 = (color, weight) -> new Apple(color, weight);

Apple a2 = c2.apply("green", 110);

7、复合Lambda表达式

把多个简单的Lambda表达式复合成复杂的表达式,可以让两个谓词or操作,组成一个更大的谓词,可以让一个函数的结果成为另一个函数的输入参数。

复合类型方法说明举例
比较器复合reversed()逆序

//按重量递减排序

inventory.sort(comparing(Apple::getWeight))

.reversed();

thenComparing比较器链(接受一个函数作为参数,如果两个对象用第一个Comparator比较之后是一样的,就提供第二个Comparator

//两个苹果一样重时按国家排序

inventory.sort(comparing(Apple::getWeight))

.reversed().

thenComparing(Apple::getCountry);

谓词复合

(and和or方法的优先级是按照在表达式链中的位置,从左向右确定的)

negate

//产生现有对象redApple的非

Predicate<Apple> notRedApple = redApple.negate();

and将两个Lambda用and组合起来

//一个苹果既是红色又比较重(链接两个谓词来生成一个Predicate对象)

Predicate<Apple> RedAndHeavyApple = redApple.and(a -> a.getWeight() > 150);

or要么

//要么重(150g以上)的红苹果,要么是绿苹果

Predicate<Apple> RedAndHeavyApple = redApple.and(a -> a.getWeight() > 150)

                                                               .or(a -> "green".equals(a.getColor()));

函数复合andThen先对输入应用一个给定函数,再对输出应用另一个函数

//等同于数学上的g(f(x)),返回4

Function<Integer, Integer> f = x -> x + 1;

Function<Integer, Integer> g = x -> x * 2;

Function<Integer, Integer> h = f.andThen(g);

int result = h.apply(1);

compose先把给定的函数用作compose的参数里面给的那个函数,然后再把函数本身用于结果

//等同于数学上的f(g(x)),返回3

Function<Integer, Integer> f = x -> x + 1;

Function<Integer, Integer> g = x -> x * 2;

Function<Integer, Integer> h = f.compose(g);

int result = h.apply(1);

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值