精华内容
下载资源
问答
  • 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

    展开全文
  • 主要介绍了Python lambda表达式用法,结合实例形式分析了lambda表达式的具体功能、应用场景及相关使用技巧,需要的朋友可以参考下
  • 本文实例讲述了Python lambda表达式用法。分享给大家供大家参考,具体如下:lambda表达式,通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数。lambda所表示的匿名函数的内容应该...

    本文实例讲述了Python lambda表达式用法。分享给大家供大家参考,具体如下:

    lambda表达式,通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数。

    lambda所表示的匿名函数的内容应该是很简单的,如果复杂的话,干脆就重新定义一个函数了,使用lambda就有点过于执拗了。

    lambda就是用来定义一个匿名函数的,如果还要给他绑定一个名字的话,就会显得有点画蛇添足,通常是直接使用lambda函数。如下所示:

    add = lambda x, y : x+y

    add(1,2) # 结果为3

    那么到底要如何使用lambda表达式呢?

    1、应用在函数式编程中

    Python提供了很多函数式编程的特性,如:map、reduce、filter、sorted等这些函数都支持函数作为参数,lambda函数就可以应用在函数式编程中。如下:

    # 需求:将列表中的元素按照绝对值大小进行升序排列

    list1 = [3,5,-4,-1,0,-2,-6]

    sorted(list1, key=lambda x: abs(x))

    运行结果:

    [0, -1, -2, 3, -4, 5, -6]

    当然,也可以如下:

    list1 = [3,5,-4,-1,0,-2,-6]

    def get_abs(x):

    return abs(x)

    sorted(list1,key=get_abs)

    只不过这种方式的代码看起来不够Pythonic

    2、应用在闭包中

    def get_y(a,b):

    return lambda x:ax+b

    y1 = get_y(1,1)

    y1(1) # 结果为2

    当然,也可以用常规函数实现闭包,如下:

    def get_y(a,b):

    def func(x):

    return ax+b

    return func

    y1 = get_y(1,1)

    y1(1) # 结果为2

    只不过这种方式显得有点啰嗦。

    那么是不是任何情况下lambda函数都要比常规函数更清晰明了呢?

    肯定不是。

    Python之禅中有这么一句话:Explicit is better than implicit(明了胜于晦涩),就是说那种方式更清晰就用哪一种方式,不要盲目的都使用lambda表达式。

    希望本文所述对大家Python程序设计有所帮助。

    展开全文
  • 本文以实例形式介绍了C#中Lambda表达式的用法,分享给大家供大家参考之用。具体如下: 从委托的角度来看,Lambda表达式与匿名方法没有区别。在前面C#基础之匿名方法一文中,我们使用了匿名方法来调用List的FindAll...
  • Java Lambda表达式入门

    万次阅读 多人点赞 2014-04-27 21:17:58
    原文链接:Start Using Java ...(译者认为: 超过3行的逻辑就不适用Lambda表达式了。虽然看着很先进,其实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 表达式使用例子

    2019-01-05 14:41:25
    Java 8 的发布,尤其是 lambda 表达式和流 API。当你越来越多的了解它们,就能写出更干净的代码。虽然一开始并不是这样。第一次看到用 lambda 表达式写出来的 Java 代码时,对这种神秘的语法感到非常失望,认为它们...

    ? lambda 表达式

    Java 8 的发布,尤其是 lambda 表达式和流 API。当你越来越多的了解它们,就能写出更干净的代码。虽然一开始并不是这样。第一次看到用 lambda 表达式写出来的 Java 代码时,对这种神秘的语法感到非常失望,认为它们把Java搞得不可读,但我错了。花了一天时间做了一些 lambda 表达式和流API示例的练习后,我开心的看到了更清晰的 Java 代码。所以不要抵触 lambda 表达式以及方法引用的神秘语法,多做几次练习,从集合类中提取、过滤数据之后,你肯定会喜欢上它。

    简介

    Lambda 表达式是一个匿名函数。简单来说,这是一种没有声明的方法,即没有访问修饰符,返回值声明和名称。在你需要使用某个方法的地方写上它。当某个方法只使用一次,而且定义很简短,使用这种速记替代之尤其有效,这样,你就不必在类中费力写声明与方法了。
    Java 中的 Lambda 表达式通常使用 (argument) -> (body) 语法书写,例如:

    (arg1, arg2...) -> { body }
    
    (type1 arg1, type2 arg2...) -> { body }
    

    表达式的结构

    • 一个 Lambda 表达式可以有零个或多个参数
    • 参数的类型既可以明确声明,也可以根据上下文来推断。例如:(int a)与(a)效果相同
    • 所有参数需包含在圆括号内,参数之间用逗号相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)
    • 空圆括号代表参数集为空。例如:() -> 42
    • 当只有一个参数,且其类型可推导时,圆括号()可省略。例如:a -> return a * a
    • Lambda 表达式的主体可包含零条或多条语句
    • 如果 Lambda 表达式的主体只有一条语句,花括号{}可省略。匿名函数的返回类型与该主体表达式一致
    • 如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空

    表达式的一些示例

    (int a, int b) -> {  return a + b; }
    
    () -> System.out.println("Hello World");
    
    (String s) -> { System.out.println(s); }
    
    () -> 42
    
    () -> { return 3.1415 };
    

    使用场景

    1. 实现Runnable

    开始使用Java 8时,首先做的就是使用lambda表达式替换匿名类,而实现Runnable接口是匿名类的最好示例。看一下Java 8之前的runnable实现方法,需要4行代码,而使用lambda表达式只需要一行代码。我们在这里做了什么呢?那就是用() -> {}代码块替代了整个匿名类

    // Java 8之前:
    new Thread(new Runnable() {
        @Override
        public void run() {
        System.out.println("Before Java8, too much code for too little to do");
        }
    }).start();
    
    //Java 8方式:
    new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!") ).start();
    

    2. 遍历列表

    如果你使过几年Java,你就知道针对集合类,最常见的操作就是进行迭代,并将业务逻辑应用于各个元素,例如处理订单、交易和事件的列表。由于 Java 是命令式语言,Java 8之前的所有循环代码都是顺序的,即可以对其元素进行并行化处理。如果你想做并行过滤,就需要自己写代码,这并不是那么容易。通过引入 lambda 表达式和默认方法,将做什么和怎么做的问题分开了,这意味着Java集合现在知道怎样做迭代,并可以在API层面对集合元素进行并行处理。下面的例子里,我将介绍如何在使用lambda或不使用lambda表达式的情况下迭代列表。你可以看到列表现在有了一个 forEach() 方法,它可以迭代所有对象,并将你的lambda代码应用在其中。

    // Java 8之前:
    List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API");
    for (String feature : features) {
        System.out.println(feature);
    }
    
    // Java 8之后:
    List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API");
    features.forEach(n -> System.out.println(n));
    

    使用 Java 8 的方法引用更方便,方法引用由::双冒号操作符标示,双冒号(::)操作符是 Java 中的方法引用。当们使用一个方法的引用时,目标引用放在 :: 之前,目标引用提供的方法名称放在 :: 之后,即目标引用::方法。比如:Person::getAge,调用Person的getAge方法;

    features.forEach(System.out::println);
    

    3. 函数式接口Predicate

    除了在语言层面支持函数式编程风格,Java 8也添加了一个包,叫做 java.util.function。它包含了很多类,用来支持Java的函数式编程。其中一个便是Predicate,使用 java.util.function.Predicate 函数式接口以及lambda表达式,可以向API方法添加逻辑,用更少的代码支持更多的动态行为。下面是Java 8 Predicate 的例子,展示了过滤集合数据的多种常用方法。Predicate接口非常适用于做过滤。

    public static void main(args[]){
        List languages = Arrays.asList("Java", "Scala", "C++", "Haskell", "Lisp");
    
        System.out.println("Languages which starts with J :");
        filter(languages, (str)->str.startsWith("J"));
    
        System.out.println("Languages which ends with a ");
        filter(languages, (str)->str.endsWith("a"));
    
        System.out.println("Print all languages :");
        filter(languages, (str)->true);
    
        System.out.println("Print no language : ");
        filter(languages, (str)->false);
    
        System.out.println("Print language whose length greater than 4:");
        filter(languages, (str)->str.length() > 4);
    }
    
    public static void filter(List names, Predicate condition) {
        for(String name: names)  {
            if(condition.test(name)) {
                System.out.println(name + " ");
            }
        }
    }
    
    // 更好的办法
    public static void filter(List names, Predicate condition) {
        names.stream().filter((name) -> (condition.test(name))).forEach((name) -> {
            System.out.println(name + " ");
        });
    }
    

    4. Map 和 Reduce

    • Map

    本例介绍最广为人知的函数式编程概念map。它允许你将对象进行转换。例如在本例中,我们将 costBeforeTax 列表的每个元素转换成为税后的值。我们将 x -> x*x lambda表达式传到 map() 方法,后者将其应用到流中的每一个元素。然后用 forEach() 将列表元素打印出来。使用流API的收集器类,可以得到所有含税的开销。有 toList() 这样的方法将 map 或任何其他操作的结果合并起来。由于收集器在流上做终端操作,因此之后便不能重用流了。你甚至可以用流API的 reduce() 方法将所有数字合成一个,下一个例子将会讲到。

    // 不使用lambda表达式为每个订单加上12%的税
    List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
    for (Integer cost : costBeforeTax) {
        double price = cost + .12*cost;
        System.out.println(price);
    }
    
    // 使用lambda表达式
    List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
    costBeforeTax.stream().map((cost) -> cost + .12*cost).forEach(System.out::println);
    
    • Reduce

    在上个例子中,可以看到map将集合类(例如列表)元素进行转换的。还有一个 reduce() 函数可以将所有值合并成一个。Map和Reduce操作是函数式编程的核心操作,因为其功能,reduce 又被称为折叠操作。另外,reduce 并不是一个新的操作,你有可能已经在使用它。SQL中类似 sum()、avg() 或者 count() 的聚集函数,实际上就是 reduce 操作,因为它们接收多个值并返回一个值。流API定义的 reduceh() 函数可以接受lambda表达式,并对所有值进行合并。IntStream这样的类有类似 average()、count()、sum()的内建方法来做 reduce 操作,也有mapToLong()、mapToDouble() 方法来做转换。这并不会限制你,你可以用内建方法,也可以自己定义。在这个Java 8的Map Reduce示例里,我们首先对所有价格应用 12% 的VAT,然后用 reduce() 方法计算总和。

    / 为每个订单加上12%的税
    // 老方法:
    List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
    double total = 0;
    for (Integer cost : costBeforeTax) {
        double price = cost + .12*cost;
        total = total + price;
    }
    System.out.println("Total : " + total);
    
    // 新方法:
    List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
    double bill = costBeforeTax.stream().map((cost) -> cost + .12*cost).reduce((sum, cost) -> sum + cost).get();
    System.out.println("Total : " + bill);
    

    5. 过滤创建一个 String 列表

    过滤是 Java 开发者在大规模集合上的一个常用操作,而现在使用 lambda 表达式和流 API 过滤大规模数据集合是惊人的简单。流提供了一个 filter() 方法,接受一个 Predicate 对象,即可以传入一个 lambda 表达式作为过滤逻辑。下面的例子是用 lambda 表达式过滤 Java 集合,将帮助理解。

    // 创建一个字符串列表,每个字符串长度大于2
    List<String> filtered = strList.stream().filter(x -> x.length()> 2).collect(Collectors.toList());
    System.out.printf("Original List : %s, filtered list : %s %n", strList, filtered);
    
    // 输出 Original List : [abc, , bcd, , defg, jk], filtered list : [abc, bcd, defg]
    

    6. 对列表的每个元素应用函数

    我们通常需要对列表的每个元素使用某个函数,例如逐一乘以某个数、除以某个数或者做其它操作。这些操作都很适合用 map() 方法,可以将转换逻辑以lambda表达式的形式放在 map() 方法里,就可以对集合的各个元素进行转换了,如下所示。

    // 将字符串换成大写并用逗号链接起来
    List<String> list = Arrays.asList("USA", "Japan", "France", "Germany", "Italy", "U.K.","Canada");
    String G7Countries = list.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", "));
    System.out.println(G7Countries);
    
    // 输出:USA, JAPAN, FRANCE, GERMANY, ITALY, U.K., CANADA
    

    7. 复制不同的值,创建一个子列表

    本例展示了如何利用流的 distinct() 方法来对集合进行去重。

    // 用所有不同的数字创建一个正方形列表
    List<Integer> numbers = Arrays.asList(9, 10, 3, 4, 7, 3, 4);
    List<Integer> distinct = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
    System.out.printf("Original List : %s,  Square Without duplicates : %s %n", numbers, distinct);
    
    // 输出 Original List : [9, 10, 3, 4, 7, 3, 4],  Square Without duplicates : [81, 100, 9, 16, 49]
    

    8. 计算集合元素的最大值、最小值、总和以及平均值

    IntStream、LongStream 和 DoubleStream 等流的类中,有个非常有用的方法叫做 summaryStatistics() 。可以返回 IntSummaryStatistics、LongSummaryStatistics 或者 DoubleSummaryStatistic s,描述流中元素的各种摘要数据。在本例中,我们用这个方法来计算列表的最大值和最小值。它也有 getSum() 和 getAverage() 方法来获得列表的所有元素的总和及平均值。

    //获取数字的个数、最小值、最大值、总和以及平均值
    List<Integer> primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
    IntSummaryStatistics stats = primes.stream().mapToInt((x) -> x).summaryStatistics();
    System.out.println("Highest prime number in List : " + stats.getMax());
    System.out.println("Lowest prime number in List : " + stats.getMin());
    System.out.println("Sum of all prime numbers : " + stats.getSum());
    System.out.println("Average of all prime numbers : " + stats.getAverage());
    
    // 输出
    Highest prime number in List : 29
    Lowest prime number in List : 2
    Sum of all prime numbers : 129
    Average of all prime numbers : 12.9
    

    Stream对象的使用

    Stream对象提供多个非常有用的方法,这些方法可以分成两类:

    • 中间操作:将原始的Stream转换成另外一个Stream;如filter返回的是过滤后的Stream。
    • 终端操作:产生的是一个结果或者其它的复合操作;如count或者forEach操作。

    1.中间操作
    在这里插入图片描述
    2.终端操作
    在这里插入图片描述

    表达式注意事项

    1. lambda表达式仅能放入如下代码:预定义使用了 @Functional 注释的函数式接口,自带一个抽象函数的方法,或者SAM(Single Abstract Method 单个抽象方法)类型。这些称为 lambda 表达式的目标类型,可以用作返回类型,或lambda目标代码的参数。例如,若一个方法接收Runnable、Comparable或者 Callable 接口,都有单个抽象方法,可以传入 lambda 表达式。类似的,如果一个方法接受声明于 java.util.function 包内的接口,例如 Predicate、Function、Consumer 或 Supplier,那么可以向其传 lambda 表达式。

    2. lambda表达式内可以使用方法引用,仅当该方法不修改lambda表达式提供的参数。本例中的lambda表达式可以换为方法引用,因为这仅是一个参数相同的简单方法调用。例如:list.forEach(System.out::println);

      list.forEach(n -> System.out.println(n));
      // 使用方法引用
      list.forEach(System.out::println);
      

      然而,若对参数有任何修改,则不能使用方法引用,而需键入完整地lambda表达式,如下所示:

      list.forEach((String s) -> System.out.println("*" + s + "*"));
      

      事实上,可以省略这里的 lambda 参数的类型声明,编译器可以从列表的类属性推测出来。

    3. lambda内部可以使用静态、非静态和局部变量,这称为lambda内的变量捕获。

    4. Lambda表达式在Java中又称为闭包或匿名函数。

    5. Lambda方法在编译器内部被翻译成私有方法,并派发 invokedynamic 字节码指令来进行调用。可以使用JDK中的 javap 工具来反编译class文件。使用 javap -p 或 javap -c -v 命令来看一看。

    6. lambda表达式有个限制,那就是只能引用 final 或 final 局部变量,这就是说不能在lambda内部修改定义在域外的变量。

      List<Integer> primes = Arrays.asList(new Integer[]{2, 3,5,7});
      int factor = 2;
      primes.forEach(element -> { factor++; });
      
      Compile time error : "local variables referenced from a lambda expression must be final or effectively final"
      

      另外,只是访问它而不作修改是可以的,如下所示:

      List<Integer> primes = Arrays.asList(new Integer[]{2, 3,5,7});
      int factor = 2;
      primes.forEach(element -> { System.out.println(factor*element); });
      
      // 输出 4,6,10,14
      
    展开全文
  • 主要介绍了Python中defaultdict与lambda表达式用法,结合实例形式分析了Python中defaultdict与lambda表达式的功能、使用方法及相关注意事项,需要的朋友可以参考下
  • day041 lambda表达式(为什么引入lambda表达式lambda表达式的语法、函数式接口、方法引用) 1.为什么引入lambda表达式 lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。先观察一下我们在Java中的...
  • lambda表达式是一个可以传递的代码块,可以在以后一次或多次执行,类似于一个方法的实现。 语法 (String f, String s) -> f.length()-s.length(); 以上就是一个lambda表达式,类似与方法,圆括号中定义参数,...
  • 二、Lambda表达式 Lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。 Lambda表达式允许你通过表达式来代替功能接口(接口的实现放在Lambda表达式中,可以取代匿名类),Lambda表达式就和方法一样,它...
  • 1、Lambda表达式理解 Lambda表达式,也可称为闭包,它是推动Java 8发布的最重要新特性。 Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)。 使用Lambda表达式可以使代码变的更加简洁紧凑。 2、...
  • Lambda表达式支持将代码块作为方法参数,Lambda表达式允许使用更简洁的代码创建只有一个抽象方法的接口(即函数式接口)的实例。 2、当使用Lambda表达式代替匿名内部类创建对象时,Lambda表达式的代码块会代替实现...
  • Lambda表达式例子

    2016-09-24 21:03:24
    声明Lambda表达式example 1因为lambda表达式是有类型的,所以可以把它指定给一个auto变量或者函数对象,如下所示:// declaring_lambda_expressions1.cpp // compile with: /EHsc /W4 #include #include <iostream>...
  • 1、静态内部类 静态内部类,属于类中的类,好处是:如果不.../*Lambda表达式 * 避免匿名内部类定义过多 * 简化线程的使用 */ public class Lambda { // 静态内部类 static class Test implements Run...
  • 今天小编就为大家分享一篇Python的条件表达式和lambda表达式实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
  • 1.Lambda表达式 基本形式:(input parameters参数) =&gt; expression表达式 使用空括号指定零个输入参数:() =&gt; { }; 2.Lambda表达式树   (转)例子:...
  • Lambda表达式在python程序中是一种很常见的匿名方法的书写形式,它书写起来非常简单,但是牺牲了可读性。下面来看一下Lambda的简单介绍。语法lambda [parameter_list]:expressionLambda表达式的返回值是一个函数,...
  • 本文实例讲述了Python学习笔记之lambda表达式用法。分享给大家供大家参考,具体如下:Lambda 表达式使用 Lambda 表达式创建匿名函数,即没有名称的函数。lambda 表达式非常适合快速创建在代码中以后不会用到的函数。...
  • lambda表达式java 在本文中,我们提供了全面的Lambda Expressions Java教程。 1. Lambda Expressions Java教程–简介 Lambda表达式被认为是Java 8中引入的最好的功能之一。Lambda表达式被认为是Java进入函数式...
  • 简单来说,Lambda表达式是一个可传递的代码段。可以不借助对象传递的一个代码段。那java中为什么要加入这么一个特性呢? 二、为什么使用Lambda表达式 从一个简单例子开始。 比如现在有一个Person类 package POJO; ...
  • 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表达式不仅可以用他们创建委托实例,而且C#编译器也能将他们转换成表达式树——用于表示Lambda表达式逻辑的一种数据结构。简言之——Lambda表达式用符号语言习惯的方法来表示LINQ数据管线中...
  • Lambda表达式语法 (1)案例:案例代码比较重要 (2)案例的两点说明:非常重要 Lambda表达式语法 (1)案例:案例代码比较重要 MathOperation接口: package com.imooc.lambda; /** * 四则运算接口 ...
  • Lambda表达式简单入门

    2019-09-18 17:44:09
    1,为什么使用Lambda表达式 Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达...
  • Lambda表达式简单用法

    2021-03-23 16:36:40
    Lambda表达式是JDK1.8中引入的重要技术特征。它能是代码变得更加简洁和灵活。 在Java中Lambda表达式的基本语法: (参数,参数,...)->{方法体} 如果方法体只有一行,则可以省略“{}”直接写语句,但是这么写是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 83,680
精华内容 33,472
关键字:

lambda表达式的简单例子