Java中使用Lambda表达式和函数编程示例

时间:2021-09-16 14:02:48

1、简单介绍

第一个示例演示变量声明上下文中的lambda。它将lambda()->{System.out.println(“running”);}分配给可运行接口类型的变量r。

第二个示例类似,但演示了赋值上下文中的lambda(到先前声明的变量r)。

第三个示例演示了return语句上下文中的lambda。它使用指定的文件扩展名参数调用getFilter()方法以返回java.io.FileFilter对象。该对象被传递给java.io.File的listFiles()方法,该方法为每个文件调用过滤器,忽略与扩展名不匹配的文件。getFilter()方法返回通过lambda表示的FileFilter对象。编译器注意到lambda满足此函数接口的boolean accept(文件路径名)方法(两者都有一个参数,lambda主体返回一个布尔值),并将lambda绑定到FileFilter。

第四个示例演示了lambda在数组初始值设定项上下文中的用法。基于lambdas创建了两个java.nio.file.PathMatcher对象。每个PathMatcher对象根据其lambda主体指定的条件匹配文件。以下是相关代码:

?
1
2
3
4
5
final PathMatcher matchers[] =
{
  (path) -> path.toString().endsWith("txt"),
  (path) -> path.toString().endsWith("java")
};

PathMatcher函数接口提供一个boolean matches(Path path)方法,该方法与lambda的参数列表及其主体的布尔返回类型一致。随后调用此方法以确定在访问当前目录和子目录期间遇到的每个文件的匹配项(基于文件扩展名)。

第五个示例演示线程构造函数上下文中的lambda

第六个示例演示了lambda上下文中的lambda,这表明lambda可以嵌套。

第七个示例演示了三元条件表达式(?:)上下文中的lambda:根据升序或降序排序从两个lambda中选择一个。

第八个(也是最后一个)示例演示了强制转换表达式上下文中的lambda。()->System.getProperty(“user.name”)lambda被强制转换为PrivilegedAction<String>函数接口类型。此强制转换解决了java.security.AccessController类中的歧义,该类声明了以下方法:

?
1
2
static <T> T doPrivileged(PrivilegedAction<T> action)
static <T> T doPrivileged(PrivilegedExceptionAction<T> action)

问题是PrivilegedActionPrivilegedExceptionAction的每个接口都声明了相同的T run()方法。由于编译器无法确定哪个接口是目标类型,因此在没有强制转换的情况下会报告错误。

编译清单4并运行应用程序。您应该观察以下输出,该输出假定LambdaDemo.java是当前目录中唯一的.java文件,并且该目录不包含.txt文件:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
running
running
Found matched file: '.\LambdaDemo.java'.
running
called
Washington
Sydney
Rome
Ottawa
Moscow
London
Jerusalem
Berlin
jeffrey

2、Lambdas和Scopes

术语范围是指程序中名称与特定实体(例如变量)绑定的部分。在程序的另一部分中,名称可能绑定到另一个实体。lambda主体不会引入新的作用域。相反,它的作用域是封闭作用域。

3、Lambdas与局部变量

lambda主体可以定义局部变量。因为这些变量被认为是封闭范围的一部分,所以编译器在检测到lambda主体正在重新定义局部变量时将报告错误。清单5演示了这个问题。

清单5。LambdaDemo.java(版本5)

?
1
2
3
4
5
6
7
8
9
10
11
12
public class LambdaDemo
{
   public static void main(String[] args)
   {
      int limit = 10;
      Runnable r = () -> {
                           int limit = 5;
                           for (int i = 0; i < limit; i++)
                              System.out.println(i);
                         };
   }
}

因为limit已经存在于封闭范围(main()方法)中,lambda主体对limit的重新定义(int limit=5;)会导致编译器报告以下错误消息:错误:变量limit已经在方法main(字符串[])中定义。

4、Lambda体与局部变量

无论是源于lambda主体还是在封闭范围内,局部变量在使用之前都必须初始化。否则,编译器将报告错误。

在lambda主体外部定义并从主体引用的局部变量或参数必须标记为final或视为有效final(初始化后无法将该变量指定给)。试图修改一个有效的最终变量会导致编译器报告一个错误,如清单6所示。

清单6。LambdaDemo.java(版本6)

?
1
2
3
4
5
6
7
8
9
10
11
12
public class LambdaDemo
{
   public static void main(String[] args)
   {
      int limit = 10;
      Runnable r = () -> {
                           limit = 5;
                           for (int i = 0; i < limit; i++)
                              System.out.println(i);
                         };
   }
}

限制实际上是最终的。lambda主体试图修改此变量会导致编译器报告错误。这样做是因为final/final变量需要挂起,直到lambda执行为止,这可能要在定义变量的代码返回后很久才会发生。非最终/非有效最终变量不再存在。

5、Lambdas和'This'和'Super'关键字

lambda主体中使用的任何thissuper引用都被视为等同于其在封闭范围中的用法(因为lambda不引入新范围)。然而,匿名类的情况并非如此,如清单7所示。

清单7。LambdaDemo.java(版本7)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class LambdaDemo
{
   public static void main(String[] args)
   {
      new LambdaDemo().doWork();
   }
   public void doWork()
   {
      System.out.printf("this = %s%n", this);
      Runnable r = new Runnable()
                       {
                          @Override
                          public void run()
                          {
                             System.out.printf("this = %s%n", this);
                          }
                       };
      new Thread(r).start();
      new Thread(() -> System.out.printf("this = %s%n", this)).start();
   }
}

