java8之lambda表达式

0 lambda的传说

  • 百科里是这么描述的:

Lambda 表达式”是一个匿名函数,可以包含表达式和语句,并且可用于创建委托或表达式目录树类型。

看百科的描述好像有那么点感觉了。可是干嘛非要搞个这么高冷的解释呢?

  • 个人是这么理解的:

lambda 类似于一个可调用的代码块或者游离函数。当然也可以有入参。

1 瞄一眼他长啥样?

  • 示例

比如下面这样:

Comparator<Integer> c = (i, j) -> Integer.compare(i, j);
 
 
  • 1
  • 1

等价的原始Java代码长这样:

Comparator<Integer> c = new Comparator<Integer>() {
  @Override
  public int compare(Integer o1, Integer o2) {
    return Integer.compare(o1, o2);
  }
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

用上lambda是什么体验?

  • 代码量好像少了
  • 逼格好像提高了
  • 代码好像更优雅了

2 lambda各种外观

至少在Java里lambda的爸爸妈妈姐姐弟弟爷爷奶奶七大姑八大姨……可能都是长这个样子的。

同时,据老夫多年的断案经验来推断,lambda的本尊应该也是这个样子的:

(Type1 param1,Type2 param2, ...) -> {
  // 一些乱七八糟、乌漆嘛黑的处理操作();
  return ret;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
  • 外观一:没有入参的时候
()->{return ret;}
 
 
  • 1
  • 1
  • 外观二:有参数的时候
(Type p)->{return ret;}

// 参数类型可以省略(隐式推掉)
(p)->{return ret;}

// 一个参数的时候,参数列表外的括号也可以没有
p->{return ret;}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 外观三:函数体只有一行的时候
// 方法体只有一行的时候,或括号连同结尾的分号都可以省略
e->ret
 
 
  • 1
  • 2
  • 1
  • 2
  • 外观四:没有返回值的时候
e->{}
 
 
  • 1
  • 1
  • 方法引用和构造器引用 
    • instance::instanceMethod
    • className::staticMethod
    • className::instanceMethod
Arrays.asList(null, "", "   ", "HelloWorld", "ByeBug")//
  .stream().filter(StringUtils::isNotBlank)//
  .forEach(System.out::println);
 
 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  • ……

3 lambda使用场景

再看一个例子:

// 原始方法启动一个线程
new Thread(new Runnable() {
  @Override
  public void run() {
  }
}).start();

// lambda版
new Thread(()->{}).start();
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

不难看出,整个匿名内部类中最关键的代码其实就是:

public void run() {
}
 
 
  • 1
  • 2
  • 1
  • 2

所以,lambda中关键部分也就是这部分代码了。

其实,用注解java.lang.FunctionalInterface修饰的接口都可以用于lambda表达式中。

这种接口,都是只有一个方法的接口。

另外,只要你的接口只有一个方法,即使是没有@FunctionalInterface注解修饰,也是可以用lambda的(很多时候编译器还是很聪明的,他会自动推断的)。

总之,lambda只钟爱函数式接口(@FunctionalInterface)。


4 再来个示例

public class HouseInfo {

    private Integer houseId; // 小区ID
    private String houseName;// 小区名
    private Integer browseCount; // 浏览数
    private Integer distance;// 距离 KM
    // constructor
    // getters
    // setters
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 有如下数据
List<HouseInfo> houseInfos = Lists.newArrayList(//
    new HouseInfo(1, "恒大星级公寓", 100, 1), //
    new HouseInfo(2, "汇智湖畔", 999, 2), //
    new HouseInfo(3, "张江汤臣豪园", 100, 1), //
    new HouseInfo(4, "保利星苑", 23, 10), //
    new HouseInfo(5, "北顾小区", 66, 23), //
    new HouseInfo(6, "北杰公寓", null, 55), //
    new HouseInfo(7, "保利星苑", 77, 66), //
    new HouseInfo(8, "保利星苑", 111, 12)//
);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 按距离排序
Collections.sort(houseInfos, (h1, h2) -> {
    if (h1 == null || h2 == null)
      return 0;
    if (h1.getDistance() == null || h2.getDistance() == null)
      return 0;
    return h1.getDistance().compareTo(h2.getDistance());
});
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 输出小区名
houseInfos.stream().map(h -> h.getHouseName()).forEach(System.out::println);
 
 
  • 1
  • 1
  • 动态条件过滤
// 该函数式接口用来当做测试条件
public static interface Condition<T> {
  boolean conditionTest(T e);
}

// 定义一个方法来输出满足condition测试结果的小区
public void printWithFilter(List<HouseInfo> houseList, Condition<HouseInfo> condition) {
  houseList.stream().filter(e -> condition.conditionTest(e)).forEach(System.out::println);
}

@Test
public void test4() {
  List<HouseInfo> houseInfos = Lists.newArrayList(//
    new HouseInfo(1, "恒大星级公寓", 100, 1), //
    new HouseInfo(2, "汇智湖畔", 999, 2), //
    new HouseInfo(3, "张江汤臣豪园", 100, 1), //
    new HouseInfo(4, "保利星苑", 23, 10), //
    new HouseInfo(5, "北顾小区", 66, 23), //
    new HouseInfo(6, "北杰公寓", null, 55), //
    new HouseInfo(7, "保利星苑", 77, 66), //
    new HouseInfo(8, "保利星苑", 111, 12)//
  );
  // 打印小区名包含北字的小区
  printWithFilter(houseInfos, e -> e != null && e.getHouseName().contains("北"));
  // 打印距离大于10KM的小区
  printWithFilter(houseInfos, e -> e != null && e.getDistance() != null && e.getDistance() > 10);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 字符串转大写
Arrays.asList("HelloWord", "ByeBug")
  .stream().map(String::toUpperCase).forEach(System.out::println);
 
 
  • 1
  • 2
  • 1
  • 2
  • 其实在Scala里lambda更加直观
List("HelloWord", "ByeBug").map(_.toUpperCase()).foreach(println _);
 
 
  • 1
  • 1

5 lambda的好基友

通过上面的示例,再结合实际。不难发现最常用的lambda的形式如下:

  • 单个输入,无输出
  • 单个输入,单个输出
  • 无输入,输出单个类型
  • 两个不同类型的输入,第三种类型的输出
  • 两个不同类型的输入,其中一种类型的输出
  • ……

其实在每次使用的时候,没必要自己去新建这些函数式接口以支持lambda,JDK就已经给lambda内置了很多好基友:

  • Consumer: 单个输入,无输出
public interface Consumer<T> {
  void accept(T t);
}
 
 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  • BiConsumer: 两个不同类型的输入,无输出
public interface BiConsumer<T, U> {
  void accept(T t, U u);
}
 
 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  • Supplier: 无输入,输出单个类型
public interface Supplier<T> {
  T get();
}
 
 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  • Function: 两个不同类型的输入
public interface Function<T, R> {
  R apply(T t); // 输入T类型,转换成R类型
}
 
 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  • ToIntFunction / ToLongFunction / ToDoubleFunction
public interface ToIntFunction<T> {
    // T类型的输入,输出int 
    // 类似于Stream的mapToInt()
    int applyAsInt(T value); 
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
  • IntFunction / LongFunction / DoubleFunction
public interface IntFunction<R> {
    R apply(int value);
}
 
 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  • BiFunction: 两个不同类型的输入,第三种类型的输出
public interface BiFunction<T, U, R> {
  R apply(T t, U u);
}
 
 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  • 条件测试
public interface Predicate<T> {
  boolean test(T t);
}
 
 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

可以不用自定义函数式接口来支持lambda,上面的例子可以改成:

public void printWithFilter(List<HouseInfo> houseList, Predicate<HouseInfo> condition) {
  houseList.stream().filter(e -> condition.test(e)).forEach(System.out::println);
}
 
 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

关于lambda的传说和他到底是什么鬼,看到这里应该够了。

毕竟我们的主要目的是使用它,知道他怎么用才是重点。

没必要纠结他严格的学术定义(这种事不应该是那种只会纸上谈兵的老不死干的吗?)。

在Java8里和lambda相关的主要API就是Stream了。在了解Stream的时候来顺便熟悉lambda吧。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值