Lambda介绍与使用

  • Post author:
  • Post category:其他


java8为什么引入lambda?

在java8出现之前,如果想传递一段代码到一个方法里是很不方便的,你几乎不可能将代码块到处传递,因为Java是一个面向对象的语言,因此你要构建一个属于某个类的对象,由它的某个方法来放置你想传递的代码块。

下面看两个非常典型的例子,构造线程与比较器:

构造线程

我们要想在另一个线程中执行一些代码逻辑时,通常会将代码放在一个实现Runnable接口的run方法当中

public static void main(String[] args) {
    myThread t = new myThread();
    t.start();
}
 
class myThread implements Runnable {
    @Override
    public void run() {
        System.out.println("放入你想执行的代码");
    }
}

你写这段代码的目的就是想开启新的线程来执行你定制的代码,

为此你创建了myThread

接着我们看下构造比较器:

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    list.add(2);
    list.add(5);
    list.add(1);
    list.add(4);
    list.add(3);
 
    Comparator<Integer> comparator = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    };
    Collections.sort(list, comparator);
}

sort 方法会不断调用compare 方法,对顺序不对的元素进行重新排序,直到有序为止。你写比较器的目的就是给Collections的sort方法传递一段比较逻辑的代码片段,这段代码会被整合到排序排序逻辑中,

为此你写了一个类和一个方法

上述两个例子你会看到它们的相似点,

将一段代码传递给其他调用者

(一个线程池或者一个排序方法),这段代码会在新的方法中被调用。

但是,在Java8出现之前,如果你想传递一段代码到另一个方法里是很不方便的,因为Java是一个面向对象的语言,因此你要构建一个属于某个类的对象,由它的某个方法来放置你想传递的代码块。而在一些其他语言中可以直接传递代码块,比如JS,以及C/C++语言的函数指针,为此,Java决定加入了lambda表达式的语法糖。

lambda表达式的语法

Lambda表达式的核心就是

函数式接口

什么是函数式接口?

一个接口中只定义了一个抽象函数,称为函数式接口。

java8中引入了函数式接口新特性,使用@FunctionalInterface标识,表示有且只有一个抽象方法,但可以有多个非抽象方法(default,static)。

为什么需要函数式接口?

java中方法是不能作为参数传给另一个方法的,方法的调用是需要引用载体的(类、对象、接口),但为了执行一个方法就去新建一个类或者对象,是很繁琐的,因此接口就充当了这个载体。

为什么Lambda表达式是长这样的

()->


{}

?

当一个接口中只有一个函数的时候,当用到这个接口时,我们都不需要关心方法名是什么,我们关心的是参数和函数体。于是

(参数)->{函数体}

的样子就出来了,并起了个名字为Lambda表达式。

语法

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

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

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


可选类型声明:

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


可选的参数圆括号:

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


可选的大括号:

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


可选的返回关键字:

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

lambda表达式的例子:

public class Java8Tester {
   public static void main(String args[]){
      Java8Tester tester = new Java8Tester();
         
      // 类型声明
      MathOperation addition = (int a, int b) -> a + b;
         
      // 不用类型声明
      MathOperation subtraction = (a, b) -> a - b;
         
      // 大括号中的返回语句
      MathOperation multiplication = (int a, int b) -> { return a * b; };
         
      // 没有大括号及返回语句
      MathOperation division = (int a, int b) -> a / b;
         
      System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
      System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
      System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
      System.out.println("10 / 5 = " + tester.operate(10, 5, division));
         
      // 不用括号
      GreetingService greetService1 = message ->
      System.out.println("Hello " + message);
         
      // 用括号
      GreetingService greetService2 = (message) ->
      System.out.println("Hello " + message);
         
      greetService1.sayMessage("Runoob");
      greetService2.sayMessage("Google");
   }
     
   interface MathOperation {
      int operation(int a, int b);
   }
     
   interface GreetingService {
      void sayMessage(String message);
   }
     
   private int operate(int a, int b, MathOperation mathOperation){
      return mathOperation.operation(a, b);
   }
}

执行以上脚本,输出结果为:


$ javac Java8Tester.java
$ java Java8Tester
10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Runoob
Hello Google

变量作用域

lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

public class Java8Tester {
  
