在“ Java SE 8新功能导览”系列的这篇文章中,我们将深入解释并探索代码,以了解如何使用lambda表达式和方法引用 遍历集合 ,并使用谓词接口过滤它们,实现默认方法在接口中,最后在接口中实现静态方法 。
在上一篇文章“ 使用Lambda Expression进行函数式编程 ”中; 我已深入了解lambda表达式。 我向您展示了Lambda表达式的几种不同用法。 它们都有功能接口的共同实现。 我还解释了编译器如何从代码中推断信息,例如特定类型的变量以及后台实际发生的情况。
源代码托管在我的Github帐户上:从此处克隆它。
表中的内容:
- 使用lambda表达式遍历集合。
- 使用谓词接口过滤集合。
- 使用方法引用遍历集合。
- 在接口中实现默认方法。
- 在接口中实现静态方法。
1,使用lambda表达式遍历集合
在Java SE 8中,可以使用lambda表达式遍历项目集合。 集合的示例包括列表,地图和集合。 所有这些数据类型都实现了一个称为iterable的接口。 为了理解我将要展示的代码,让我们从文档开始。 我正在eg.com.tm.java8.features.lambda2
包下的名为TraverseFileContent.java
的类中工作。 我将右键单击方法名称readAllLines
,然后单击 Show Javadoc。
它返回扩展了许多接口的List
类。 这是我感兴趣的一种叫做可迭代的。 此接口已在Java SE 5中添加。 它有一种称为iterater
的方法。 这将返回一个Iterater
接口的实例,您可以使用该实例然后遍历集合的内容。 但是在Java SE 8中,有两种新方法。 一台叫做forEach
,另一台叫做spliterator
。 我将专注于forEach方法。
它接受称为Consumer
的功能接口的实例。 使用者接口只有一个抽象方法,名为accept
。 通过实现此接口及其单一抽象方法,您可以添加对集合中的某个项目进行操作的代码。
因此,让我们回到代码。 在TraverseFileContent.java
类中,我遍历了这行集合,即表示文件内容的字符串数组列表,两次。 在第一个版本中,从第51行开始,我使用了forEach
循环,这是一段简单的代码,它为列表中的每个项目创建一个String变量行,然后执行所需的任何代码。
在第58行,我使用了另一种方法,即调用集合的迭代器方法。 获取迭代器对象,然后循环使用while代码块,调用迭代器具有next方法。 现在,这两段代码都可以很好地工作到Java 5,但我将向您展示使用lambda表达式和forEach方法的情况。 在Java 8中,我将从此forEach方法开始。
从65行开始,我将引用我的lines集合并调用新的forEach
方法。 再一次,它将接收Consumer接口的实例。 因为Consumer是一个功能接口,所以我可以使用lambda表达式。 使用者接口的accept方法需要一个具有适当数据类型的参数。 因为lines变量被声明为字符串列表,所以我必须传入一个字符串作为参数。
我用行名将其传递。 然后,我将添加我的lambda运算符, 箭头标记 。 我将在此处添加我的系统输出,并直接传递。
您可以通过删除旧的遍历来清理代码。 现在,这一行代码取代了forEach
循环的三行。 我将复制此代码并将其移至使用迭代器的版本。 然后,我将选择这些代码行并将其注释掉。 并粘贴我的lambda表达式版本。 因此,当您替换迭代器时,您将用一行替换四行代码。
您可以将Stream
与集合一起使用更高级,Streams支持一系列元素,这些元素支持顺序和并行聚合操作。 通常,它用于以下用途:
现在保存并运行代码,看看它的行为与以前完全相同,但是代码更少。 因此,这只是遍历集合的另一种选择。 您可以使用forEach
循环。 您可以使用迭代器对象,现在可以对lambda表达式使用forEach
方法。
2.使用谓词接口过滤集合
除了新的lambda语法外, Java SE 8还添加了许多新的功能接口 。 最有用的一种称为Predicate
接口。 该接口具有一个名为test
布尔方法,您可以使用该方法包装条件处理,并使条件代码更加整洁。 我将向您展示如何在该项目中使用谓词接口。
从包eg.com.tm.java8.features.lambda2.predicate
名为FileDirFilter
类eg.com.tm.java8.features.lambda2.predicate
,我声明了一个名为predicateInInnerClass
的方法。
我将使用NIO.2 DirectoryStream
类来获取特定的路径条目流,在我们的案例中该流声明为:
现在,我的目标是过滤此流,仅显示某些条目,即目录。 您可以将谓词接口与内部类语法或lambda语法一起使用。
我将从内部类语法开始。 在predicateInInnerClass
方法中,我将声明谓词接口的实例。 我将输入接口名称,然后按Ctrl + Space 。 然后从列表中选择接口。 它是Java.util.function
包的成员,在那里您Java.util.function
找到许多其他新的功能接口。 该接口需要通用类型声明。 我将其设置为Path
类。 我将谓词命名为dirsFilter
。
现在,对于内部类语法,我将从new关键字开始,然后按Ctrl + Space并为谓词接口选择构造函数。 选择该选项后, NetBeans会自动实现单个抽象方法test
。 因为我用通用的Path
类型声明了谓词,所以test方法也接受该类型的单个参数。 现在,我将实现该方法。 我将要传递的路径对象的名称更改为“ t”。
我将return语句设置为使用非常简单的条件。 我将添加一组括号来包装我的条件。 然后将条件设置为isDirectory(t, NOFOLLOW_LINKS)
。 因此,现在我的谓词对象封装了我的测试,并且可以使用测试方法来确定我是否要处理集合中的对象。
下一步是遍历集合。 您可以通过forEach
循环,迭代器对象或新的forEach方法以多种方式执行此操作。
我将使用经典的forEach
循环。 我将输入foreach并按Ctrl + Space ,然后选择foreach代码模板。 在for循环中,我将使用一条if
语句。 我将设置条件以使用我刚刚声明的dirsFilter对象。 我将呼叫dirsFilter.test
。 我将在foreach循环中传递要声明的文件对象。 然后,如果条件为true,则将使用系统输出,并将输出调用file
对象的getFileName
方法的结果。
我将保存并运行该代码。 而且我只看到目录类型路径的完整路径。 现在,如果我想更改条件,则可以更改谓词对象并重新运行代码,它将可以正常工作。 但是我们这里的目标是使代码尽可能简洁和可读。 为此,您可能决定使用lambda表达式来实现此谓词接口。 因此,让我们回到代码,然后折叠该方法predicateInInnerClass
,并展开其他方法predicateWithLambda
。
现在,我将进行非常类似的处理。 但这次我将使用lambda表达式声明我的谓词对象。 再一次,我将输入接口的名称。 我将按Ctrl + Space并从列表中选择它,然后设置其通用类型。 我将这个谓词对象noFilter
。 现在,我将使用lambda表达式实现该接口。 我将从要实现的方法的签名开始。 那是test
方法。 并且由于我声明的谓词具有通用的Path
类型,因此该参数将成为path的实例。
我将其命名为p
。 然后,我将添加arrow标记 ,然后使用一个简单的条件表达式来实现我的方法,将其设置为true
即可返回所有已定义的路径条目。 这就是我所需要的。 在内部类版本中,这一行代码替换了五到六行代码。 接下来,我将遍历该列表并使用谓词对象的test方法。 这次,我将使用doFilterAndPrintPath
方法传入谓词noFilter
。
我展示了如何在以前的lambda实现中使用此方法。 这是Java SE 8中添加的一个新方法。在doFilterAndPrintPath
方法的实现中,我使用newDirectoryStream
返回的路径集合的forEach
方法,我将从作为参数传递的对象的名称开始。 这次,我不会将其包装在括号内只是为了向您展示语法上的差异。 然后,我将添加箭头标记和一对大括号。 在大括号内,我将使用if
语句。 然后我将通过条件,这也是谓词的test
方法。 我将使用pred.test
,并传递path
对象。
如果条件为真,我将使用系统输出。 然后我将输出路径对象文件名。 我将保存并运行代码。 结果就是。 我再次显示所有条目。 但是,如果我想处理不止一种可能的情况怎么办? 好吧,这是关于lambda语法和谓词接口的伟大之处。
您可以根据需要创建任意多个谓词对象,每个谓词对象代表不同的条件。
因此,我将复制这一行代码,并将新代码的名称更改为hiddenFilter
。 并且我将其条件更改为仅显示隐藏的文件和目录。 为了使传递想要的谓词真的很容易,我将遍历集合的这段代码放入单独的方法中。 我们已经在doFilterAndPrintPath
方法中做到了。
现在,该方法不知道它将获得哪个谓词,因此我将方法中谓词对象的名称重构为pred
。 现在,我现有的代码将传递给hiddenFilter
,所以我将运行它。 而且我得到了所有隐藏的文件和目录 。 然后将传入的谓词对象更改为timeFilter
,然后再次运行代码。
这次我得到了今天修改的所有文件和目录。 因此,这就是您可以使用新的谓词接口和lambda表达式将条件封装在单个对象中的方式。 然后将这些对象传递到您自己的方法中进行处理。
我不会详细介绍Java SE 8中的许多其他新功能接口,但是我鼓励您研究一下谓词接口是java.util.function
的一部分的程序包。 您会在那里找到许多新的有用界面。
而且由于它们都是功能性接口,因此都可以使用lambda表达式实现。
3,使用方法引用遍历集合
除了Lambda表达式外,Java SE 8的Project Lambda还向该语言添加了方法引用。 方法参考提供了一种命名您要调用的方法的方法,而不是直接调用它。 就像Lambda表达式一样,目标是使您的代码更简洁,更易读。
我将在名为MethodReference
此类中的类( MethodReference
下进行eg.com.tm.java8.features.lambda2.mthdRefs
。
您可以对四种方法使用方法引用:
- 任何类的静态方法。
- 特定对象的实例方法。
- 任意对象的实例方法,在这种情况下,您将像静态方法一样引用它。
- 并引用构造函数方法。
我将从静态方法开始。 在这段代码中,我有一个FilesDirTests
类,我的目标是检查是否可以访问特定文件。 我将创建一个对路径类进行一些比较的方法。 现在您可以将此方法放置在任何您喜欢的位置,并且开发人员在最佳放置位置上会有所不同,但是我将创建该方法作为我的FilesDirTests
类的静态方法。
我将打开类,并放置此新方法。 我将其声明为public
和static
,并将其返回数据类型设置为boolean
。 我将方法命名为isAccessible
。 并且该方法将接受对路径类的引用。 我将其命名为p。 该方法将知道目标是将路径与java.nio.file.Files
类中定义的某些可访问性方法进行比较。 就像Predicate
接口的test
方法一样,如果该路径可访问,则返回true,否则返回false表示该路径不可访问。
我将保存该更改,现在我将进入类MethodReference
。 为了测试给定路径文件的可访问性,我再次使用doFilterAndPrintPath(Predicate<Path> pred)
方法。 我将在doPrint
方法中调用它。 定义的路径在doFilterAndPrintPath
方法内部使用。 对于谓词对象,我将使用方法reference 。 它看起来像这样,我指的是静态方法,因此我将从包含静态方法的类的类型开始。
然后,我将输入双冒号运算符,这就是您将类型或对象与要调用的方法的名称分开的方式。 然后,我将传入方法的名称isAccessible
。 现在,这就是为什么这样做的原因。 此方法doFilterAndPrintPath
方法期望谓词接口的实例。 该接口只有一个抽象方法,该方法需要一个值。 我正在调用一个期望一个值的方法。 并返回测试方法可以使用的数据类型。
doFilterAndPrintPath
将遍历路径文件,并根据测试输出值。 保存更改,然后运行代码,即可得到结果。 Opppps没有结果? 没有打印文件? 这是因为isAccessible
具有测试条件,该条件会使测试失败,这是isExecutable
方法。
这是一个静态方法参考。 如果愿意,可以将方法引用与实例方法一起使用 。 为此,我将在MethodReference
类的结尾处添加以下两个方法:
在此类的main方法中,我将创建当前类的实例,然后将其称为doFilterAndPrintPath
方法。 在我到这里时,所有数据和方法都是实例成员,而不是静态成员。
因为main是静态方法,所以我们不能使用this
关键字,作为替代语法,您可以在对象实例方法中将此关键字用作引用。
和以前一样,我将保存并运行,然后得到结果。 因此,方法引用是使代码简明扼要的一种简单方法。
4,在接口中实现默认方法
在Java SE 8之前,接口可以包含抽象方法和常量声明,但是您不能提供可继承的完全实现的方法。
我正在使用一个名为eg.com.tm.java8.features.lambda2.defltMthd
的软件包。 在此应用程序中,我有一个名为VehicleInterface.Java
的接口。 它有八个抽象方法,在接口中所有抽象方法都即将公开,因此我没有包含public
关键字,它们是基本的getter和setter 。
然后,我有一个名为Car.java
的类,该类具有实现的setter和getter。 还有一个构造函数方法,可以很容易地实例化该类。
然后,我有一个名为DefaultMethod.java
的主类。 在这段代码中,我使用谓词对象过滤汽车列表,然后显示汽车。 我将一个名为info的字符串放在一起,并将其输出到控制台。 因此,我将使用Java SE 8的新功能来重构此代码,该功能使我可以向接口添加称为默认方法的内容。
将默认方法添加到接口时,可以添加其完整实现。 然后,实现该接口的任何类都将继承该方法,并且您可以调用该方法本身,或者该方法将可在应用程序的任何其他位置调用,因为与抽象方法一样,该方法将是公共的。
返回VehicleInterface.Java
我将光标移到abstract
方法下面。 然后,我将使用new关键字default
开始方法签名。 该方法的其余部分看起来完全一样,就像我在一个类中实现它一样。
我将从返回类型开始,然后是方法的名称。 接下来,我将添加代码,它将是一个return语句,可以将name , model ,car CC和make year的值连接起来。 现在, 因为这是一个接口,所以我不能引用私有字段。 你不能那样做 。
因此,我将只引用抽象方法,我知道它将由类本身实现。 我将调用getName
, getModel
, getCC
。 然后,我将getMakeYear
和getMakeYear
圆括号连接getMakeYear
。
我将保存该更改,现在该方法可用于实现该接口的每个类。 我不会对Car类进行任何更改。 它已经有该方法。 然后,我将在这里转到主类,使用默认方法,然后更改此代码。 我不再需要创建名为Info的字符串,这将由Car类继承的新方法完成。 因此,我将注释掉那行代码。 然后,我将使用对getInfo
方法的调用来替换对info变量的引用。
但是我将其称为我现在正在使用的汽车对象的成员。 我将保存更改并运行代码。 结果就来了。 我成功地调用了getInfo
方法来获取字符串,名称,模型,CC和制造年份的值的串联,然后使用主类中的代码将其输出到控制台。
通过使用默认方法 ,有时可以消除整个继承层。 例如,某些开发人员在Java的早期版本中可能创建了一个接口,然后创建了实现该接口的基类,然后创建了他们将在其代码中实际使用的子类。
使用此新功能,您可能根本不需要基类,而是可以直接实现子类,直接从接口继承默认方法。
5,在接口中实现静态方法
前面我已经描述了如何向接口添加默认方法,这些方法已完全实现并由实现类继承。 在Java SE 8中,还可以向接口添加完全实现的静态方法。 与默认方法一样,目标是让您消除继承层并简化应用程序。
我正在使用一个名为eg.com.tm.java8.features.lambda2.staticMthd
的包。 就像在较早的项目中一样,用于默认方法的那个主类在这里称为StaticMethod
,它具有从汽车对象获取名称 , model , CC以及年份的代码。
在本课程的第47行。 我的目标是采用此代码并将其移至静态方法,但不是将其添加到基类或其他具体类中,而是将其添加到接口中。 我在早期版本的Java中无法执行的操作。 我将使用此代码并将其复制到剪贴板。 然后,我将打开名为VehicleInterface.java
的接口。 和以前一样,我从一组抽象方法声明开始。 现在,将光标放在这些抽象方法声明之后,并从关键字static
开始。
与默认方法和抽象方法一样,这将自动成为公共方法。 我不需要声明它。 它将返回一个字符串,并将其命名为getVehicleInfo
。 现在,由于这是一个static
方法,因此无法引用上面声明的实例方法。 因此,我将传递Car对象的实例。 然后,我将提供return关键字,并粘贴之前的代码,然后进行清理,以便现在返回name
, model
, CC
以及make year
和右括号。
现在,可以在应用程序中的任何位置使用此static
方法。 和以前一样,我无需对Model类做任何事情。 之所以称为car
,是因为该接口中已经提供了要获取字符串并输出的所有代码。
我将回到我的主类,使用静态方法,现在,我将使用接口名称VehicleInterface.java
调用该接口,而不是在此处将这个字符串放在一起,然后将其称为新的静态方法, getVehicleInfo
并传入Car对象c
。
我将保存更改并运行代码。 结果就来了。 从功能上讲,它与使用默认方法,将此代码放入Car
类或在主类的顶级将其完全相同。
默认方法和静态方法的目的只是为您提供更多选择,以为您的应用程序组合继承模型。 使用默认方法和静态方法, 您可以消除应用程序的整个继承层,并极大地简化编码模型,从而使应用程序更易于编码和维护 。
资源资源
- Java教程,Lambda表达式
- JSR 310:日期和时间API
- JSR 337:Java SE 8发行内容
- OpenJDK网站
- Java平台,标准版8,API规范
翻译自: https://www.javacodegeeks.com/2014/12/java-se-8-new-features-tour-traversing-filtering-processing-collection-methods-enhancements-with-lambda.html