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> T void Consumer<Integer> c = (int x) -> { System.out.println(x) };
Supplier<T> None T Supplier<Person> supplier = Person::new;
Function<T,R> T R Function<String, Integer> fuc = Integer::parseInt;
BiFunction<T, R, U> T, R U BiFunction<Integer, Integer, Integer> fuc = (x, y) -> x + y;
Predicate<T> T boolean Predicate<Person> 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<String> stream1 = Stream.of("1", "2", "3");

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

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

        // method 4
        Stream<String> 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<T> action)

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

2 map(Function<T, R> 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<T, Stream<R>> 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<T> predicate)

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

7 forEachOrdered(Predicate<T> 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 可用于返回列表或字符串

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> 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<Person> personList = Lists.newArrayList();

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

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

9 更多例子

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

// 等同如下代码
String k = "key";
HashMap<String, Integer> 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<Apple> cmp = Comparator.comparing(Apple::getWeight),该表达式表示比较苹果的重量,如果想对苹果按重量递减排序,此时无需重新定义比较器,接口有一个默认方法reversed可以使给定的比较器逆序。因此仍然用开始的那个比较器,只要简单修改修改一下前一个例子就可以对苹果按重量递减排序: appleList.sort(cmp.reversed());

2 谓词复合

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

3 函数复合

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

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

总结

  • stream只能遍历一次,遍历完之后,这个流已经被消费掉了。 可以从原始数据源那里再获得一个新的流来重新遍历一遍,重复遍历会报IllegalStateException
List<String> title = Arrays.asList("1", "2", "3");
Stream<String> 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都比较熟悉的基础上,如果写了太复杂的表达式,其他同学维护和理解起来也会增加相应的成本。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容