   final static String salutation = "Hello! ";
    
   public static void main(String args[]){
      GreetingService greetService1 = message ->
      System.out.println(salutation + message);
      greetService1.sayMessage("Runoob");
   }
     
   interface GreetingService {
      void sayMessage(String message);
   }
}

执行以上脚本,输出结果为:

$ javac Java8Tester.java
$ java Java8Tester
Hello! Runoob

我们也可以直接在 lambda 表达式中访问外层的局部变量:

public class Java8Tester {
    public static void main(String args[]) {
        final int num = 1;
        Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
        s.convert(2);  // 输出结果为 3
    }
  
    public interface Converter<T1, T2> {
        void convert(int i);
    }
}

lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)


int num = 1; 
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);
num = 5; 
//报错信息:Variable used in lambda expression should be final or effectively final

在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。

String first = ""; 
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length());  //编译会出错

方法引用


对象::实例方法,将lambda的参数当做方法的参数使用

objectName::instanceMethod

示例:

Consumer<String> sc = System.out::println;
//等效
Consumer<String> sc2 = (x) -> System.out.println(x);
sc.accept("618, 狂欢happy");



类::静态方法

,将lambda的参数当做方法的参数使用

ClassName::staticMethod

示例:

//ClassName::staticMethod  类的静态方法:把表达式的参数值作为staticMethod方法的参数
Function<Integer, String> sf = String::valueOf;
//等效
Function<Integer, String> sf2 = (x) -> String.valueOf(x);
String apply1 = sf.apply(61888);


类::实例方法

,将lambda的第一个参数当做方法的调用者,其他的参数作为方法的参数。开发中尽量少些此类写法,减少后续维护成本。

ClassName::instanceMethod

示例:

//ClassName::instanceMethod  类的实例方法:把表达式的第一个参数当成instanceMethod的调用者,其他参数作为该方法的参数
BiPredicate<String, String> sbp = String::equals;
//等效
BiPredicate<String, String> sbp2 = (x, y) -> x.equals(y);
boolean test = sbp.test("a", "A");

构造函数


无参

的构造方法就是

类::实例方法模型,如:


Supplier<User> us = User::new;
//等效
Supplier<User> us2 = () -> new User();
//获取对象
User user = us.get();

当有参数时:

//一个参数,参数类型不同则会编译出错
Function<Integer, User> uf = id -> new User(id);
//或加括号
Function<Integer, User> uf2 = (id) -> new User(id);
//等效
Function<Integer, User> uf3 = (Integer id) -> new User(id);
User apply = uf.apply(61888);
 
//两个参数
BiFunction<Integer, String, User> ubf = (id, name) -> new User(id, name);
User user = ubf.apply(618, "狂欢happy");

问题1:lambda表达式中为什么要求外部变量为final?

问题2:lambda表达式是不是就是匿名内部类的一种简写方式?

参考文章:

【小家java】使用lambda表达式传参是否有性能问题?_方向盘(YourBatman)的博客-CSDN博客

lambda表达式的使用

构造线程与比较器的改进

public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(2);
        list.add(5);
        list.add(1);
        list.add(4);
        list.add(3);
 
        Collections.sort(list, (one,two)->{
            return two - one;
        });
    }

编译器会解析出来 one 与 two 是原先接口compare方法的入参,并自动赋予Integer类型。

public static void main(String[] args) {
    new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            System.out.println("这是一个线程" + i);
        }
    }).start();
 
    for (int i = 100; i < 200; i++) {
        System.out.println("这是主线程" + i);
    }
}

Stream API的使用

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

import org.junit.Test;
 
import java.io.Serializable;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
 
/**
 * @author shuai.ding
 * @date 2022年04月04日 下午9:47
 */
