不知道面试会不会问Lambda怎么用

咱们先假设一个场景想象一下,当一个项目出现bug的时候,恰巧这个时候须要你去修改,而当你打开项目以后,眼前的代码让你有一种特别严重的陌生感,你会不会慌?内心是否是瞬间就会喷涌而出各类想法:我这是打开的啥语言的项目?仍是我眼花看错了?难道是我过期了?这写的是个啥子玩意儿…

java8在14年就出来了,已经好久了,可是仍是有不少人没用过,包括我以前的同事都对这个不太熟悉,缘由多是多样的,多是老程序员以为不必;也多是性格使然,拒绝接受新的东西,一切守旧,能用就行;也多是项目太老了,还在用JDK1.7,或者更老的版本,平时根本就接触不到java8的写法,也不须要去接触。java

不管是什么缘由,在新事物出现以后,没有一股探险精神,不去尝试,不去结合本身的处境去思考,这样下去就算天上掉馅饼也轮不到你啊。 这篇短文说下Lambda表达式,有必定的编程基础的小伙伴简单看下应该就会明白,不只仅写着舒服,更能提供你的工做效率,让你有更多的时间带薪划水,自我提升,走向人生巅峰。程序员

Lambda表达式

Lambda表达式能够理解为一种匿名函数:没有名称、有参数列表、函数主体、返回类型,可能还会有异常的列表。express

参数 -> 主体编程

lambda表达式:(parameters) -> expression 或者是 (parameters) -> { statements; }bash

函数式接口

什么是函数式接口?

仅仅定义了一个抽象方法的接口,相似于Predicate、Comparator和Runnable。 @FunctionalInterface 函数式接口都带有这个注解,这个注解表示这个接口会被设计为函数式接口。app

行为参数化

一个方法接受多个不一样的行为做为参数,并在内部使用它们,完成不一样行为的能力。函数

函数式接口能够作些什么?

Lambda表达式容许你直接之内联的形式为函数式接口的抽象方法提供实现,而且把整个表达式做为函数式接口的实例,也就是说,Lambda是函数式接口的一个具体实现。函数式接口和Lambda会在项目中写出更加简洁易懂的代码。 接下来咱们看下几种函数式接口:性能

  • java.util.function.Predicate:这个接口中定义了一个test的抽象方法,它接受泛型T对象,并返回一个boolean值,在你须要表示一个涉及类型T的布尔表达式时,就可使用这个接口。
  • java.util.function.Consumer:这个接口中定义了accept抽象方法,它接受泛型T的对象,没有返回。若是你须要访问类型T的对象,并执行某些操做,能够用它。
  • java.util.function.Function:这个接口定义了一个apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象,若是你须要定一个Lambda,将输入对象的信息映射到输出对象,就可使用这个接口。
  • ps:咱们也能够本身定义一个本身须要的函数式接口。 这么说实在是太生涩了,仍是贴点代码,让你们都看看:
@FunctionalInterface
public interface Predicate<T> {
    //我只截取了部分代码,test是这个接口惟一的抽象方法,话说从java8开始,接口中不只   
    //仅只能有抽象方法了,实现的方法也能够存在,用default和static来修饰。
    boolean test(T t);
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
复制代码

接下来,看下Lambda和函数式接口是怎么配合,一块儿快乐的工做的: 首先定义一个方法,这个方法的参数中有函数式接口:ui

private static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
    List<T> result = new ArrayList<>();
    for (T e : list) {
        if (predicate.test(e)) {
            result.add(e);
        }
    }
    return result;
}
复制代码

接下来你就能够这么写:spa

List<Apple> apples = filter(list, (Apple apple) -> "red".equals(apple.getColor()));
复制代码

以上,filter方法的参数是一个泛型集合和Predicate,这个函数式接口中的抽象方法是接受一个对象并返回一个布尔值,因此Lambda咱们能够写成参数是一个实体对象Apple,主体是一个返回boolean值的表达式,将这段Lambda做为参数传给filter()方法,这也是java8的行为参数化特性。以上咱们就能够挑选出红苹果。 使用了泛型,就表明着咱们还能够复用这段代码作些别的事情,挑选出你想要东东的:

List<String> stringList = filter(strList, StringUtils::isNoneBlank);
复制代码

抽象方法的方法签名和Lambda表达式的签名是一一对应的,若是你要应用不一样的Lambda表达式,就须要多个函数式接口,固然了我也是能够本身定义的。

在java中只有引用类型或者是原始类型,这是由泛型内部的实现方式形成的。所以,在Java里有一个将原始类型转换为对应的引用类型的机制,这个机制叫做装箱(boxing)。相反的操做,也就是将引用类型转换为对应的原始类型,叫做拆箱(unboxing)。