清单7的main()方法实例化LambdaDemo并调用对象的doWork()方法来输出对象的this引用,实例化一个实现Runnable的匿名类,创建一个线程对象,在其线程启动时执行此Runnable,并创建另一个线程对象,其线程在启动时执行lambda

编译清单7并运行应用程序。您应该观察与以下输出类似的情况:

?
1
2
3
this = LambdaDemo@776ec8df
this = LambdaDemo$1@48766bb
this = LambdaDemo@776ec8df

第一行显示LambdaDemo的this引用,第二行显示新可运行范围中不同的this引用,第三行显示lambda上下文中的this引用。第三行和第一行匹配,因为lambda的作用域嵌套在doWork()方法中;这在整个方法中具有相同的含义。

6、Lambdas和Exceptions

lambda主体不允许抛出比函数接口方法的throws子句中指定的更多的异常。如果lambda主体抛出异常,则函数接口方法的throws子句必须声明相同的异常类型或其超类型。考虑清单8。

清单8。LambdaDemo.java(版本8)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.awt.AWTException;
import java.io.IOException;
@FunctionalInterface
interface Work
{
   void doSomething() throws IOException;
}
public class LambdaDemo
{
   public static void main(String[] args) throws AWTException, IOException
   {
      Work work = () -> { throw new IOException(); };
      work.doSomething();
      work = () -> { throw new AWTException(""); };
   }
}

清单8声明了一个工作函数接口,其doSomething()方法声明为抛出java.io.IOException。main()方法将抛出IOExceptionlambda分配给work,这是正常的,因为IOException列在doSomething()的throws子句中。

main()接下来分配一个lambda,该lambda抛出java.awt.AWTException来工作。但是,编译器不允许此赋值,因为AWTException不是doSomething()的throws子句的一部分(当然也不是IOException的子类型)。

7、预定义的功能接口

您可能会发现自己反复创建类似的功能接口。例如,您可以使用布尔IsConnection(连接c)方法创建CheckConnection函数接口,使用布尔isPositiveBalance(帐户帐户)方法创建CheckAccount函数接口。这是浪费。

前面的示例公开了谓词(布尔值函数)的抽象概念。Oracle提供了常用功能接口的java.util.function包,以预测这些模式。例如,这个包的Predicate<T>功能接口可以用来代替CheckConnectionCheckAccount

Predicate<T>提供一个boolean test(T t)方法,该方法根据其argument (t)计算该谓词,当T与predicate匹配时返回true,否则返回false。请注意,test()提供了与isConnected()isPositiveBalance()相同的参数列表。另外,请注意,它们都具有相同的返回类型(布尔值)。

清单9中的应用程序源代码演示了谓词<T>。

清单9。LambdaDemo.java(版本9)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
class Account
{
   private int id, balance;
   Account(int id, int balance)
   {
      this.balance = balance;
      this.id = id;
   }
   int getBalance()
   {
      return balance;
   }
   int getID()
   {
      return id;
   }
   void print()
   {
      System.out.printf("Account: [%d], Balance: [%d]%n", id, balance);
   }
}
public class LambdaDemo
{
   static List<Account> accounts;
   public static void main(String[] args)
   {
      accounts = new ArrayList<>();
      accounts.add(new Account(1000, 200));
      accounts.add(new Account(2000, -500));
      accounts.add(new Account(3000, 0));
      accounts.add(new Account(4000, -80));
      accounts.add(new Account(5000, 1000));
      // Print all accounts
      printAccounts(account -> true);
      System.out.println();
      // Print all accounts with negative balances.
      printAccounts(account -> account.getBalance() < 0);
      System.out.println();
      // Print all accounts whose id is greater than 2000 and less than 5000.
      printAccounts(account -> account.getID() > 2000 &&
                               account.getID() < 5000);
   }
   static void printAccounts(Predicate<Account> tester)
   {
      for (Account account: accounts)
         if (tester.test(account))
            account.print();
   }
}

清单9创建了一个基于数组的帐户列表,其中有正余额、零余额和负余额。然后,它通过使用lambdas调用printAccounts()来演示谓词<T>,以便打印出所有帐户,仅打印出那些余额为负数的帐户,以及仅打印出ID大于2000且小于5000的帐户。

考虑lambda表达式帐户->真。编译器验证lambda是否匹配谓词<T>的布尔测试(T)方法,它会这样做——lambda提供单个参数(account),其主体始终返回布尔值(true)。对于这个lambda,test()被实现为执行return true;。

编译清单9并运行应用程序。我们能观察以下输出:

?
1
2
3
4
5
6
7
8
9
Account: [1000], Balance: [200]
Account: [2000], Balance: [-500]
Account: [3000], Balance: [0]
Account: [4000], Balance: [-80]
Account: [5000], Balance: [1000]
Account: [2000], Balance: [-500]
Account: [4000], Balance: [-80]
Account: [3000], Balance: [0]
Account: [4000], Balance: [-80]

Predicate<T>只是java.util.function的各种预定义函数接口之一。另一个示例是Consumer<T>,它表示接受单个参数但不返回结果的操作。与Predicate<T>不同,Consumer<T>预期通过副作用进行操作。换句话说,它以某种方式修改了它的论点。

使用者的void accept(T)方法对其argument(T)执行操作。当出现在此函数接口的上下文中时,lambda必须符合accept()方法的单独参数和返回类型。

到此这篇关于Java中使用Lambda表达式和函数编程示例的文章就介绍到这了,更多相关Java中使用Lambda表达式和函数编程内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://www.tuicool.com/articles/zqUzyuN