Java Lambda表达式

1 简介

Lambda表达式是java8提供的新特性,是一种匿名函数,也是函数式接口实现的快捷方式,类似js中的闭包,它允许把函数当做参数来使用,是面向函数式编程的思想,Lambda的格式为: (参数) -> {方法体},具有如下特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值
    例子:
// 1 无参数的lambda表达式,返回值5  
() -> 5  
  
// 2 一个数值类型的参数,返回其2倍的值  
x -> 2 * x  
  
// 3 两个数值类型的参数,返回差值  
(x, y) -> x – y  
  
// 4 string类型的参数,在控制台打印,无返回类型  
(String s) -> System.out.print(s)

java是一个面向对象的语言,而Lambda表达式却是一个匿名函数,因此java把Lambda表达式抽象成一个匿名内部类,并没有破坏面向对象的特性。

2 函数式接口

java8中通过注解@FunctionalInterface标明接口为一个函数式接口,函数式接口是只包含一个抽象方法声明的接口,但是允许有默认实现的方法(比如: java.util.function.BinaryOperator)。如果定义了多个抽象方法,编译器会报错,如下图。所以,当我们的代码中定义了函数式接口的时候,要记得加上@FunctionalInterface注解,防止被其他人增加额外的方法而破坏函数式接口的规范。


错误示范

java.lang.Runnable 就是一种函数式接口,每个 Lambda 表达式都能隐式地赋值给函数式接口,例如,我们可以通过 Lambda 表达式创建 Runnable 接口的引用:

Runnable r = () -> System.out.println("hello world");

当不指明函数式接口时,编译器会自动进行类型推断,如下面代码,编译器会自动根据线程类的构造函数签名 public Thread(Runnable r) { },将该 Lambda 表达式赋给 Runnable 接口:

new Thread(
   () -> System.out.println("hello world")
).start();

下表列举了一些常用的函数式接口

接口 参数 返回值 示例
Consumer T void Consumer c = (int x) -> { System.out.println(x) };
Supplier None T Supplier supplier = Person::new;
Function T R Function fuc = Integer::parseInt;
BiFunction T, R U BiFunction fuc = (x, y) -> x + y;
Predicate T boolean Predicate p = (Person person) -> { "zhangsan".equals(person.getName()) };

3 Lambda表达式的作用

Lambda表达式可以让代码更加简练,具有可读性。
举个栗子,这个例子大家都在举,当创建新线程时,不使用lambda的情况下:

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("为了这一行有用的代码,写了那么多多余的行:(  ");
    }
}).start();

使用lambda表达式:

new Thread( () -> System.out.println("一起摇摆~~   ") ).start();

4 配合Stream让代码整洁到飞起

Stream是Java 8 API添加的一个新的抽象,称为流Stream,可以让我们以一种声明的方式处理数据。Stream类似SQL的存储过程,提供了一种对 Java 集合运算和表达的高阶抽象。

Stream的构成

当我们使用一个流的时候,通常包括三个步骤:

  • 获取一个数据源(source),可以是集合,数组;
// method 1
        Stream stream1 = Stream.of("1", "2", "3");

        // method 2
        List list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        Stream stream2 = list.stream();

        // method 3
        Stream stream3 = list.parallelStream();

        // method 4
        Stream stream4 = Arrays.stream(new String[]{"1", "2"});

        // method 5
        IntStream stream5 = IntStream.range(0, 100);
  • 数据转换;
  • 执行操作获取想要的结果。

流的操作

  • 中间操作:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip,操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)
  • 最终操作:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

使用

1 forEach(Consumer action)

public static void main(String[] args) {
        // 输出Stream中的元素
        Stream.of("1","2","3","4","5").forEach(e->System.out.println(e));
}

2 map(Function mapper)

public static void main(String[] args) {
         Stream.of("1","2","3","4","5")
                .map(Integer::parseInt) //转成int
                .forEach(System.out::println);
}

3 flatMap(Function> mapper): 会将每一个输入对象输入映射为一个新集合,然后把这些新集合连成一个大集合

