欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

不知道面试会不会问Lambda怎么用(推荐)

程序员文章站 2023-12-13 10:54:10
我们先假设一个场景想象一下,当一个项目出现bug的时候,恰巧这个时候需要你去修改,而当你打开项目之后,眼前的代码让你有一种特别严重的陌生感,你会不会慌?心里是不是瞬间就会喷...

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

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

无论是什么原因,在新事物出现之后,没有一股探险精神,不去尝试,不去结合自己的处境去思考,这样下去就算天上掉馅饼也轮不到你啊。

这篇短文说下lambda表达式,有一定的编程基础的小伙伴简单看下应该就会明白,不仅仅写着舒服,更能提供你的工作效率,让你有更多的时间带薪划水,自我提高,走向人生巅峰。

lambda表达式

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

参数 -> 主体

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

函数式接口

什么是函数式接口?

仅仅定义了一个抽象方法的接口,类似于predicate、comparator和runnable。

@functionalinterface 函数式接口都带有这个注解,这个注解表示这个接口会被设计为函数式接口。

行为参数化

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

函数式接口可以做些什么?

lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并且把整个表达式作为函数式接口的实例,也就是说,lambda是函数式接口的一个具体实现。函数式接口和lambda会在项目中写出更加简洁易懂的代码。

接下来我们看下几种函数式接口:

  1. java.util.function.predicate:这个接口中定义了一个test的抽象方法,它接受泛型t对象,并返回一个boolean值,在你需要表示一个涉及类型t的布尔表达式时,就可以使用这个接口。
  2. java.util.function.consumer:这个接口中定义了accept抽象方法,它接受泛型t的对象,没有返回。如果你需要访问类型t的对象,并执行某些操作,可以用它。
  3. java.util.function.function:这个接口定义了一个apply的方法,它接受一个泛型t的对象,并返回一个泛型r的对象,如果你需要定一个lambda,将输入对象的信息映射到输出对象,就可以使用这个接口。
  4. 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和函数式接口是怎么配合,一起快乐的工作的: 首先定义一个方法,这个方法的参数中有函数式接口:

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;
}

接下来你就可以这么写:

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),但是有两个方法可以抛出异常:

  1. 定义一个自己的函数式接口,在唯一的抽象方法抛出异常;
  2. 用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);
}

总结:

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

以上所述是小编给大家介绍的lambda表达式详解整合,希望对大家有所帮助

上一篇:

下一篇: