Java8新特性(lambda表达式)

这些是自己在看Java书籍时从书上摘抄的,主要是为了加强自己对知识点的记忆,同时也是对这个知识点不太懂所以记录一下,就是自己的学习笔记。

1.1 lambda表达式简介

理解lambda表达式的Java实现,有两个结构十分关键:第一个是lambda表达式自身,第二个是函数式接口。
lambda表达式本质上就是一个匿名(即未命名)方法。但是,这个方法不是独立执行的,而是用于实现由函数式接口定义的另一个方法。因此,lambda表达式会产生一个匿名类。lambda表达式也常被称为闭包。
函数式接口是仅包含一个抽象方法的接口。这个方法指明了接口的目标用途。因此,函数式接口通常表示单个动作。例如:标准接口Runnable是一个函数式接口,因为它只定义了一个方法run(),run()定义了Runnable的动作。此外,接口式函数定义了lambda表达式的目标类型。特别注意:lambda表达式只能用于其目标类型已被指定的上下文中。

1.1.1 lambda表达式的基础知识

lambda表达式在Java语言中引入了一个新的语法元素和操作符。这个操作符是-> ,操作符被称为lambda操作符或者是箭头操作符。它将lambda表达式分成两个部分,左侧指定了lambda表达式需要的所有参数(如果不需要参数,则使用空的参数列表“()”),右侧指定了lambda体,即lambda表达式要执行的动作。
Java定义了两种lambda体:一种包含单独一个表达式,另一种包含一个代码块
看一个简单的lambda表达式的例子,它的计算结果是一个常量值,如下所示:

() -> 123.45

这个lambda表达式没有参数,所以参数列表为空,它返回常量值123.45。因此,这个表达式的作用类似于下面的方法;

    double myMath(){
        return 123.45;
    }

lambda表达式定义的方法没有名称
下面在看一个lambda表达式:

() -> Math.random() * 100

这个lambda表达式使用Math.random()获得一个随机数,将其乘以100,然后返回结果。这个lambda表达式也不需要参数。
当lambda表达式需要参数时,需要在操作符左侧的参数列表中加以指定,下面是一个简单的例子:

(n) -> (n % 2) == 0

如果参数n的值是偶数,这个lambda表达式会返回true。尽管可以显式指定参数的类型,例如本例中的n,但是通常不需要这么做,因为很多时候,参数的类型是可以从上下文中推断出来。与命名方法一样,lambda表达式可以指定需要用到的任意数量的参数。

1.1.2 函数式接口

函数式接口是仅指定了一个抽象方法的接口。从JDK8开始,可以为接口声明的方法指定默认行为(默认的方法实现),即所谓的默认方法,只有当没有指定默认实现时,接口的方法才是抽象方法。因为没有指定默认实现的接口方法隐式的是抽象方法,所以没有必要使用abstract修饰符。
下面是函数式接口的一个例子:

public interface MyNumber {
    double getValue();
}

在本例中,getValue()方法隐式的是抽象方法,并且是MyNumber定义的唯一方法,所以MyNumber是一个函数式接口,其功能由getValue()定义。
lambda表达式不是独立执行的,而是构成了一个函数式接口定义的抽象方法的实现,该函数式接口定义了它的目标类型,只有在定义了lambda表达式的目标类型的上下文中,才能使用该表达式。当把一个lambda表达式赋给一个函数式接口引用时,就创建了这样的上下文。
下面通过一个例子来说明如何在参数上下文中使用lambda表达式。首先,声明对函数式接口MyNumber的一个引用

MyNumber myNum;

接下来,将一个lambda表达式赋给该接口引用:

myNum = () -> 123.45;

当目标类型上下文中出现lambda表达式时,会自动创建实现了函数式接口的一个类的实例,函数式接口声明的抽象方法的行为由lambda表达式定义。当通过目标调用该方法时,就会执行lambda表达式。因此,lambda表达式提供了一种将代码片段转换为对象的方法
在前面的例子中,lambda表达式成了getValue()方法的实现,所以,下面的代码将显示123.45:

