精华内容
下载资源
问答
  • Java8 Lambda表达式教程

    万次阅读 多人点赞 2013-10-16 16:08:07
    Java8新特性,Lambda表达式与集合类bulk operation教程。迄今为止最全面的中文原创java lambda表达式教程。

    1. 什么是λ表达式

    λ表达式本质上是一个匿名方法。让我们来看下面这个例子:

        public int add(int x, int y) {
            return x + y;
        }

    转成λ表达式后是这个样子:
       
        (int x, int y) -> x + y;

    参数类型也可以省略,Java编译器会根据上下文推断出来:

        (x, y) -> x + y; //返回两数之和
     
    或者

        (x, y) -> { return x + y; } //显式指明返回值

    可见λ表达式由三部分组成:参数列表,箭头(->),以及一个表达式或语句块。

    下面这个例子里的λ表达式没有参数,也没有返回值(相当于一个方法接受0个参数,返回void,其实就是Runnable里run方法的一个实现):

        () -> { System.out.println("Hello Lambda!"); }

    如果只有一个参数且可以被Java推断出类型,那么参数列表的括号也可以省略:

        list -> { return list.size(); }

    2. λ表达式的类型(它是Object吗?)

    λ表达式可以被当做是一个Object(注意措辞)。λ表达式的类型,叫做“目标类型(target type)”。λ表达式的目标类型是“函数式接口(functional interface)”,这是Java8新引入的概念。它的定义是:一个接口,如果只有一个显式声明的抽象方法,那么它就是一个函数式接口。一般用@FunctionalInterface标注出来(也可以不标)。举例如下:

        @FunctionalInterface
        public interface Runnable { void run(); }
       
        public interface Callable<V> { V call() throws Exception; }
       
        public interface ActionListener { void actionPerformed(ActionEvent e); }
       
        public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); }

    注意最后这个Comparator接口。它里面声明了两个方法,貌似不符合函数式接口的定义,但它的确是函数式接口。这是因为equals方法是Object的,所有的接口都会声明Object的public方法——虽然大多是隐式的。所以,Comparator显式的声明了equals不影响它依然是个函式数接口。

    你可以用一个λ表达式为一个函数式接口赋值:
     
        Runnable r1 = () -> {System.out.println("Hello Lambda!");};
       
    然后再赋值给一个Object:

        Object obj = r1;
       
    但却不能这样干:

        Object obj = () -> {System.out.println("Hello Lambda!");}; // ERROR! Object is not a functional interface!

    必须显式的转型成一个函数式接口才可以:

        Object o = (Runnable) () -> { System.out.println("hi"); }; // correct
       
    一个λ表达式只有在转型成一个函数式接口后才能被当做Object使用。所以下面这句也不能编译:

        System.out.println( () -> {} ); //错误! 目标类型不明
       
    必须先转型:

        System.out.println( (Runnable)() -> {} ); // 正确

    假设你自己写了一个函数式接口,长的跟Runnable一模一样:

        @FunctionalInterface
        public interface MyRunnable {
            public void run();
        }
       
    那么

        Runnable r1 =    () -> {System.out.println("Hello Lambda!");};
        MyRunnable2 r2 = () -> {System.out.println("Hello Lambda!");};

    都是正确的写法。这说明一个λ表达式可以有多个目标类型(函数式接口),只要函数匹配成功即可。
    但需注意一个λ表达式必须至少有一个目标类型。

    JDK预定义了很多函数式接口以避免用户重复定义。最典型的是Function:

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

    这个接口代表一个函数,接受一个T类型的参数,并返回一个R类型的返回值。   

    另一个预定义函数式接口叫做Consumer,跟Function的唯一不同是它没有返回值。

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

    还有一个Predicate,用来判断某项条件是否满足。经常用来进行筛滤操作:
       
        @FunctionalInterface
        public interface Predicate<T> {
            boolean test(T t);
        }
       
    综上所述,一个λ表达式其实就是定义了一个匿名方法,只不过这个方法必须符合至少一个函数式接口。
           
    3. λ表达式的使用

    3.1 λ表达式用在何处

    λ表达式主要用于替换以前广泛使用的内部匿名类,各种回调,比如事件响应器、传入Thread类的Runnable等。看下面的例子:

        Thread oldSchool = new Thread( new Runnable () {
            @Override
            public void run() {
                System.out.println("This is from an anonymous class.");
            }
        } );
       
        Thread gaoDuanDaQiShangDangCi = new Thread( () -> {
            System.out.println("This is from an anonymous method (lambda exp).");
        } );

    注意第二个线程里的λ表达式,你并不需要显式地把它转成一个Runnable,因为Java能根据上下文自动推断出来:一个Thread的构造函数接受一个Runnable参数,而传入的λ表达式正好符合其run()函数,所以Java编译器推断它为Runnable。

    从形式上看,λ表达式只是为你节省了几行代码。但将λ表达式引入Java的动机并不仅仅为此。Java8有一个短期目标和一个长期目标。短期目标是:配合“集合类批处理操作”的内部迭代和并行处理(下面将要讲到);长期目标是将Java向函数式编程语言这个方向引导(并不是要完全变成一门函数式编程语言,只是让它有更多的函数式编程语言的特性),也正是由于这个原因,Oracle并没有简单地使用内部类去实现λ表达式,而是使用了一种更动态、更灵活、易于将来扩展和改变的策略(invokedynamic)。

    3.2 λ表达式与集合类批处理操作(或者叫块操作)

    上文提到了集合类的批处理操作。这是Java8的另一个重要特性,它与λ表达式的配合使用乃是Java8的最主要特性。集合类的批处理操作API的目的是实现集合类的“内部迭代”,并期望充分利用现代多核CPU进行并行计算。
    Java8之前集合类的迭代(Iteration)都是外部的,即客户代码。而内部迭代意味着改由Java类库来进行迭代,而不是客户代码。例如:

        for(Object o: list) { // 外部迭代
            System.out.println(o);
        }

    可以写成:

        list.forEach(o -> {System.out.println(o);}); //forEach函数实现内部迭代

    集合类(包括List)现在都有一个forEach方法,对元素进行迭代(遍历),所以我们不需要再写for循环了。forEach方法接受一个函数式接口Consumer做参数,所以可以使用λ表达式。

    这种内部迭代方法广泛存在于各种语言,如C++的STL算法库、python、ruby、scala等。

    Java8为集合类引入了另一个重要概念:流(stream)。一个流通常以一个集合类实例为其数据源,然后在其上定义各种操作。流的API设计使用了管道(pipelines)模式。对流的一次操作会返回另一个流。如同IO的API或者StringBuffer的append方法那样,从而多个不同的操作可以在一个语句里串起来。看下面的例子:

        List<Shape> shapes = ...
        shapes.stream()
          .filter(s -> s.getColor() == BLUE)
          .forEach(s -> s.setColor(RED));

    首先调用stream方法,以集合类对象shapes里面的元素为数据源,生成一个流。然后在这个流上调用filter方法,挑出蓝色的,返回另一个流。最后调用forEach方法将这些蓝色的物体喷成红色。(forEach方法不再返回流,而是一个终端方法,类似于StringBuffer在调用若干append之后的那个toString)

    filter方法的参数是Predicate类型,forEach方法的参数是Consumer类型,它们都是函数式接口,所以可以使用λ表达式。

    还有一个方法叫parallelStream(),顾名思义它和stream()一样,只不过指明要并行处理,以期充分利用现代CPU的多核特性。

        shapes.parallelStream(); // 或shapes.stream().parallel()

    来看更多的例子。下面是典型的大数据处理方法,Filter-Map-Reduce:

        //给出一个String类型的数组,找出其中所有不重复的素数
        public void distinctPrimary(String... numbers) {
            List<String> l = Arrays.asList(numbers);
            List<Integer> r = l.stream()
                    .map(e -> new Integer(e))
                    .filter(e -> Primes.isPrime(e))
                    .distinct()
                    .collect(Collectors.toList());
            System.out.println("distinctPrimary result is: " + r);
        }

    第一步:传入一系列String(假设都是合法的数字),转成一个List,然后调用stream()方法生成流。

    第二步:调用流的map方法把每个元素由String转成Integer,得到一个新的流。map方法接受一个Function类型的参数,上面介绍了,Function是个函数式接口,所以这里用λ表达式。

    第三步:调用流的filter方法,过滤那些不是素数的数字,并得到一个新流。filter方法接受一个Predicate类型的参数,上面介绍了,Predicate是个函数式接口,所以这里用λ表达式。

    第四步:调用流的distinct方法,去掉重复,并得到一个新流。这本质上是另一个filter操作。

    第五步:用collect方法将最终结果收集到一个List里面去。collect方法接受一个Collector类型的参数,这个参数指明如何收集最终结果。在这个例子中,结果简单地收集到一个List中。我们也可以用Collectors.toMap(e->e, e->e)把结果收集到一个Map中,它的意思是:把结果收到一个Map,用这些素数自身既作为键又作为值。toMap方法接受两个Function类型的参数,分别用以生成键和值,Function是个函数式接口,所以这里都用λ表达式。

    你可能会觉得在这个例子里,List l被迭代了好多次,map,filter,distinct都分别是一次循环,效率会不好。实际并非如此。这些返回另一个Stream的方法都是“懒(lazy)”的,而最后返回最终结果的collect方法则是“急(eager)”的。在遇到eager方法之前,lazy的方法不会执行。

    当遇到eager方法时,前面的lazy方法才会被依次执行。而且是管道贯通式执行。这意味着每一个元素依次通过这些管道。例如有个元素“3”,首先它被map成整数型3;然后通过filter,发现是素数,被保留下来;又通过distinct,如果已经有一个3了,那么就直接丢弃,如果还没有则保留。这样,3个操作其实只经过了一次循环。

    除collect外其它的eager操作还有forEach,toArray,reduce等。

    下面来看一下也许是最常用的收集器方法,groupingBy:

        //给出一个String类型的数组,找出其中各个素数,并统计其出现次数
        public void primaryOccurrence(String... numbers) {
            List<String> l = Arrays.asList(numbers);
            Map<Integer, Integer> r = l.stream()
                .map(e -> new Integer(e))
                .filter(e -> Primes.isPrime(e))
                .collect( Collectors.groupingBy(p->p, Collectors.summingInt(p->1)) );
            System.out.println("primaryOccurrence result is: " + r);
        }

    注意这一行:

        Collectors.groupingBy(p->p, Collectors.summingInt(p->1))

    它的意思是:把结果收集到一个Map中,用统计到的各个素数自身作为键,其出现次数作为值。

    下面是一个reduce的例子:

        //给出一个String类型的数组,求其中所有不重复素数的和
        public void distinctPrimarySum(String... numbers) {
            List<String> l = Arrays.asList(numbers);
            int sum = l.stream()
                .map(e -> new Integer(e))
                .filter(e -> Primes.isPrime(e))
                .distinct()
                .reduce(0, (x,y) -> x+y); // equivalent to .sum()
            System.out.println("distinctPrimarySum result is: " + sum);
        }

    reduce方法用来产生单一的一个最终结果。
    流有很多预定义的reduce操作,如sum(),max(),min()等。

    再举个现实世界里的栗子比如:

        // 统计年龄在25-35岁的男女人数、比例
        public void boysAndGirls(List<Person> persons) {
            Map<Integer, Integer> result = persons.parallelStream().filter(p -> p.getAge()>=25 && p.getAge()<=35).
                collect(
                    Collectors.groupingBy(p->p.getSex(), Collectors.summingInt(p->1))
            );
            System.out.print("boysAndGirls result is " + result);
            System.out.println(", ratio (male : female) is " + (float)result.get(Person.MALE)/result.get(Person.FEMALE));
        }


    3.3 λ表达式的更多用法

        // 嵌套的λ表达式
        Callable<Runnable> c1 = () -> () -> { System.out.println("Nested lambda"); };
        c1.call().run();

        // 用在条件表达式中
        Callable<Integer> c2 = true ? (() -> 42) : (() -> 24);
        System.out.println(c2.call());

        // 定义一个递归函数,注意须用this限定
        protected UnaryOperator<Integer> factorial = i -> i == 0 ? 1 : i * this.factorial.apply( i - 1 );
        ...
        System.out.println(factorial.apply(3));

    在Java中,随声明随调用的方式是不行的,比如下面这样,声明了一个λ表达式(x, y) -> x + y,同时企图通过传入实参(2, 3)来调用它:

        int five = ( (x, y) -> x + y ) (2, 3); // ERROR! try to call a lambda in-place

    这在C++中是可以的,但Java中不行。Java的λ表达式只能用作赋值、传参、返回值等。

    4. 其它相关概念

    4.1 捕获(Capture)

    捕获的概念在于解决在λ表达式中我们可以使用哪些外部变量(即除了它自己的参数和内部定义的本地变量)的问题。

    答案是:与内部类非常相似,但有不同点。不同点在于内部类总是持有一个其外部类对象的引用。而λ表达式呢,除非在它内部用到了其外部类(包围类)对象的方法或者成员,否则它就不持有这个对象的引用。

    在Java8以前,如果要在内部类访问外部对象的一个本地变量,那么这个变量必须声明为final才行。在Java8中,这种限制被去掉了,代之以一个新的概念,“effectively final”。它的意思是你可以声明为final,也可以不声明final但是按照final来用,也就是一次赋值永不改变。换句话说,保证它加上final前缀后不会出编译错误。

    在Java8中,内部类和λ表达式都可以访问effectively final的本地变量。λ表达式的例子如下:

        ...   
        int tmp1 = 1; //包围类的成员变量
        static int tmp2 = 2; //包围类的静态成员变量
        public void testCapture() {
            int tmp3 = 3; //没有声明为final,但是effectively final的本地变量
            final int tmp4 = 4; //声明为final的本地变量
            int tmp5 = 5; //普通本地变量
           
            Function<Integer, Integer> f1 = i -> i + tmp1;
            Function<Integer, Integer> f2 = i -> i + tmp2;
            Function<Integer, Integer> f3 = i -> i + tmp3;
            Function<Integer, Integer> f4 = i -> i + tmp4;
            Function<Integer, Integer> f5 = i -> {
                tmp5  += i; // 编译错!对tmp5赋值导致它不是effectively final的
                return tmp5;
            };
            ...
            tmp5 = 9; // 编译错!对tmp5赋值导致它不是effectively final的
        }
        ...

    Java要求本地变量final或者effectively final的原因是变量作用域和多线程问题

    4.2 方法引用(Method reference)

    任何一个λ表达式都可以代表某个函数式接口的唯一方法的匿名描述符。我们也可以使用某个类的某个具体方法来代表这个描述符,叫做方法引用。例如:

        Integer::parseInt //静态方法引用
        System.out::print //实例方法引用
        Person::new       //构造器引用

    下面是一组例子,教你使用方法引用代替λ表达式:

        //c1 与 c2 是一样的(静态方法引用)
        Comparator<Integer> c2 = (x, y) -> Integer.compare(x, y);
        Comparator<Integer> c1 = Integer::compare;
       
        //下面两句是一样的(实例方法引用1)
        persons.forEach(e -> System.out.println(e));
        persons.forEach(System.out::println);
       
        //下面两句是一样的(实例方法引用2)
        persons.forEach(person -> person.eat());
        persons.forEach(Person::eat);
       
        //下面两句是一样的(构造器引用)
        strList.stream().map(s -> new Integer(s));
        strList.stream().map(Integer::new);
       
    使用方法引用,你的程序会变得更短些。现在distinctPrimarySum方法可以改写如下:

        public void distinctPrimarySum(String... numbers) {
            List<String> l = Arrays.asList(numbers);
            int sum = l.stream().map(Integer::new).filter(Primes::isPrime).distinct().sum();
            System.out.println("distinctPrimarySum result is: " + sum);
        }
       
    还有一些其它的方法引用:

        super::toString //引用某个对象的父类方法
        String[]::new //引用一个数组的构造器

    4.3 默认方法(Default method)

    Java8中,接口声明里可以有方法实现了,叫做默认方法。在此之前,接口里的方法全部是抽象方法。

        public interface MyInterf {
       
            String m1();
           
            default String m2() {
                return "Hello default method!";
            }
           
        }
       
    这实际上混淆了接口和抽象类,但一个类仍然可以实现多个接口,而只能继承一个抽象类。

    这么做的原因是:由于Collection库需要为批处理操作添加新的方法,如forEach(),stream()等,但是不能修改现有的Collection接口——如果那样做的话所有的实现类都要进行修改,包括很多客户自制的实现类。所以只好使用这种妥协的办法。

    如此一来,我们就面临一种类似多继承的问题。如果类Sub继承了两个接口,Base1和Base2,而这两个接口恰好具有完全相同的两个默认方法,那么就会产生冲突。这时Sub类就必须通过重载来显式指明自己要使用哪一个接口的实现(或者提供自己的实现):
       
        public class Sub implements Base1, Base2 {
       
            public void hello() {
                Base1.super.hello(); //使用Base1的实现
            }
           
        }

    除了默认方法,Java8的接口也可以有静态方法的实现:

        public interface MyInterf {
       
            String m1();
           
            default String m2() {
                return "Hello default method!";
            }
           
            static String m3() {
                return "Hello static method in Interface!";
            }
           
        }
       
    4.4 生成器函数(Generator function)

    有时候一个流的数据源不一定是一个已存在的集合对象,也可能是个“生成器函数”。一个生成器函数会产生一系列元素,供给一个流。Stream.generate(Supplier<T> s)就是一个生成器函数。其中参数Supplier是一个函数式接口,里面有唯一的抽象方法 <T> get()。

    下面这个例子生成并打印5个随机数:

        Stream.generate(Math::random).limit(5).forEach(System.out::println);

    注意这个limit(5),如果没有这个调用,那么这条语句会永远地执行下去。也就是说这个生成器是无穷的。这种调用叫做终结操作,或者短路(short-circuiting)操作。 

     

    参考资料:
    http://openjdk.java.net/projects/lambda/
    http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

    展开全文
  • 要想理解lambda表达式,特别是为什么要使用lambda表达,首先要理解回调函数。如果不太理解回调函数请戳这里。简而言之,回调函数就是被作为参数供另一个函数调用的函数(注意不是函数的返回值被另一个函数调用,而是...

    lambda表达式是C++ 11的新特性。它是一个匿名函数,但是又跟函数不同。要想理解lambda表达式,特别是为什么要使用lambda表达,首先要理解回调函数。如果不太理解回调函数请戳这里。简而言之,回调函数就是被作为参数供另一个函数调用的函数(注意不是函数的返回值被另一个函数调用,而是函数的指针或者说代码被另一个函数调用)。比如void funcA(int i, bool (*funcB)(int))。 这里函数funcA有两个参数,一个是i,另一个就是函数funcB的指针。我们知道在声明一个函数时,函数参数的数据类型就确定了。所以如果要调用funcA,那么我们传递给它的参数必须是两个,一个是整型,另一个是以整型为参数以布尔型为返回值的函数类型(函数的类型由它的参数类型和返回值类型决定)。所以,我们在实现回调函数funcB时,必须按照这个参数和返回类型来实现。这就会带来一定的局限性。比如,我想让我的函数funcB更具普适性,让它多带一个参数以适应不同的情况,变成bool myFuncB(int i, int j)。那么它就不能作为参数被funcA调用了。这时lambda表达式就可以粉墨登场了。lambda表达式语法如下所示:

    [capture list](parameter list)->return type{function body}
    其中,参数列表和返回类型都可以省略。捕获列表也可以为空,但是[]必须写上。可见lambda表达式区别于普通函数,除了没有函数名外,还多了一个捕获列表。它之所以能解决我们之前所说的那个局限性就全靠这个捕获列表。捕获对象和参数的区别在于,捕获对象的值在程序运行到lambda表达式之前就已经确定了。而参数的值在运行到lamdba表达式之前是不确定的。举个栗子:

    void fun()
    {
          int i=1;
          int j=2;
    
          funcA(i, [j](int k){return k>j;});
    }
    当程序运行funcA之前,j的值就确定了,而k的值是不确定的。k的值只有在funcA函数里面真正调用lambda表达式的时候才确定。这个程序还可以换一种更加明确的写法。

    void fun()
    {
          int i=1;
          int j=2;
          function<bool(int)> f=[j](int k)(return k>j;);
    
          funcA(i, f);
    }
    当程序运行到创建f对象时,j的值是确定的(它其实被作为f的一个成员变量初始化了),而k的值是不确定的。注意lambda表达式是一种特殊的数据类型(即使它和某函数具有相同参数类型和返回值类型,它们也属于不同数据类型),编译器在编译的过程中会根据我们所写的lambda表达式自动产生一个数据类型。所以声明f对象的时候我们一般只能用auto f=[j](int k)(return k>j),因为我们不知道它是什么类型。但是我们之前说过回调函数的类型在声明调用者的时候(也就是声明funcA的时候)就已经确定了。所以这里有点矛盾。这个矛盾需要另一个库模版函数类型funcion来解决。有了它,只要参数和返回值类型都相同就可以定义为同一种function类型。

    lambda表达式可以解决回调函数 的参数适配问题。但是毕竟lambd表达式只能表达少量代码。当代码一多,用lambda表达式就很难看了。这时我们就需要另一个工具,叫做bind。欲知详情请戳这里


    水平有限,如有不妥,敬请拍砖!


    参考文献

    C++ Primer

    展开全文
  • 一、Lambda表达式什么 简单来说,Lambda表达式是一个可传递的代码段。可以不借助对象传递的一个代码段。那java中为什么要加入这么一个特性呢? 二、为什么使用Lambda表达式 从一个简单的例子开始。 比如现在有一个...

    一、Lambda表达式是什么

    简单来说,Lambda表达式是一个可传递的代码段。可以不借助对象传递的一个代码段。那java作为一个面向对象的语言,为什么要加入这么一个特性呢?

    二、为什么使用Lambda表达式

    从一个简单的例子开始。
    比如现在有一个Person类

    package POJO;
    
    import lombok.Data;
    import java.util.Objects;
    
    /**
     * @author wangtianqi
     * @create 2020-08-19 15:59
     */
    @Data
    public class Person {
        private String name;
        private Integer age;
        private String country;
        private char sex;
    }
    

    如果不使用Lambda表达式将下面这个Person集合中的元素按照年龄从小到大排序。

    List<Person> personList = new ArrayList<>();
            //personList.add(new Person("欧阳霜",18,"中国",'F'));
            personList.add(new Person("欧阳雪", 18, "中国", 'F'));
            personList.add(new Person("Tom", 24, "美国", 'M'));
            personList.add(new Person("Harley", 22, "英国", 'F'));
            personList.add(new Person("向天笑", 23, "中国", 'M'));
            personList.add(new Person("李康", 22, "中国", 'M'));
            personList.add(new Person("小梅", 20, "中国", 'F'));
            personList.add(new Person("何雪", 21, "中国", 'F'));
            personList.add(new Person("李康", 22, "中国", 'M'));
    

    很自然的可以想到,使用工具类Collections中的sort方法。sort方法需要提供一个Comparator接口的实现类。

    package POJO;
    
    import java.util.Comparator;
    
    /**
     * @author wangtianqi
     * @create 2020-09-08 10:03
     */
    public class ComparePerson implements Comparator<Person> {
    
        @Override
        public int compare(Person p1, Person p2) {
            return p1.getAge() - p2.getAge();
        }
    }
    
    

    然后使用sort方法将集合排序。

    /**
     * lambda表达式相关
     *
     * @author wangtianqi
     * @create 2020-09-08 10:05
     */
    public class Test3 {
    
        public static void main(String[] args) {
    
            List<Person> personList = new ArrayList<>();
            //personList.add(new Person("欧阳霜",18,"中国",'F'));
            personList.add(new Person("欧阳雪", 18, "中国", 'F'));
            personList.add(new Person("Tom", 24, "美国", 'M'));
            personList.add(new Person("Harley", 22, "英国", 'F'));
            personList.add(new Person("向天笑", 23, "中国", 'M'));
            personList.add(new Person("李康", 22, "中国", 'M'));
            personList.add(new Person("小梅", 20, "中国", 'F'));
            personList.add(new Person("何雪", 21, "中国", 'F'));
            personList.add(new Person("李康", 22, "中国", 'M'));
    
            Collections.sort(personList,new ComparePerson());
    
    		// 遍历打印
            for (Person person : personList) {
                System.out.println(person);
            }
        }
    }
    
    

    例子到这就结束了。再回过头看main方法中的Collections.sort(personList,new ComparePerson());这条语句。如果仅仅是看这条语句,并不能直观的了解到这个排序是按照哪个条件进行的。需要找到ComparePerson这个实现类,才能看到排序的条件。代码的可读性不高。并且提供实现类,也需要编写代码,代码不简洁
    如果java支持函数式编程,就可以很好的解决这问题。例如ComparePerson类中只提供了一种方法。使用函数式编程,就不需要为了这个代码段编写一个类,直接在需要的地方编写一个代码段即可。
    lambda表达式就是为了让java实现函数式编程而出现的。

    三、Lambda表达式的语法

    接着上面一节,这次我们使用Lambda表达式实现集合排序。

    import POJO.ComparePerson;
    import POJO.Person;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    /**
     * lambda表达式相关
     *
     * @author wangtianqi
     * @create 2020-09-08 10:05
     */
    public class Test3 {
    
        public static void main(String[] args) {
    
            List<Person> personList = new ArrayList<>();
            //personList.add(new Person("欧阳霜",18,"中国",'F'));
            personList.add(new Person("欧阳雪", 18, "中国", 'F'));
            personList.add(new Person("Tom", 24, "美国", 'M'));
            personList.add(new Person("Harley", 22, "英国", 'F'));
            personList.add(new Person("向天笑", 23, "中国", 'M'));
            personList.add(new Person("李康", 22, "中国", 'M'));
            personList.add(new Person("小梅", 20, "中国", 'F'));
            personList.add(new Person("何雪", 21, "中国", 'F'));
            personList.add(new Person("李康", 22, "中国", 'M'));
    
    //        Collections.sort(personList,new ComparePerson());
    
            Collections.sort(personList, (p1, p2) -> p1.getAge() - p2.getAge());
            
            for (Person person : personList) {
                System.out.println(person);
            }
        }
    }
    
    

    我们使用非常简短的一个代码段完成了ComparePerson类提供的功能。代码更简洁。并且对比使用实现类的语句,这段代码可以很清楚的了解到这次排序是按照年龄进行的。可读性更好。

    lambda表达式基本语法

    • Java8中引入了一个新的操作符,->,该操作符称为箭头操作符或者Lambda操作符,箭头操作符将Lambda表达式拆分成两部分;
    • 左侧: Lambda表达式的参数列表,对应的是接口中抽象方法的参数列表;
    • 右侧: Lambda表达式中所需要执行的功能(Lambda体),对应的是对抽象方法的实现;(函数式接口(只能有一个抽象方法))
    • Lambda表达式的实质是 对接口的实现;当接口只有一个方法(非静态方法,非默认方法)时,才可以使用lambda表达式。

    四、函数式接口

    上面提到了当接口只有一个方法(非静态方法,非默认方法)时,才可以使用lambda表达式。 在java中这种接口叫做,函数式接口。可以使用@FunctionalInterface注解来声明某个接口是一个函数式接口。
    当在方法中的某个参数为函数式接口的引用时,在调用该方法时,就可以使用lambda表达式为该接口引用提供一个具体实现。

    看一眼sort方法的源码,第二个参数Comparator<? super T> c是一个接口引用。

    public static <T> void sort(List<T> list, Comparator<? super T> c) {
            list.sort(c);
        }
    

    点进去看一眼,果然Comparator<T>是一个函数式接口。所以我们可以在使用sort方法时使用lambda表达式作为第二个参数。

    @FunctionalInterface
    public interface Comparator<T> {
    

    五、Java中提供的四大函数式接口

    如果你想要在自己的方法中添加函数式接口引用作为参数,Java提供了非常有代表性的四个函数式接口,我们只需要选择自己想要的接口即可。

    四大函数式接口

    • Consumer< T >消费型 接口: void accept(T t);
      • 消费型接口Consumer,是一个有参数,但是无返回值类型的接口(void accept(T t);)
    • Supplier< T >供给型接口 : T get();
      • 供给型接口Supplier,是一个只有产出,没有输入,就是只有返回值,没有参数(T get();)
    • Function< T , R > 函数式接口 : R apply (T t);
      • 函数型接口Function<T,R>,是一个参数为T类型,返回值为R类型的接口(R apply(T t);)
    • Predicate< T > 断言型接口 : boolean test(T t);
      • 断言型接口Predicate,是一个参数为T类型,返回值为boolean类型的接口(boolean test(T t);)

    六、总结

    为何使用: 使用lambda表达式可以让代码的可读性更强,同时代码更简洁。但lambda的意义不仅仅于此,这里只是最显而易见的好处。
    何时使用: 当调用的方法中的某个参数为函数式接口的引用时,就可以使用lambda表达式为该接口引用提供一个具体实现。
    实质: 接口的具体实现。只不过不需要单独编写类。

    展开全文
  • Java Lambda表达式入门

    万次阅读 多人点赞 2014-04-27 21:17:58
    原文链接:Start Using Java Lambda Expressions 下载示例程序 Examples.zip 。...虽然看着很先进,其实Lambda表达式本质只是一个"语法糖",由编译器推断并帮你转换包装为常规的代码,因此你可以使用...

    原文链接: Start Using Java Lambda Expressions

    下载示例程序 Examples.zip
    原文日期: 2014年4月16日

    翻译日期: 2014年4月27日
    翻译人员: 铁锚
    简介

    (译者认为: 超过3行的逻辑就不适用Lambda表达式了。虽然看着很先进,其实Lambda表达式的本质只是一个"语法糖",由编译器推断并帮你转换包装为常规的代码,因此你可以使用更少的代码来实现同样的功能。本人建议不要乱用,因为这就和某些很高级的黑客写的代码一样,简洁,难懂,难以调试,维护人员想骂娘.)
    Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。
    Lambda表达式还增强了集合库。 Java SE 8添加了2个对集合数据进行批量操作的包: java.util.function 包以及 java.util.stream 包。 流(stream)就如同迭代器(iterator),但附加了许多额外的功能。 总的来说,lambda表达式和 stream 是自Java语言添加泛型(Generics)和注解(annotation)以来最大的变化。 在本文中,我们将从简单到复杂的示例中见认识lambda表达式和stream的强悍。
    环境准备
    如果还没有安装Java 8,那么你应该先安装才能使用lambda和stream(译者建议在虚拟机中安装,测试使用)。 像NetBeans 和IntelliJ IDEA 一类的工具和IDE就支持Java 8特性,包括lambda表达式,可重复的注解,紧凑的概要文件和其他特性。
    下面是Java SE 8和NetBeans IDE 8的下载链接:
    Java Platform (JDK 8): 从Oracle下载Java 8,也可以和NetBeans IDE一起下载
    NetBeans IDE 8: 从NetBeans官网下载NetBeans IDE
    Lambda表达式的语法
    基本语法:
    (parameters) -> expression

    (parameters) ->{ statements; }

    下面是Java lambda表达式的简单例子:

    // 1. 不需要参数,返回值为 5
    () -> 5
    
    // 2. 接收一个参数(数字类型),返回其2倍的值
    x -> 2 * x
    
    // 3. 接受2个参数(数字),并返回他们的差值
    (x, y) -> x – y
    
    // 4. 接收2个int型整数,返回他们的和
    (int x, int y) -> x + y
    
    // 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
    (String s) -> System.out.print(s)

    基本的Lambda例子
    现在,我们已经知道什么是lambda表达式,让我们先从一些基本的例子开始。 在本节中,我们将看到lambda表达式如何影响我们编码的方式。 假设有一个玩家List ,程序员可以使用 for 语句 ("for 循环")来遍历,在Java SE 8中可以转换为另一种形式:

     

    String[] atp = {"Rafael Nadal", "Novak Djokovic",
           "Stanislas Wawrinka",
           "David Ferrer","Roger Federer",
           "Andy Murray","Tomas Berdych",
           "Juan Martin Del Potro"};
    List<String> players =  Arrays.asList(atp);
    
    // 以前的循环方式
    for (String player : players) {
         System.out.print(player + "; ");
    }
    
    // 使用 lambda 表达式以及函数操作(functional operation)
    players.forEach((player) -> System.out.print(player + "; "));
     
    // 在 Java 8 中使用双冒号操作符(double colon operator)
    players.forEach(System.out::println);

    正如您看到的,lambda表达式可以将我们的代码缩减到一行。 另一个例子是在图形用户界面程序中,匿名类可以使用lambda表达式来代替。 同样,在实现Runnable接口时也可以这样使用:

     

    // 使用匿名内部类
    btn.setOnAction(new EventHandler<ActionEvent>() {
              @Override
              public void handle(ActionEvent event) {
                  System.out.println("Hello World!"); 
              }
        });
     
    // 或者使用 lambda expression
    btn.setOnAction(event -> System.out.println("Hello World!"));
    

    下面是使用lambdas 来实现 Runnable接口 的示例:

     

    // 1.1使用匿名内部类
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("Hello world !");
        }
    }).start();
    
    // 1.2使用 lambda expression
    new Thread(() -> System.out.println("Hello world !")).start();
    
    // 2.1使用匿名内部类
    Runnable race1 = new Runnable() {
        @Override
        public void run() {
            System.out.println("Hello world !");
        }
    };
    
    // 2.2使用 lambda expression
    Runnable race2 = () -> System.out.println("Hello world !");
     
    // 直接调用 run 方法(没开新线程哦!)
    race1.run();
    race2.run();


    Runnable 的 lambda表达式,使用块格式,将五行代码转换成单行语句。 接下来,在下一节中我们将使用lambdas对集合进行排序。
    使用Lambdas排序集合
    在Java中,Comparator 类被用来排序集合。 在下面的例子中,我们将根据球员的 name, surname, name 长度 以及最后一个字母。 和前面的示例一样,先使用匿名内部类来排序,然后再使用lambda表达式精简我们的代码。
    在第一个例子中,我们将根据name来排序list。 使用旧的方式,代码如下所示:

     

    String[] players = {"Rafael Nadal", "Novak Djokovic", 
        "Stanislas Wawrinka", "David Ferrer",
        "Roger Federer", "Andy Murray",
        "Tomas Berdych", "Juan Martin Del Potro",
        "Richard Gasquet", "John Isner"};
     
    // 1.1 使用匿名内部类根据 name 排序 players
    Arrays.sort(players, new Comparator<String>() {
        @Override
        public int compare(String s1, String s2) {
            return (s1.compareTo(s2));
        }
    });

    使用lambdas,可以通过下面的代码实现同样的功能:

     

    // 1.2 使用 lambda expression 排序 players
    Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2));
    Arrays.sort(players, sortByName);
    
    // 1.3 也可以采用如下形式:
    Arrays.sort(players, (String s1, String s2) -> (s1.compareTo(s2)));


    其他的排序如下所示。 和上面的示例一样,代码分别通过匿名内部类和一些lambda表达式来实现Comparator :

     

    // 1.1 使用匿名内部类根据 surname 排序 players
    Arrays.sort(players, new Comparator<String>() {
        @Override
        public int compare(String s1, String s2) {
            return (s1.substring(s1.indexOf(" ")).compareTo(s2.substring(s2.indexOf(" "))));
        }
    });
    
    // 1.2 使用 lambda expression 排序,根据 surname
    Comparator<String> sortBySurname = (String s1, String s2) -> 
        ( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) );
    Arrays.sort(players, sortBySurname);
    
    // 1.3 或者这样,怀疑原作者是不是想错了,括号好多...
    Arrays.sort(players, (String s1, String s2) -> 
          ( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) ) 
        );
    
    // 2.1 使用匿名内部类根据 name lenght 排序 players
    Arrays.sort(players, new Comparator<String>() {
        @Override
        public int compare(String s1, String s2) {
            return (s1.length() - s2.length());
        }
    });
    
    // 2.2 使用 lambda expression 排序,根据 name lenght
    Comparator<String> sortByNameLenght = (String s1, String s2) -> (s1.length() - s2.length());
    Arrays.sort(players, sortByNameLenght);
    
    // 2.3 or this
    Arrays.sort(players, (String s1, String s2) -> (s1.length() - s2.length()));
    
    // 3.1 使用匿名内部类排序 players, 根据最后一个字母
    Arrays.sort(players, new Comparator<String>() {
        @Override
        public int compare(String s1, String s2) {
            return (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));
        }
    });
    
    // 3.2 使用 lambda expression 排序,根据最后一个字母
    Comparator<String> sortByLastLetter = 
        (String s1, String s2) -> 
            (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));
    Arrays.sort(players, sortByLastLetter);
    
    // 3.3 or this
    Arrays.sort(players, (String s1, String s2) -> (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)));
    

    就是这样,简洁又直观。 在下一节中我们将探索更多lambdas的能力,并将其与 stream 结合起来使用。
    使用Lambdas和Streams
    Stream是对集合的包装,通常和lambda一起使用。 使用lambdas可以支持许多操作,如 map, filter, limit, sorted, count, min, max, sum, collect 等等。 同样,Stream使用懒运算,他们并不会真正地读取所有数据,遇到像getFirst() 这样的方法就会结束链式语法。 在接下来的例子中,我们将探索lambdas和streams 能做什么。 我们创建了一个Person类并使用这个类来添加一些数据到list中,将用于进一步流操作。 Person 只是一个简单的POJO类:

     

    public class Person {
    
    private String firstName, lastName, job, gender;
    private int salary, age;
    
    public Person(String firstName, String lastName, String job,
                    String gender, int age, int salary)       {
              this.firstName = firstName;
              this.lastName = lastName;
              this.gender = gender;
              this.age = age;
              this.job = job;
              this.salary = salary;
    }
    // Getter and Setter 
    // . . . . .
    }

    接下来,我们将创建两个list,都用来存放Person对象:

     

    List<Person> javaProgrammers = new ArrayList<Person>() {
      {
        add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 43, 2000));
        add(new Person("Tamsen", "Brittany", "Java programmer", "female", 23, 1500));
        add(new Person("Floyd", "Donny", "Java programmer", "male", 33, 1800));
        add(new Person("Sindy", "Jonie", "Java programmer", "female", 32, 1600));
        add(new Person("Vere", "Hervey", "Java programmer", "male", 22, 1200));
        add(new Person("Maude", "Jaimie", "Java programmer", "female", 27, 1900));
        add(new Person("Shawn", "Randall", "Java programmer", "male", 30, 2300));
        add(new Person("Jayden", "Corrina", "Java programmer", "female", 35, 1700));
        add(new Person("Palmer", "Dene", "Java programmer", "male", 33, 2000));
        add(new Person("Addison", "Pam", "Java programmer", "female", 34, 1300));
      }
    };
    
    List<Person> phpProgrammers = new ArrayList<Person>() {
      {
        add(new Person("Jarrod", "Pace", "PHP programmer", "male", 34, 1550));
        add(new Person("Clarette", "Cicely", "PHP programmer", "female", 23, 1200));
        add(new Person("Victor", "Channing", "PHP programmer", "male", 32, 1600));
        add(new Person("Tori", "Sheryl", "PHP programmer", "female", 21, 1000));
        add(new Person("Osborne", "Shad", "PHP programmer", "male", 32, 1100));
        add(new Person("Rosalind", "Layla", "PHP programmer", "female", 25, 1300));
        add(new Person("Fraser", "Hewie", "PHP programmer", "male", 36, 1100));
        add(new Person("Quinn", "Tamara", "PHP programmer", "female", 21, 1000));
        add(new Person("Alvin", "Lance", "PHP programmer", "male", 38, 1600));
        add(new Person("Evonne", "Shari", "PHP programmer", "female", 40, 1800));
      }
    };

    现在我们使用forEach方法来迭代输出上述列表:

     

    System.out.println("所有程序员的姓名:");
    javaProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
    phpProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

    我们同样使用forEach方法,增加程序员的工资5%:

     

    System.out.println("给程序员加薪 5% :");
    Consumer<Person> giveRaise = e -> e.setSalary(e.getSalary() / 100 * 5 + e.getSalary());
    
    javaProgrammers.forEach(giveRaise);
    phpProgrammers.forEach(giveRaise);

    另一个有用的方法是过滤器filter() ,让我们显示月薪超过1400美元的PHP程序员:

     

    System.out.println("下面是月薪超过 $1,400 的PHP程序员:")
    phpProgrammers.stream()
              .filter((p) -> (p.getSalary() > 1400))
              .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

    我们也可以定义过滤器,然后重用它们来执行其他操作:

     

    // 定义 filters
    Predicate<Person> ageFilter = (p) -> (p.getAge() > 25);
    Predicate<Person> salaryFilter = (p) -> (p.getSalary() > 1400);
    Predicate<Person> genderFilter = (p) -> ("female".equals(p.getGender()));
    
    System.out.println("下面是年龄大于 24岁且月薪在$1,400以上的女PHP程序员:");
    phpProgrammers.stream()
              .filter(ageFilter)
              .filter(salaryFilter)
              .filter(genderFilter)
              .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
    
    // 重用filters
    System.out.println("年龄大于 24岁的女性 Java programmers:");
    javaProgrammers.stream()
              .filter(ageFilter)
              .filter(genderFilter)
              .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
    

    使用limit方法,可以限制结果集的个数:

     

    System.out.println("最前面的3个 Java programmers:");
    javaProgrammers.stream()
              .limit(3)
              .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
    
    
    System.out.println("最前面的3个女性 Java programmers:");
    javaProgrammers.stream()
              .filter(genderFilter)
              .limit(3)
              .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

    排序呢? 我们在stream中能处理吗? 答案是肯定的。 在下面的例子中,我们将根据名字和薪水排序Java程序员,放到一个list中,然后显示列表:

    // 静态引入

    import static java.util.stream.Collectors.toList;

     

    System.out.println("根据 name 排序,并显示前5个 Java programmers:");
    List<Person> sortedJavaProgrammers = javaProgrammers
              .stream()
              .sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName())))
              .limit(5)
              .collect(toList());
    
    sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));
     
    System.out.println("根据 salary 排序 Java programmers:");
    sortedJavaProgrammers = javaProgrammers
              .stream()
              .sorted( (p, p2) -> (p.getSalary() - p2.getSalary()) )
              .collect( toList() );
    
    sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));

    如果我们只对最低和最高的薪水感兴趣,比排序后选择第一个/最后一个 更快的是min和max方法:

     

    System.out.println("工资最低的 Java programmer:");
    Person pers = javaProgrammers
              .stream()
              .min((p1, p2) -> (p1.getSalary() - p2.getSalary()))
              .get()
    
    System.out.printf("Name: %s %s; Salary: $%,d.", pers.getFirstName(), pers.getLastName(), pers.getSalary())
    
    System.out.println("工资最高的 Java programmer:");
    Person person = javaProgrammers
              .stream()
              .max((p, p2) -> (p.getSalary() - p2.getSalary()))
              .get()
    
    System.out.printf("Name: %s %s; Salary: $%,d.", person.getFirstName(), person.getLastName(), person.getSalary())

    上面的例子中我们已经看到 collect 方法是如何工作的。 结合 map 方法,我们可以使用 collect 方法来将我们的结果集放到一个字符串,一个 Set 或一个TreeSet中:

     

    System.out.println("将 PHP programmers 的 first name 拼接成字符串:");
    String phpDevelopers = phpProgrammers
              .stream()
              .map(Person::getFirstName)
              .collect(joining(" ; ")); // 在进一步的操作中可以作为标记(token)   
    
    System.out.println("将 Java programmers 的 first name 存放到 Set:");
    Set<String> javaDevFirstName = javaProgrammers
              .stream()
              .map(Person::getFirstName)
              .collect(toSet());
    
    System.out.println("将 Java programmers 的 first name 存放到 TreeSet:");
    TreeSet<String> javaDevLastName = javaProgrammers
              .stream()
              .map(Person::getLastName)
              .collect(toCollection(TreeSet::new));
    

    Streams 还可以是并行的(parallel)。 示例如下:

     

    System.out.println("计算付给 Java programmers 的所有money:");
    int totalSalary = javaProgrammers
              .parallelStream()
              .mapToInt(p -> p.getSalary())
              .sum();

    我们可以使用summaryStatistics方法获得stream 中元素的各种汇总数据。 接下来,我们可以访问这些方法,比如getMax, getMin, getSum或getAverage:

     

    //计算 count, min, max, sum, and average for numbers
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    IntSummaryStatistics stats = numbers
              .stream()
              .mapToInt((x) -> x)
              .summaryStatistics();
    
    System.out.println("List中最大的数字 : " + stats.getMax());
    System.out.println("List中最小的数字 : " + stats.getMin());
    System.out.println("所有数字的总和   : " + stats.getSum());
    System.out.println("所有数字的平均值 : " + stats.getAverage()); 

    OK,就这样,希望你喜欢它!
    总结
    在本文中,我们学会了使用lambda表达式的不同方式,从基本的示例,到使用lambdas和streams的复杂示例。 此外,我们还学习了如何使用lambda表达式与Comparator 类来对Java集合进行排序。

    展开全文
  • lambda表达式

    2021-06-14 19:21:11
    1.1什么是lambda表达式 Lambda Lambda 是一个 匿名函数 ,我们可以把 Lambda Lambda Lambda 表达式理解为是 一段可以 传递的代码 (将代码像数据一样进行传递)。使用它可以写出更 简洁、灵活的代码。作为一种更紧凑...
  • python lambda表达式

    2021-01-20 02:25:38
    lambda 表达式的语法格式为: ...lambda 表达式本质是匿名的、单行函数体的函数,故lambda 表达式可以写成函数的形式。例如,对于如下 lambda 表达式lambda x , y : x * y改写为函数形式如下: def add(x, y):
  • Lambda 表达式

    2019-05-27 11:08:46
    *同时可以作为参数传入到方法中执行,而执行的具体动作就取决于我们书写的lambda表达式定义了什么动作。 lambda表达式本质就是一个匿名函数。 lambda表达式使Java在具有面向对象的同时也具有函数式编程的可能性。 ...
  • Lambda表达式

    2021-03-05 16:51:34
    Lambda表达式背景基础语法函数式接口Lambda表达式的基本使用变量的捕获匿名类中的变量捕获Lambda中的变量捕获Lambda在集合当中的使用Lambda的优点和缺点 背景 Lambda表达式是Java SE 8中一个重要的新特性。lambda...
  • lambda表达式javaJava lambda expression can be considered as one of the coolest feature introduced in Java 8. It is Java’s first step into functional programming. A Java lambda expression can be ...
  • lambda表达式java 在本文中,我们提供了全面的Lambda Expressions Java教程。 1. Lambda Expressions Java教程–简介 Lambda表达式被认为是Java 8中引入的最好的功能之一。Lambda表达式被认为是Java进入函数式...
  • 1、静态内部类 静态内部类,属于类中的类,好处是:如果不.../*Lambda表达式 * 避免匿名内部类定义过多 * 简化线程的使用 */ public class Lambda { // 静态内部类 static class Test implements Run...
  • Lambda表达式实质属于函数式编程思想的概念 使用Lamba表达式可以避免匿名内部类定义过多 Lambda表达式的用法: lambda表达式只能有一行代码的情况下,才能简化成为一行,如果有多行那么就用代码块包裹 必须是函数...
  • c#中lambda表达式 Lambda表达式最初是在.NET 3.5中引入的,同时提供了语言集成查询(LINQ)。 Lambda表达式就像匿名方法,但具有更大的灵活性。 使用lambda表达式时,无需指定输入的类型。 因此,lambda表达式提供了...
  • lambda表达式,从本质上来讲,是一个匿名函数,可以使用这个匿名函数,实现接口中的方法,对接口进行非常简洁的实现,从而简化代码。 1.2Lambda表达式使用的场景: 通常来讲,使用lambda表达式,是为了简化接口实现...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 26,146
精华内容 10,458
关键字:

本质上lambda表达式是什么