好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

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)

问题是 PrivilegedAction 和 PrivilegedExceptionAction 的每个接口都声明了相同的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主体中使用的任何 this 或 super 引用都被视为等同于其在封闭范围中的用法(因为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() 方法将抛出 IOException 的 lambda 分配给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>功能接口可以用来代替 CheckConnection 和 CheckAccount 。

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

查看更多关于Java中使用Lambda表达式和函数编程示例的详细内容...

  阅读:14次