System.out.println(myNum.getValue());
//结果如下:
//123.45

因为赋给myNum的lambda表达式返回值为123.45,所以调用getValue()方法返回的值也是123.45
为了在目标类型上下文中使用lambda表达式,抽象方法的类型和lambda表达式的类型必须兼容。例如:如果抽象方法指定了两个int类型的参数,那么lambda表达式也必须指定两个参数,其类型要么被显式的指定为int类型,要么在上下文中可以隐式的推断为int类型。总的来说,lambda表达式的参数类型和数量必须与方法的参数兼容;返回类型必须兼容;并且lambda表达式可能抛出的异常必须能被方法接受

package lambda;

interface NumericTest{
    boolean test(int n);
}

public class LambdaDemo2 {
    public static void main(String[] args) {
        NumericTest isEven = (n) -> (n % 2) == 0;
        if (isEven.test(10)){
            System.out.println("10 是偶数");
        }
        if (!isEven.test(9)){
            System.out.println("9不是偶数");
        }
        NumericTest isNonNeg = (n) -> n >= 0;
        if (isNonNeg.test(1)){
            System.out.println("1是正数");
        }
        if (!isNonNeg.test(-1)){
            System.out.println("-1不是正数");
        }
    }
}
//结果:
//10 是偶数
//9不是偶数
//1是正数
//-1不是正数

这个程序演示了关于lambda表达式的一个重要的地方:函数式接口引用可以用来执行任何与其兼容的lambda表达式。注意,程序中定义了两个不同的lambda表达式,它们都是与函数式接口NumericTest的test()方法兼容,所以都可以通过NumericTest引用执行。
当lambda表达式中有多个参数时,如下所示:

(n,d) -> (n % d) == 0;

两个参数n和d才参数列表中指定,并且用逗号隔开。每当需要一个以上的参数时,就在lambda表达式操作符左侧,使用一个带括号的参数列表指定参数,参数之间使用逗号隔开。
对于lambda表达式中的多个参数,有一点十分重要:如果需要显式声明一个参数类型,那么必须为所有的参数声明类型。例如下面的代码是合法的:

(int n,int d) -> (n % d) == 0;

这是不合法的:

(int n, d) -> (n % d) == 0;

1.2 块lambda表达式

块lambda表达式是操作符的右侧的代码不是一个简单的表达式,而是由一个代码块组成,其中包含多条语句。这种类型的lambda体被称为块体,具有块体的lambda表达式被称为块lambda。
块lambda扩展了lambda表达式内部可以处理的操作类型,因为它允许lambda体包含多条语句。例如,在块lambda中可以声明变量、使用循环、指定if和switch语句、创建嵌套代码块等。创建块lambda只需要使用花括号包围lambda体,就像创建其他语句块一样。
在块lambda中重要一点是:在块lambda中必须显式使用return语句来返回值。因为块lambda体代表的不是单独一个表达式。
下面这个例子使用块lambda来计算并返回一个int类型值的阶乘:

package lambda;

interface NumuricFunc{
    int func(int n);
}

public class BlockLambdaDemo {
    public static void main(String[] args) {
        NumuricFunc factorial = (n) -> {
          int result = 1;
          for (int i = 1;i <= n;i++){
              result *= i;
          }
          return result;
        };
        System.out.println(factorial.func(3));
        System.out.println(factorial.func(6));
    }
}
//结果如下:
//6
//720

在程序中,块lambda声明了变量result,使用了for循环,有一条return语句。在lambda表达式中出现return语句时,只是从lambda体返回,而不会导致包围lambda提的方法返回

1.3 泛型函数式接口

lambda表达式自身不能指定类型参数。所以lambda表达式不能是泛型的(由于存在类型推断,所有lambda表达式都展现出类似于泛型的特征)。与lambda表达式关联的函数式接口可以是泛型的。此时,lambda表达式的目标类型部分由声明函数式接口引用时指定的参数类型决定。如下例子所示:
定义接口

interface SomeFunc<T>{
    T func(T t);
}

