lambda表达式

什么时候使用lambda表达式

如果一段代码需要延迟执行,就可以使用lambda表达式,如在另外的线程运行的一段代码、需要在某个时间点运行的代码、某个条件触发回调的代码等。

lambda表达式定义

lambda表达式是一段可以传递的、可以执行的代码。之所以叫这个名字是因为lambda表达式是一个带参数变量的表达式,而在数学里面带参数的表达式就是“λ”;

lambda表达式语法

(参数)->表达式 (1)
参数为输入,表达式负责计算结果。如果需要进行的计算无法用一个表达式来表示,那么可以编写代码并用一对大括号“{代码段}”包裹起来。即为
(参数)->{表达式} (2)

(1)形式的lambda表达式不需要返回结果,因为它的结果可以从上下文中推到出来,如:

Comparator<String> comparator = (String first, String second)->Integer.compare(first.length(), second.length());

可以推倒出该表达式可以被使用在返回结果为int的上下文中。而Comparator接口有且仅有一个抽象方法:

int compare(T o1, T o2);

之所以要强调Comparator只包含一个抽象方法接口,是因为我们使用lambda表达式只能创建函数式接口的对象。那什么是函数式接口?对了,就是只包含一个抽象方法的接口。函数式接口的转换是我们在java中使用lambda表达式唯一能够做的事情。

lambda表达式的推导特性除了反应在对结果的推导上外,还有对变量类型的推导,如上面的代码也可以写成:

Comparator<String> comparator = ( first,  second)->Integer.compare(first.length(), second.length());

lambda表达式真的很灵活,如果要为lambda表达式指定返回结果,可以用(2)类型的lambda表达式。

Comparator<String> comparator = ( first,  second)->{
            if(first.length()<second.length()) return -1;
            else if(first.length()>second.length()) return 1;
            else return 0;
        };

但此时要注意,在lambda表达式中,只有某些分支有返回值,而其它分支没有返回值是错误的。如:

Comparator<String> comparator = ( first,  second)
->{
if(first.length()<second.length()) return -1;
};

以上讨论的lambda表达式都是自己写的实现方法,实际编程中,我们很可能需要在lambda表达式中调用其它已经实现的方法。如我们要不区分大小写的字符串进行排序,那么可以这样使用:

Arrays.sort(words,String::compareToIgnoreCase);

上面这个示例代码与下面这段是等价的

Arrays.sort(words,(String first,String second)->first.compareToIgnoreCase(second));

这就是lambda表达式的方法引用,方法引用用“::”符号做分割,方法引用有三种使用形式:

对象::实例方法
类::静态方法
类::实例方法

对于前两种形式,方法引用等同于提供方法参数的lambda表达式,第三种形式中,第一个参数会成为执行的方法对象。分别举三个例子对应上面三种形式

对象::实例方法
Button button = new Button();
button.setOnAction(System.out::println);
button.setOnAction(event -> System.out.println(event));
类::静态方法
(x,y)->Math.pow(x,y)
Math::pow
类::实例方法
String::compareToIgnoreCase
(String first,String second)->first.compareToIgnoreCase(second)

lambda表达式理解

经常有同学拿lambda表达式和java的内部类做比较,但lambda表达式的执行效率会比内部类的方式更高。如当我们使用:

Arrays.sort(words,(String first,String second)->Integer.compare(first.length(), second.length()));

Arrays.sort接收一个实现了Comparator接口的实例,会调用该实例的compare方法,然后执行lambda表达式的代码,这些对象和类的管理完全依赖于如何实现。所以建议把lambda表达式想象为一个函数,而不是一个对象,但要记住它可以转换为一个函数式接口。正是由于lambda表达式可以转换为一个函数式接口,这使得在JAVA中不能声明像(String,String)->int这样的函数类型,因为它无法转换为一个函数式接口,我们需要使用像comparator<String>这样的函数式接口来声明函数的目的。java设计者坚持使用了接口的概念,没有将函数类型引入到Java中。

lambda表达式使用

要接受一个lambda表达式需要选择一个函数式接口,这个接口的选择是根据我们的需求而定。Java8中已经定义了很多函数式接口,在java.util.function包中,函数式接口一般会用@FunctionalInterface注解,使用这个注解有两个好处,首先编译器会检查标注该注解的实体,检测该接口是否只包含一个抽象方法,因为这样的接口才是函数式接口,其次在javadoc页面也会有一条声明,说明这个接口是函数式接口。

举个例子,方法repate是反复多次执行一个action,如果这个action执行的过程中需要一些参数的传入,那我们可以选择IntConsumer函数接口,如果不需要传入参数,我们可以选择Runnable函数接口,具体代码如下

public static void repeat(int n, IntConsumer action){
for (int i = 0; i < n; i++) {
action.accept(i);
}
}

repeat(3,i->System.out.println("循环次数:"+i));

public static void repeat(int n, Runnable action){
for (int i = 0; i < n; i++) {
action.run();
}
}

repeat(3,()->System.out.println("hello world"));

输出结果:

循环次数:0
循环次数:1
循环次数:2
hello world
hello world
hello world

lambda表达式的泛型使用

在以前使用java泛型的时候,由于类型擦除,我们无法建立一个泛型数组。这使得我们在调用Stream<T>类的toArray方法时无法调用T[] result = new T[n]。这个时候我们可以使用lambda表达式传入构造函数。

List<String> list = new ArrayList<>();
list.add("a");
list.add("bc");
list.add("def");
list.add("ghij");
list.add("kmlno");
list.add("pqrstu");
String[] result = list.stream().toArray(String[]::new);
//下面这一句跟上面等价
//String[] result = list.stream().toArray(n->new String[n]);

这样在调用过程中,数组长度可以被当作参数传入,然后建立一个长度为n的String数组。

除了以上简单的应用外,泛型应用还有复杂的形式。如

 * @param <T> the type of the input to the function
 * @param <R> the type of the result of the function
 * @since 1.8
 */
@FunctionalInterface
public interface Function<T, R> {
...
}

举例来说,employee是person的子类,如果我们有Function<Person,employee>,那么可以把它传递给以Function<employee,Person>作为参数的方法。虽然我们的函数可以处理Person实例,但是它们只会用employee对象调用它,我们期望函数返回Person实例,但是它实际上返回了更好的结果(employee)。所以在函数类型中有一个一般准则,父类作为参数类型,子类作为返回类型。根据这个准则如果要实现一个接受泛型的函数式表达式的方法,只需在不是返回类型的参数类型使用<? super>,在不是参数类型的返回类型上使用<? extend>。如在Stream<String>的forEach方法。我们可以把一个Consumer<Object>传递给该方法。因为如果Consumer<Object>能够处理任何对象,那么它也一定可以处理字符串。

//Stream的forEach方法
void forEach(Consumer<? super T> action);

另外,当一个函数类型的参数和返回值都是一个类型的时候就没有必要使用泛型了,如T reduce(T identity, BinaryOperator<T> accumulator)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值