public static void main(String[] args) {
        Stream.of("a-b-c-d","e-f-g-h")
                .flatMap(e->Stream.of(e.split("-")))
                .forEach(e->System.out.print(e));

}
// abcdefgh

4 limit(long maxSize)

public static void main(String[] args) {
        Stream.of(1,2,3,4,5,6)
                .limit(3) 
                .forEach(e->System.out.println(e)); 
}
// 输出前三个 1,2,3

5 distinct()

Stream.of(1,1,2,3)
                .distinct() //去重
                .forEach(e->System.out.println(e));
// 1 2 3

6 filter(Predicate predicate)

// 可以用and()、or()逻辑函数来合并Predicate
Predicate p1 = (n) -> n.startsWith("chang"); //Predicate: 验证传进来的参数符不符合规则, 返回true|false
Predicate p2 = (n) -> n.length() == 5;
names.stream()
    .filter(p1.and(p2))
    .forEach((n) -> System.out.print(n));

7 forEachOrdered(Predicate predicate): 适用用于并行流的情况下进行迭代,能保证迭代的有序性

Stream.of(0,1,2,3,4,5,6,7,8,9)
                .parallel()
                .forEachOrdered(e->{
                    System.out.println(Thread.currentThread().getName()+": "+e);});

8 Collectors: 将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串

Liststrings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
 
System.out.println("筛选列表: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);

// 筛选列表: [abc, bc, efg, abcd, jkl]
// 合并字符串: abc, bc, efg, abcd, jkl


List personList = Lists.newArrayList();

// 同名分组
Map> map = personList.stream().collect(Collectors.groupingBy(Person::getName));

// 根据唯一id映射, 保证Person::getId是唯一的,否则报java.lang.IllegalStateException
Map map = personList.stream().collect(Collectors.toMap(Person::getId, p -> p));

9 更多例子

String k = "key";
HashMap map = new HashMap<>() ;
map.put(k, 1);
map.merge(k, 2, (oldVal, newVal) -> oldVal + newVal);

// 等同如下代码
String k = "key";
HashMap map = new HashMap<>() ;
map.put(k, 1);
int newVal = 2;
if(map.containsKey(k)) {
    map.put(k, map.get(k) + newVal);
} else {
    map.put(k, newVal);
}

复合lambda表达式

1 比较器复合

可以使用静态方法Comparator.comparing定义比较器,根据提取用于比较的键值的Function来返回一个Comparator,
如: Comparator cmp = Comparator.comparing(Apple::getWeight),该表达式表示比较苹果的重量,如果想对苹果按重量递减排序,此时无需重新定义比较器,接口有一个默认方法reversed可以使给定的比较器逆序。因此仍然用开始的那个比较器,只要简单修改修改一下前一个例子就可以对苹果按重量递减排序: appleList.sort(cmp.reversed());

2 谓词复合

谓词接口包括三个方法:negate、and和or,让我们可以重用已有的Predicate来创建更复杂的谓词,如4.3.6中的filter方法

3 函数复合

Function f = x -> x + 1;
Function g = x -> x * 2;
Function h = f.andThen(g); // andThen方法会返回一个函数,它先对输入应用一个给定函数,再对输出应用另一个函数
int result = h.apply(1); // 返回结果为4

Function f = x -> x + 1;
Function g = x -> x * 2;
Function h = f.compose(g); // compose方法,先把给定的函数用作compose的参数里面给的那个函数,然后再把函数本身用于结果
int result = h.apply(1);  // 返回结果为3
函数复合

总结

  • stream只能遍历一次,遍历完之后,这个流已经被消费掉了。 可以从原始数据源那里再获得一个新的流来重新遍历一遍,重复遍历会报IllegalStateException
List title = Arrays.asList("1", "2", "3");
Stream s = title.stream();
s.forEach(System.out::println);
s.forEach(System.out::println); // Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
  • 简洁的lambda可以增加代码可读性,但这简历在团队成员对lambda都比较熟悉的基础上,如果写了太复杂的表达式,其他同学维护和理解起来也会增加相应的成本。

你可能感兴趣的:(Java Lambda表达式)