声明接口,并将lambda表达式赋给接口变量

        SomeFunc<String> reverse = (str) -> {
          String result = null;
          int i;
          for (i = str.length() - 1;i >= 0;i--){
              result += str.charAt(i);
          }
          return result;
        };
        SomeFunc<Integer> factorial = (n) -> {
          int result = 1;
          for (int i = 1;i <= n;i++){
              result *= i;
          }
          return result;
        };

SomeFunc接口用于提供对两种不同类型的lambda表达式的引用。第一种表达式使用String类型,第二种使用Integer类型。因此,同一个泛型函数式接口可以用于不同类型的lambda表达式,参数的类型由声明接口的类型推断得出。

1.4 作为参数传递lambda表达式

lambda表达式可以用在任何提供了目标类型的上下文中。一种情况就是作为参数传递lambda表达式。这是lambda表达式的一种常见用途。另外这也是lambda表达式的一种强大用途,因为可以将可执行代码作为参数传递给方法,这极大地增强了Java的表达力。下面演示这个过程:
定义接口

interface StringFunc{
    String func(String n);
}

定义一个方法

static String stringOp(StringFunc sf,String s){
        return sf.func(s);
}

调用方法时使用lambda表达式代替StringFunc sf

        String inStr = "lambda add power to java";
        String outStr;
        //方式一
        outStr = stringOp((str) -> str.toUpperCase(),inStr);
        //方式二
        outStr = stringOp((str) -> {
            String result = null;
            for (int i = 0;i < str.length() - 1;i++){
                result += str.charAt(i);
            }
            return result;
        },inStr);
        //方式三
        StringFunc reverse = (str) -> {
            String result = null;
            for (int i = str.length() - 1;i >= 0;i--){
                result += str.charAt(i);
            }
          return result;
        };
        stringOp(reverse,inStr);

注意到stringOp()方法有两个参数,第一个参数类型是StringFunc,而StringFunc是一个函数式接口,所以这个参数可以接受对任何StringFunc实例的引用,包括lambda表达式创建的实例,第二个参数是需要操作的字符串。
上例中使用三种方式向stringOp传递参数。可以传递简单的表达式,也可以传递一段代码块,甚至可以先创建接口,将lambda表达式赋值给接口引用,直接将接口引用传递给方法。
传递一个简单的表达式lambda作为参数,这会创建函数式接口的一个实例,并把对该实例的引用传递给方法参数,这就把嵌入在一个类实例中的lambda表达式代码传递给了方法。目标类型上下文由参数的类型决定。

1.5 lambda表达式与异常

lambda表达式可以抛出异常。但是,如果抛出经检查的异常,该异常就必须与函数式接口的抽象方法的throws子句中列出的异常兼容。看如下例子:
定义函数式接口,定义自己的异常

interface DoubleNumericArrayFunc{
    double func(double[] n) throws EmptyArrayException;
}

class EmptyArrayException extends Exception{
    EmptyArrayException(){
        super("Array Empty");
    }
}

将lambda表达式赋值给接口引用

        DoubleNumericArrayFunc average = (n) -> {
          double sum = 0;
          if (n.length == 0){
              throw new EmptyArrayException();
          }
          for (int i = 0;i < n.length;i++){
              sum += n[i];
          }
          return sum;
        };

在func()方法中包含throws子句是必要的,如果不这么做,那么由于lambda表达式不在与func()方法兼容,程序将无法通过编译

1.6 lambda表达式和变量捕获

在lambda表达式中可以访问其外层作用域内定义的变量。

1.7 方法引用

有一个重要的特征与lambda表达式相关,叫做方法引用。方法引用提供了一种引用而不执行方法的方式。这种特性与lambda表达式相关,因为它也需要由兼容的函数式接口构成的目标上下文。计算时,方法引用也会创建函数式接口的一个实例。
被引用的方法实际上是与函数式接口中的方法兼容的,也就是参数列表相同,返回类型相同
1.静态方法的方法引用
ClassName::methodName
2.实例方法的方法引用
objRef::methodName
3.泛型中的方法引用
(1)泛型类
ClassName<类型参数>::methodName
(2)泛型方法
ClassName::<类型参数>methodName

