lambda表达式的分析及使用

上一篇文章:java通过行为参数化传递代码
上一篇文章讲述了什么叫做行为参数化,也描述了如何从一个变量最终抽象演化到了lambda表达式,这是一个循序渐进的过程,是通过一层一层的抽象实现的。所以延续上篇,本篇来特别详细的讲述一下lambda表达式的使用。

lambda表达式的组成

lambda表达式由三部分组成,分别是:
参数列表:参数列表里面放的是要进行操作的参数,和方法里面的参数是一样的。
箭头:就是这个:-> 它表示需要拿到参数列表里面的参数,然后进行后面的操作。
lambda主体:它是一个表达式,表示参数列表里面的参数在这里进行了什么操作,返回了什么内容。
小小的总结一下,其实它就是一个方法,有参数,有返回值。下面看几个例子,来熟悉一下lambda表达式
第一个例子我来展示全面的代码,顺便在复习一下上篇文章的内容-----什么叫做行为参数化

//创建一个函数式接口
public interface StringTestService {
    public int test(String s);
}

//写一个方法来打印字符串的长度(省略class)
 public static void printLength(StringTestService stringTestService){
        String s = "123";
        System.out.println(stringTestService.test(s));
    }
//main方法中通过lambda表达式来调用上面的方法(省略class)
 public static void main(String[] args) {
         printLength((String s)->s.length());
 }

在这里插入图片描述

上面就是行为参数化的写法啦(将代码作为参数进行传入),打印结果为3。其中下面lambda表达式的解释:

(String s)->s.length()

表示String类型的参数,返回一个int类型的值,没有return语句,因为lambda隐含了return,如果想要return,需要加上花括号{}。(改为 {return s.length();} 参考第三个例子)
第二个例子:

(Apple a)->a.getWeight()>50

该表达式参数为Apple类型,返回一个Boolean值(重量大于50的)
第三个例子:

(String s)->{return "hahaha";}

这是显示返回的写法,会加上花括号和return。
第四个例子:

()->{"hahhahha"}

这是没有参数,且返回值是String类型的lambda表达式。可以显示的写成下面的形式:

()->{return "hahhahha";}

lambda表达式实战

上面介绍了lambda表达式的组成,接下来说一下在什么场景下会使用lambda表达式------函数式接口
函数式接口就是只有一个抽象方法的接口,下面我们来看一下java中常见的函数式接口都有哪些:
Predicate
Predicate接口是java.util包下面的,里面有一个抽象方法test()参数为泛型,返回值为布尔值,我们可以用它来做一个集合中去除空字符串的方法,代码如下:

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
    }

public class PredicateOperation {
    public static <T> List<T> filter(List<T> list, Predicate<T> predicate){
        List<T> result = new ArrayList<>();
       list.forEach(item->{
           if (predicate.test(item)){
               result.add(item);
           }
       });
       return result;
    }

    public static void main(String[] args) {
        System.out.println(filter(Arrays.asList("", "123", "456"), (String s) -> !s.isEmpty()));
    }
}
    

Consumer
java.util下的Consumer接口定义了一个抽象方法accept(),它接收参数是泛型T,但是没有返回值,是void方法。如果我们想访问某对象,并对其进行操作,可以采用该接口。比如可以用它来创建一个foreach方法,接收一个Integer类型的集合,并对其中的每个元素进行操作,代码如下:

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
 }
public class ConsumerOperation {

    public static <T> void printContent(List<T> list, Consumer<T> consumer) {
        list.forEach(item->{
            consumer.accept(item);
        });
    }

    public static void main(String[] args) {
        printContent(Arrays.asList(1,3,4), (Integer i)-> System.out.println(
                i
        ));
    }
}

Function
java.util包下的Function接口定义了一个叫做apply的方法,它接收一个泛型T的对象,返回一个泛型R的对象,如果我们需要定一个lambda,将输入对象的信息映射到输出,可以使用该接口。比如我们可以做一个map方法,来打印每一个字符串的长度,代码如下:

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
 }


public class FunctionOperation {
    public static <T, R> List<R> map(List<T> stringList, Function<T, R> function) {
        List<R> result = new ArrayList<>();
        stringList.forEach(item -> {
            result.add(function.apply(item));
        });
        return result;
    }

    public static void main(String[] args) {
        System.out.println(map(Arrays.asList("123", "11111", "hehehe"), (String s) -> s.length()));
    }
}

lambda与局部变量

上面的lambda表达式都是使用的参数列表里面的参数,它还可以使用外层作用域中的变量,比如下面的代码就可以捕获参数列表外的参数port进行打印:

public static void main(String[] args) {
        int port=123123;
        Thread thread = new Thread(()-> System.out.println(port));
        thread.start();
    }
}

但是需要注意的是lambda表达式只能捕获局部变量一次,也就是该局部变量必须声明为final,或者事实上为final(只赋予一次值),这是因为局部变量是存储在栈中的,而lambda是单独的一个线程,当lambda访问局部变量时,如果该局部变量所在的方法执行结束了,就释放栈帧,而这时,lambda所在线程就无法访问该局部变量,因为这根本是两个线程。所以使用final来修饰的时候,lambda实际访问的是该变量的副本。如下图,我再对port变量进行赋值的话,就会报错:
在这里插入图片描述

方法引用

如果一个lambda表达式是直接调用某个方法,那可以直接显示的指明该方法的名字,这样可以让我们的代码可读性更好。写法是:目标引用放在分隔符::前面,方法的名称放在后面。比如:

Apple::getWeight 

上面代码的含义就是引用了Apple类中定义的方法getWeight,它也可以写成下面的形式,只是上面引用的方式看起来更加的简单明了:

(Appple a)->a.getWeight()

方法引用可以作用于静态方法和实例方法,下面我们敲一个demo来实战一下。
现在想要写一个方法来对集合里面的数字进行排序,在不使用stream流的情况下,可以用集合自带的sort方法,sort方法里面的参数是一个Comparator接口,该接口为函数式接口,里面有一个抽象方法compare,参数为两个同类型的泛型,返回值为int,我们可以初步来这样实现:

//sort方法源码如下:
    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }
 //Comparator接口如下:
 @FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}

//我们的第一种写法:
 public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 5, 2, 7);
        list.sort((Integer a, Integer b) -> a.compareTo(b));
    }

学习了方法引用之后,可以变成下面的方法,直接调用Integer的compareTo方法:

public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 5, 2, 7);

        list.sort(Integer::compareTo);
    }

有人好奇他的参数类型在哪里判断的呢,这个java会根据上下文自动判断其参数类型,可以写也可以不写,比如第一种写法写成下面这样的也是没问题的:

   public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 5, 2, 7);

        list.sort( (a,b) -> a.compareTo(b));
    }

总结:本篇文章主要讲了lambda表达式的组成、使用方法、简化,也涉及到一些常见的java自带的函数式接口,我们可以在工作中尝试着来使用。其实我们对集合的操作大部分还是使用stream流,后面我会继续写有关stream流的文章,欢迎大家斧正。
参考资料----《java8实战》

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值