public class CollectionTester {
    @Test
    public void test1(){
        List<String> list = Arrays.asList("a12", "b2222", "c");
 
        // 使用增强for循环实现
        for (String s : list) {
            if (s.length() > 3) {
                System.out.println("使用增强for循环实现:" + s);
            }
        }
 
        // 使用匿名内部类实现Comsumer接口
        list.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                if (s.length() > 3) {
                    System.out.println("使用Consumer内部类实现:" + s);
                }
            }
        });
 
        // lambda写法
        list.forEach(s -> {
            if (s.length() > 3) {
                System.out.println("使用lambda表达式实现:" + s);
            }
        });
    }
 
    @Test
    public void test2(){
        List<String> list = Arrays.asList("a", "b", "c");
        List<String> uppercaseNames = new ArrayList<>();
 
//        list.forEach(name -> uppercaseNames.add(name.toUpperCase()));
//        System.out.println(uppercaseNames);
 
        list.stream().map(name -> name.toUpperCase())
                .forEach(name -> System.out.println(name + " "));
    }
 
    /**
     * 寻找元素
     */
    @Test
    public void test3(){
        List<String> list = Arrays.asList("NICE", "HELLO", "HAPPY");
        List<String> startWithN = list.stream().filter(name -> name.startsWith("N"))
                .collect(Collectors.toList());
        System.out.println(startWithN);
    }
 
    /**
     * lambda表达式重用
     */
    @Test
    public void test4(){
        List<String> list = Arrays.asList("NICE", "HELLO", "HAPPY");
 
//        final Function<String, Predicate<String>> startsWithLetter = (String letter) -> {
//            Predicate<String> checkStartWith = (String name) -> name.startsWith(letter);
//            return checkStartWith;
//        };
 
        //简写
//        final Function<String, Predicate<String>> startsWithLetter = (String letter) -> (String name) -> name.startsWith(letter);
        final Function<String, Predicate<String>> startsWithLetter = letter -> name -> name.startsWith(letter);
 
        List<String> startWithNList = list.stream().filter(startsWithLetter.apply("N")).collect(Collectors.toList());
        List<String> startWithHList = list.stream().filter(startsWithLetter.apply("H")).collect(Collectors.toList());
        System.out.println(startWithNList);
        System.out.println(startWithHList);
    }
 
    /**
     * 挑选元素
     */
    @Test
    public void test5(){
        List<String> list = Arrays.asList("NICE", "HELLO", "HAPPY");
        String startingLetter = "a";
 
        Optional<String> foundName = list.stream().filter(name -> name.startsWith(startingLetter))
                .findFirst();
        System.out.println(String.format("list name starting with %s:%s",startingLetter,foundName.orElse("No name found")));
    }
 
 
    /**
     * 集合规约
     */
    @Test
    public void test6(){
        List<String> list = new ArrayList<>(Arrays.asList("NICE", "HELLO", "HAPPY"));
        System.out.println("Total number of characters is all names: "+
                list.stream().mapToInt(String::length).sum());
 
        //获取集合中,最长的名字
        Optional<String> aLongName =  list.stream().reduce((name1,name2) -> name1.length() >= name2.length() ? name1 : name2);
        aLongName.ifPresent(name -> System.out.println(String.format("A longest name:%s",name)));
    }
 
    /**
     * 元素连接
     */
    @Test
    public void test7(){
        List<String> list = Arrays.asList("NICE", "HELLO", "HAPPY");
        System.out.println(String.join(",",list));
        System.out.println(list.stream().map(String::toUpperCase).collect(Collectors.joining(",")));
    }
 
    /**
     * list转map
     */
    @Test
    public void test8(){
        List<Account> list = new ArrayList<>();
        Account account1 = new Account(1, "zhangsan");
        Account account2 = new Account(2, "lisi");
        Account account3 = new Account(3, "lisi");
        list.add(account1);
        list.add(account2);
        list.add(account3);
 
        //常规操作
        Map<Integer,String> map = list.stream().collect(Collectors.toMap(Account::getId,Account::getUserName));
        System.out.println(map);
 
        //收集实体本身map
//        Map<Integer,Account> map2 = list.stream().collect(Collectors.toMap(Account::getId,account -> account));
        Map<Integer,Account> map2 = list.stream().collect(Collectors.toMap(Account::getId, Function.identity()));
        System.out.println(map2);
 
        //重复key的情况
//        Map<String,Account> map3 = list.stream().collect(Collectors.toMap(Account::getUserName,Function.identity()));
        Map<String,Account> map3 = list.stream().collect(Collectors.toMap(Account::getUserName,Function.identity(),(key1,key2) -> key2));
        System.out.println(map3);
 
        //指定具体收集的map
        Map<String,Account> map4 = list.stream().collect(Collectors.toMap(Account::getUserName,Function.identity(),(key1,key2) -> key2, LinkedHashMap::new));
        System.out.println(map4);
    }
 
 
    /**
     * 去重
     */
    @Test
    public void test9(){
        List<String> list = Arrays.asList("123","456","789","123","666");
        list.stream().distinct().forEach(System.out::println);
    }
 
    /**
     * 排序
     */
    @Test
    public void test10(){
        List<Account> list = new ArrayList<>();
        Account account1 = new Account(1, "zhangsan");
        Account account2 = new Account(2, "lisi");
        Account account3 = new Account(2, "lisi2");
        Account account4 = new Account(4, "wanger");
        list.add(account1);
        list.add(account2);
        list.add(account3);
        list.add(account4);
 
        list.stream()
//                .sorted(Comparator.comparingInt(Account::getId))
//                .sorted(Comparator.comparing(Account::getUserName))
                .sorted((a1,a2) ->a2.getId() - a1.getId())
                .forEach(System.out::println);
 
    }
 
    /**
     * 返回前n个元素
     */
    @Test
    public void test11(){
        List<String> list = Arrays.asList("333","222","111");
        list.stream().limit(2).forEach(System.out::println);
    }
 
    /**
     * 删除前n个元素
     */
    @Test
    public void test12(){
        List<String> list = Arrays.asList("333","222","111");
        list.stream().skip(2).forEach(System.out::println);
    }
 
    /**
     * 求最值
     */
    @Test
    public void test13(){
        List<String> list = Arrays.asList("333","222","1114");
        System.out.println(list.stream().min(String::compareTo).get());
        System.out.println(list.stream().max(String::compareTo).get());
    }
 
    /**
     * 匹配
     */
    @Test
    public void test14(){
        List<Account> list = new ArrayList<>();
        Account account1 = new Account(1, "zhangsan");
        Account account2 = new Account(2, "lisi");
        Account account3 = new Account(3, "wanger");
        list.add(account1);
        list.add(account2);
        list.add(account3);
        list.add(account3);
 
        boolean anyMatch = list.stream().anyMatch(a -> a.getId() > 1);
        if(anyMatch) System.out.println("有id大于1的记录");
 
        boolean allMath = list.stream().allMatch(a -> a.getId() > 1);
        if(allMath) System.out.println("所有记录id都大于1");
 
        boolean noneMatch = list.stream().noneMatch(a -> a.getUserName().equals("ha"));
        if(noneMatch) System.out.println("没有记录的userName为ha");
    }
 
    /**
     * 并行流
     */
    @Test
    public void test15(){
        //输出的顺序是随机的
        List<String> list = Arrays.asList("333","222","111");
        list.parallelStream().forEach(System.out::println);
 
        //并行处理,默认是非线程安全的
        List<Integer> listParallel = new ArrayList<>();
        IntStream.range(0, 1000).parallel().forEach(listParallel::add);
        System.out.println("listParallel size :" + listParallel.size());
    }
 
    public static class Account implements Serializable {
        private static final long serialVersionUID = -2369723218842119180L;
        private Integer id;
        private String userName;
 
        public Account(Integer id, String userName) {
            this.id = id;
            this.userName = userName;
        }
 
        public Integer getId() {
            return id;
        }
 
        public void setId(Integer id) {
            this.id = id;
        }
 
        public String getUserName() {
            return userName;
        }
 
        public void setUserName(String userName) {
            this.userName = userName;
        }
 
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", userName='" + userName + '\'' +
                    '}';
        }
    }
 
}

lambda表达式的优点和缺点

优点

1、函数式编程,会是代码更加简洁,提高编程效率,也是编程语言的发展趋势

2、并行操作容易实现

缺点

1、不容易调试

2、若其他程序员没有学过lambda表达式,代码不容易让其他语言的程序员看懂

3、类型推断让代码可读性变差,例如下面的例子,我们并不能一下子看出v是什么类型的变量

mImageView.setOnClickListener(v -> {
 });

参考:

Java8 parallelStream浅析:

Java8 parallelStream浅析 – 知乎

Java8 ParallelStream:

Java8 ParallelStream – GGuoLiang – 博客园



版权声明:本文为csdn_ds原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。