zoukankan      html  css  js  c++  java
  • Java8Lambda表达式

    “Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。

    Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

    Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

    使用 Lambda 表达式可以使代码变的更加简洁紧凑。有了Lambda表达式,java将开启函数式编程的大门。

    函数式编程:

    函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。 [1] 
    和指令式编程相比,函数式编程强调函数的计算比指令的执行重要。
    和过程化编程相比,函数式编程里函数的计算可随时调用。

    lambda 表达式的语法格式如下:

    (parameters) -> expression
    或
    (parameters) ->{ statements; }

    以下是lambda表达式的重要特征:

    • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
    • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
    • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
    • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

    Lambda表达式创建一个线程:

    new Thread(
            () -> System.out.println("Thread run()")
    ).start();

    不用Lambda表达式创建一个线程:

    new Thread(new Runnable(){
        @Override
        public void run(){
            System.out.println("Thread run()");
        }
    }).start();

    Lambda表达式一个常见的用法是取代(某些)匿名内部类,但Lambda表达式的作用不限于此。

    Lambda 表达式实例

       public static void main(String args[]){
          Java8Tester tester = new Java8Tester();
            
          // 类型声明
          MathOperation addition = (int a, int b) -> a + b;
            
          // 不用类型声明
          MathOperation subtraction = (a, b) -> a - b;
            
          // 大括号中的返回语句
          MathOperation multiplication = (int a, int b) -> { return a * b; };
            
          // 没有大括号及返回语句
          MathOperation division = (int a, int b) -> a / b;
            
          System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
          System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
          System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
          System.out.println("10 / 5 = " + tester.operate(10, 5, division));
            
          // 不用括号
          GreetingService greetService1 = message ->
          System.out.println("Hello " + message);
            
          // 用括号
          GreetingService greetService2 = (message) ->
          System.out.println("Hello " + message);
            
          greetService1.sayMessage("Runoob");
          greetService2.sayMessage("Google");
       }
        
       interface MathOperation {
          int operation(int a, int b);
       }
        
       interface GreetingService {
          void sayMessage(String message);
       }
        
       private int operate(int a, int b, MathOperation mathOperation){
          return mathOperation.operation(a, b);
       }

    使用 Lambda 表达式需要注意以下两点:

    • Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。
    • Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。

    变量作用域

    lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

        final static String salutation = "Hello! ";
    
        public static void main(String args[]){
            GreetingService greetService1 = message ->
                    System.out.println(salutation + message);
            greetService1.sayMessage("Runoob");
        }
    
        interface GreetingService {
            void sayMessage(String message);
        }

    我们也可以直接在 lambda 表达式中访问外层的局部变量:

        public static void main(String args[]) {
            final int num = 1;
            Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
            s.convert(2);  // 输出结果为 3
        }
    
        public interface Converter<T1, T2> {
            void convert(int i);
        }

    lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)

    int num = 1;  
    Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
    s.convert(2);
    num = 5;  
    //报错信息:Local variable num defined in an enclosing scope must be final or effectively 
     final

    在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。

    String first = "";  
    Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length());  //编译会出错 

    Lambda表达式的原理

     Lambda表达式实际上是通过invokedynamic指令来实现的。下面是Lambda表达式几种可能的书写形式。

    Runnable run = () -> System.out.println("Hello World");// 1
    ActionListener listener = event -> System.out.println("button clicked");// 2
    Runnable multiLine = () -> {// 3
        System.out.println("Hello ");
        System.out.println("World");
    };
    BinaryOperator<Long> add = (Long x, Long y) -> x + y;// 4
    BinaryOperator<Long> addImplicit = (x, y) -> x + y;// 5
    • Lambda表达式是有类型的,赋值操作的左边就是类型。Lambda表达式的类型实际上是对应接口的类型
    • Lambda表达式可以包含多行代码,需要用大括号把代码块括起来,就像写函数体那样。
    • 大多数时候,Lambda表达式的参数表可以省略类型,就像代码2和5那样。这得益于javac的类型推导机制,编译器可以根据上下文推导出类型信息。

     表面上看起来每个Lambda表达式都是原来匿名内部类的简写形式,该内部类实现了某个函数接口(Functional Interface),但事实比这稍微复杂一些,这里不再展开。所谓函数接口是指内部只有一个接口函数的接口。Java是强类型语言,无论有没有显式指明,每个变量和对象都必须有明确的类型,没有显式指定的时候编译器会尝试确定类型。Lambda表达式的类型就是对应函数接口的类型

    Lambda表达式和Stream

    Lambda表达式的另一个重要用法,是和Stream一起使用。Stream is a sequence of elements supporting sequential and parallel aggregate operations。Stream就是一组元素的序列,支持对这些元素进行各种操作,而这些操作是通过Lambda表达式指定的。可以把Stream看作Java Collection的一种视图,就像迭代器是容器的一种视图那样(但Stream不会修改容器中的内容)。下面例子展示了Stream的常见用法。

    假设需要从一个字符串列表中选出以数字开头的字符串并输出,Java 7之前需要这样写:

    List<String> list = Arrays.asList("1one", "two", "three", "4four");
    for(String str : list){
        if(Character.isDigit(str.charAt(0))){
            System.out.println(str);
        }
    }

    而Java 8就可以这样写:

    List<String> list = Arrays.asList("1one", "two", "three", "4four");
    list.stream()// 1.得到容器的Steam
        .filter(str -> Character.isDigit(str.charAt(0)))// 2.选出以数字开头的字符串
        .forEach(str -> System.out.println(str));// 3.输出字符串

    上述代码首先1. 调用List.stream()方法得到容器的Stream,2. 然后调用filter()方法过滤出以数字开头的字符串,3. 最后调用forEach()方法输出结果。

    使用Stream有两个明显的好处:

    1. 减少了模板代码,只用Lambda表达式指明所需操作,代码语义更加明确、便于阅读。
    2. 将外部迭代改成了Stream的内部迭代,方便了JVM本身对迭代过程做优化(比如可以并行迭代)。

     假设需要从一个字符串列表中,选出所有不以数字开头的字符串,将其转换成大写形式,并把结果放到新的集合当中。Java 8书写的代码如下:

    List<String> list = Arrays.asList("1one", "two", "three", "4four");
    Set<String> newList =
            list.stream()// 1.得到容器的Stream
            .filter(str -> !Character.isDigit(str.charAt(0)))// 2.选出不以数字开头的字符串
            .map(String::toUpperCase)// 3.转换成大写形式
            .collect(Collectors.toSet());// 4.生成结果集

    上述代码首先1. 调用List.stream()方法得到容器的Stream,2. 然后调用filter()方法选出不以数字开头的字符串,3. 之后调用map()方法将字符串转换成大写形式,4. 最后调用collect()方法将结果转换成Set。这个例子还向我们展示了方法引用method references,代码中标号3处)以及收集器Collector,代码中标号4处)的用法,这里不再展开说明。

    通过这个例子我们看到了Stream链式操作,即多个操作可以连成一串。不用担心这会导致对容器的多次迭代,因为不是每个Stream的操作都会立即执行。Stream的操作分成两类,一类是中间操作(intermediate operations),另一类是结束操作(terminal operation),只有结束操作才会导致真正的代码执行,中间操作只会做一些标记,表示需要对Stream进行某种操作。这意味着可以在Stream上通过关联多种操作,但最终只需要一次迭代。如果你熟悉Spark RDD,对此应该并不陌生。

  • 相关阅读:
    Linux 中 /proc/meminfo 的含义
    Linux OOM-killer(内存不足时kill高内存进程的策略)
    java反射
    IDEA 创建Web项目
    centos7 源码安装php7
    linux 基本原则和常用命令
    ls file less
    centos7安装nginx1.10.1
    mysqlworkbench访问远程服务器
    redis 简单的注册
  • 原文地址:https://www.cnblogs.com/jiangwz/p/9197238.html
Copyright © 2011-2022 走看看