1.8 构造函数引用

与创建方法引用相似,可以创建构造函数的引用(包括对象,数组的构造函数)
classname::new
当是泛型类时
classname<类型参数>::new

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java 8引入了lambda达式作为一种的编程语言特性lambda达式是一种匿名函数,它可以作为参数传递给方法或存储在变量中。它可以简化代码,使代码更加易读和易维护。lambda达式的语法非常简洁,可以用来替代匿名内部类。它可以在集合框架中使用,使代码更加简洁和易读。lambda达式Java 8中最重要的特性之一,它使Java编程更加现代化和高效。 ### 回答2: Lambda 达式Java 8 中最重要的特性之一,它可以让我们以更简洁的方式来编写代码,并且能够更优雅的解决许多问题。 Lambda 达式本身是一个匿名函数,它可以被当做对象进行传递和处理。它强调的是函数式编程思想,将函数作为一等公民,并支持灵活的函数组合。Lambda 达式通常由左侧的参数列、箭头符号和右侧的函数体组成。例如: ``` (x, y) -> x + y ``` 上述代码定义了一个 lambda 达式,这个达式接受两个参数 x 和 y,然后返回它们的和。 Lambda 达式的用途非常广泛,它们通常用于简化代码,比如用于普通的遍历集合和数组: ``` List<String> names = Arrays.asList("alice", "bob", "charlie"); names.forEach(name -> System.out.println(name)); ``` Lambda 达式还可以用于函数式接口,这是一种只包含一个抽象方法的接口。函数式接口可以被当做 lambda 达式的类型,这意味着我们可以使用 lambda 达式来创建这种类型的对象。例如: ``` interface Calculator { int calculate(int a, int b); } Calculator add = (a, b) -> a + b; Calculator sub = (a, b) -> a - b; ``` 上述代码定义了一个接口类型 Calculator,它包含一个抽象方法 calculate,并且使用 lambda 达式来定义 add 和 sub 两个对象,它们分别代加法和减法。 Lambda 达式还支持方法引用,这是一种更简洁的语法形式,它允许我们使用方法名来代替 lambda 达式的函数体。例如: ``` List<String> names = Arrays.asList("alice", "bob", "charlie"); names.forEach(System.out::println); ``` 上述代码使用了方法引用,它代替了之前的 lambda 达式。 总之,Java 8 的 Lambda 达式是一项非常有用的功能,它提供了一种更简单的方式来编写代码,让我们的代码更加简约且易于阅读。同时,它也是一种函数式编程思想的体现,让 Java 开发者能够更好的应用这些思想来解决实际问题。 ### 回答3: Java8的lambda达式是一个Java编程语言的特性,它可以方便地为函数式接口创建实例。Lambda达式允许你直接以更加简单和精简的方式来定义内部类,并且能够省略掉那些没有用的代码。 在Java 8中,Lambda达式通过一个箭头“->”来定义。在箭头左边是参数列,这些参数可以是任何类型,但是Lambda达式中只能有一个方法参数。在箭头右边是Lambda达式的主体,也就是这个Lambda达式所要执行的代码块。 Lambda达式还可以访问外部作用域中的变量,这是通过捕获变量的方式来实现的。Lambda达式在使用外部变量时会将其捕获到Lambda达式内部,从而形成一个闭包,这使得Lambda达式能够访问外部的变量。 Lambda达式的使用能够使得代码更加简洁,能够将代码逻辑更加清晰地达。在Java8中,Lambda达式被广泛应用于集合的处理,比如通过对集合进行排序、过滤、映射等操作,能够更加简单地处理数据集合。Lambda达式还被应用于多线程的编程中,能够使得并发编程更加方便和简单。 总之,Java 8的Lambda达式是一个很有用的特性。它能够使得Java代码更加简洁和易于理解,减少了冗余的结构和语法。同时,它也提供了更加灵活的编程方式,使得Java编程能够更加高效和便利。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值