Java还有一个自动装箱机制,也就是说装箱和拆箱操做是自动完成的,但这在性能方面是要付出代价的。装箱后的值本质上就是把原始类型包裹起来,并保存在堆里。所以,装箱后的值须要更多的内存,并须要额外的内存来搜索获取被包裹的原始值。

针对于这一点,java8中的函数式接口提供了单独的接口,就是为了在输入和输出的时候避免自动装箱拆箱的操做,是否是很贴心。

通常状况下,在名称上咱们就能看得出来,一目了然。在原来的名称上会有原始类型前缀。像Function接口针对输出参数类型的变形。好比说:ToIntFunction、IntToDoubleFunction等。

在必要的状况下,咱们也能够本身定义一个函数式接口,请记住,(T,U) -> R的表达方式展现了对一个函数的简单描述,箭头的的左侧表明了参数类型,右侧表明着返回类型,这里它表明一个函数,具备两个参数,分别为泛型T和U,返回类型为R。

函数式接口是不容许抛出 受检异常(checked exception),可是有两个方法能够抛出异常:

  • 定义一个本身的函数式接口,在惟一的抽象方法抛出异常;
  • 用try-catch 将lambda 包起来。

类型检查

java7是经过泛型从上下文推断类型,lambda的类型检查是经过它的上下文推断出来的。lambda会找到它所在的方法的方法签名,也就是它的参数,也就是他们说的目标类型,再找到这个方法中定义的抽象方法,这个方法描述的函数描述符是什么?也就是这个方法是个什么样的,接受什么参数,返回什么。lambda也必须是符合这样的。当lambda抛出异常的时候,那个抽象方法也必需要抛出异常。 有了目标类型,那么同一个lambda就能够与不一样的函数式接口联系起来。只要他们的抽象方法签名是同样的。 例如:

Callable<Integer> c = () -> 42;
PrivilegedAction<Integer> p = () -> 42;
复制代码

这两个接口都是没有参数,且返回一个泛型T的函数。 void兼容规则 lambda的主题是一个语句表达式,和一个返回void的函数描述符兼容,包括参数列表, 好比下面:

// Predicate返回了一个boolean
Predicate<String> p = s -> list.add(s);
// Consumer返回了一个void
Consumer<String> b = s -> list.add(s);
复制代码

在lambda中使用局部变量

final int local_value = 44;
Consumer<String> stringConsumer = (String s) -> {
            int new_local_value = s.length() + local_value;
        };
复制代码

在lambda中能够无限制的使用实例变量和静态变量,可是只能是final的,若是在表达式里面给变量赋值,就会编译不经过。为何会有这样的呢? 由于实例变量存储在堆中,局部变量存储在栈中,lambda是在一个线程中,若是lambda能够直接访问局部变量,lambda的线程可能会在分配该变量的线程将这个变量回收以后,再去访问该变量。在访问局部变量的时候,其实是访问他的副本,而不是原始变量。

方法引用

方法引用,方法目标实体放在::的前面,方法名放在后面。好比 Apple::getWeight,不须要括号。 构造函数是能够利用它的名称和关键字 new来建立一个引用。

//Supplier也是一个函数式接口,惟一的抽象方法不接受参数,直接返回一个对象
Supplier<Apple> sup = Apple::new;
        Apple apple = sup.get();
复制代码

可是若是是有参数的呢?

//一个参数
Function<Long, Apple> fun = Apple::new;
        Apple apple1 = fun.apply(110L);
//两个参数
 BiFunction<Long, String, Apple> biFunction = Apple::new;
 Apple biApple = biFunction.apply(3L, "red");
复制代码

可是若是有三个参数、四个参数呢?咱们上面说了怎么样能够自定义一个本身须要的函数式接口。

@FunctionalInterface
public interface AppleWithParam<T, U, V, R> {
    R apply(T t, U u, V v);
}
复制代码

总结:

  • java8中自带的函数式接口,以及为了不拆装箱操做而产生的函数式接口的原始类型转化。
  • 函数式接口就是仅仅定义一个抽象方法的接口。抽象方法的签名(称为函数描述符) 描述了Lambda表达式的签名。
  • 只有在接受函数式接口的地方才可使用Lambda表达式。
  • 接口如今还能够拥有默认方法,(就是类没有对方法进行实现的时候,它实现的接口来提供默认实现的方法)

最后

只有主动拥抱变化,才能更快的成长。

若是对本文有任何异议或者说有什么好的建议,能够加我好友(公众号后台联系做者),也能够在下面留言区留言。但愿这篇文章能帮助你们披荆斩棘,乘风破浪。

这样的分享我会一直持续,你的关注、转发和好看是对我最大的支持,感谢。

相关文章
相关标签/搜索