学习不同 Java.net 语言中类似的函数结构

前言

函数式编程语言包含多个系列的常见函数。但开发人员有时很难在语言之间进行切换,因为熟悉的函数具有不熟悉的名称。函数式语言倾向于基于函数范例来命名这些常见函数。从脚本背景衍生而来的语言倾向于使用更具描述性的名称(有时是多个名称,包含多个指向同一个函数的别名)。

在本期文章中,我将继续探讨 3 种重要函数(过滤、映射和缩减)的实用性,展示来自每种 Java 下一代语言的实现细节。文中的讨论和示例旨在减轻 3 种语言对类似函数结构使用的不一致名称时可能引起的混淆。

过滤

在过滤 函数中,您可指定一个布尔值条件(通常为一个高阶函数的形式),将它应用到一个集合。该函数返回集合的子集,其中的元素与该条件匹配。过滤与查找 函数紧密相关,后者返回集合中第一个匹配的元素。

Scala

Scala 拥有多个过滤函数变体。最简单的情形基于传递的条件来过滤某个列表。在第一个示例中,我创建一个数字列表。然后使用了 filter() 函数,并传递了一个代码块,指定了所有元素都可以被 3 整除的条件:

val numbers = List.range(1, 11)
numbers filter (x => x % 3 == 0)
// List(3, 6, 9)

我可依靠隐式的参数来创建该代码快的更加简洁的版本:

numbers filter (_ % 3 == 0)
// List(3, 6, 9)

第二个版本不那么冗长,因为在 Scala 中,您可以将参数替换为下划线。两个版本都可以得到相同的结果。

过滤操作的许多示例都使用了数字,但 filter() 适用于任何集合。此示例将 filter() 应用到一个单词列表来确定 3 字母单词:

val words = List("the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog")
words filter (_.length == 3)
// List(the, fox, the, dog)

Scala 中的另一个过滤函数变体是 partition() 函数,它将一个集合拆分为多个部分。这种拆分基于您传递的高阶函数来确定分离条件。在这里,partition() 函数将返回两个列表,它们依据哪些列表成员可被 3 整除来进行拆分:

numbers partition (_ % 3 == 0)
// (List(3, 6, 9),List(1, 2, 4, 5, 7, 8, 10))

filter() 函数返回一个匹配元素集合,而 find() 仅返回第一个匹配元素:

numbers find (_ % 3 == 0)
// Some(3)

但是,find() 的返回值不是匹配的值本身,而是一个包装在 Option 类中的值。Option 有两个可能的值:Some 或 None。像其他一些函数式语言一样,Scala 使用 Option 作为一种约定来避免在缺少某个值时返回 null。Some() 实例包装实际的返回值,在 numbers find (_ % 3 == 0) 的情况下,该值为 3。如果我尝试查找某个不存在的值,那么返回值将为 None:

numbers find (_ < 0)
// None

Scala 还包含多个函数,它们基于一个判定函数来处理一个集合并返回值或丢弃它们。takeWhile() 函数返回集合中满足判定函数的最大的值集:

List(1, 2, 3, -4, 5, 6, 7, 8, 9, 10) takeWhile (_ > 0)
// List(1, 2, 3)

dropWhile() 函数跳过满足判定条件的最大元素数量:

words dropWhile (_ startsWith "t")
// List(quick, brown, fox, jumped, over, the, lazy, dog)

Groovy

Groovy 不是一个函数式语言,但它包含许多函数范例,一些范例的名称源自脚本语言。例如,在函数式语言中,该函数在传统上被称为 filter() 的函数,就是 Groovy 中的 findAll() 方法:

(1..10).findAll {it % 3 == 0}
// [3, 6, 9]

像 Scala 的过滤函数一样,Groovy 可处理所有类型,包括字符串:

def words = ["the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog"]
words.findAll {it.length() == 3}
// [The, fox, the, dog]

Groovy 还有一个类似 partition() 的函数,称为 split():

(1..10).split {it % 3}
// [[1, 2, 4, 5, 7, 8, 10], [3, 6, 9]]

split() 方法的返回值是一个嵌套数组,就像 Scala 中从 partition() 返回的嵌套列表。

Groovy 的 find() 方法返回集合中第一个匹配的元素:

(1..10).find {it % 3 == 0}
// 3

不同于 Scala,Groovy 遵循 Java 约定,在 find() 未能找到元素时返回 null:

(1..10).find {it < 0}
// null

Groovy 还拥有 takeWhile() 和 dropWhile() 方法,它们具有与 Scala 的版本类似的语义:

[1, 2, 3, -4, 5, 6, 7, 8, 9, 10].takeWhile {it > 0}
// [1, 2, 3]
words.dropWhile {it.startsWith("t")}
// [quick, brown, fox, jumped, over, the, lazy, dog]

与 Scala 示例中一样,dropWhile 被用作一个专门的过滤器:它丢弃与判定条件匹配的最大前缀,仅过滤列表的第一部分:

def moreWords = ["the", "two", "ton"] + words
moreWords.dropWhile {it.startsWith("t")}
// [quick, brown, fox, jumped, over, the, lazy, dog]

Clojure

Clojure 拥有令人震惊的集合操作例程数量。由于 Clojure 的动态类型,其中许多例程都是通用的。许多开发人员倾向于使用 Clojure,因为它的集合库非常丰富和灵活。Clojure 使用传统的函数式编程名称,如 (filter ) 函数所示:

(def numbers (range 1 11))
(filter (fn [x] (= 0 (rem x 3))) numbers)
; (3 6 9)

像其他语言一样,Clojure 为简单的匿名函数提供了简洁的语法:

(filter #(zero? (rem % 3)) numbers)
; (3 6 9)

而且与其他语言中一样,Clojure 的函数适用于任何适用的类型,比如字符串:

(def words ["the" "quick" "brown" "fox" "jumped" "over" "the" "lazy" "dog"])
(filter #(= 3 (count %)) words)
; (the fox the dog)

Clojure 的 (filter ) 返回类型为 Seq,它通过圆括号来描述。Seq 是 Clojure 中的顺序集合的核心抽象。

映射

所有 Java 下一代语言中常见的第二个主要的函数变形是映射。映射函数接受一个高阶函数和一个集合,然后向每个元素应用传递的函数并返回一个集合。返回的集合(不同于过滤)的大小与原始集合相同,但更新了值。

Scala

Scala 的 map() 函数接受一个代码块并返回转换的集合:

List(1, 2, 3, 4, 5) map (_ + 1)
// List(2, 3, 4, 5, 6)

map() 函数适用于所有适用的类型,但它不一定返回集合元素的已转换集合。在此示例中,我在一个字符串中返回所有元素的大小列表:

words map (_.length)
// List(3, 5, 5, 3, 6, 4, 3, 4, 3)

在函数式编程语言中常常会产生嵌套列表,以至于嵌套列表对解除嵌套(通常称为扁平化)的库支持很常见。以下是扁平化一个嵌套列表的示例:

List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) flatMap (_.toList)
// List(1, 2, 3, 4, 5, 6, 7, 8, 9)

获得的 List 中仅包含元素,删除了额外的基础架构。flatMap 函数也适用于可能未以传统方式嵌套的数据结构。例如,您可将一个字符串视为一个嵌套字符系列:

words flatMap (_.toList)
// List(t, h, e, q, u, i, c, k, b, r, o, w, n, f, o, x, ...

Groovy

Groovy 还包含多个称为 collect() 的映射变体。默认的变体接受一个代码块,以便将该变体应用到集合的每个元素:

(1..5).collect {it += 1}
// [2, 3, 4, 5, 6]

像其他语言一样,Groovy 允许对简单的匿名高阶函数使用简写;it 保留字用于替代单独的参数。

collect() 方法适用于您可向其提供合理的判定条件的任何集合,比如一个字符串列表:

def words = ["the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog"]
words.collect {it.length()}
// [3, 5, 5, 3, 6, 4, 3, 4, 3]

Groovy 还有一个类似于 flatMap() 的折叠内部结构的方法,称为 flatten():

[[1, 2, 3], [4, 5, 6], [7, 8, 9]].flatten()
// [1, 2, 3, 4, 5, 6, 7, 8, 9]

flatten() 方法也适用于不太明显的集合,比如字符串:

(words.collect {it.toList()}).flatten()
// [t, h, e, q, u, i, c, k, b, r, o, w, n, f, o, x, j, ...

Clojure

Clojure 包含一个 (map ) 函数,它接受一个高阶函数(其中包含运算符)和一个集合:

(map inc numbers)
; (2 3 4 5 6 7 8 9 10 11)

(map ) 的第一个参数可以是任何接受单个参数的函数:命名函数、匿名函数或已存在的函数,比如递增其参数的 inc。此示例中演示了更典型的匿名语法,它生成一个字符串中的单词长度的集合:

(map #(count %) words)
; (3 5 5 3 6 4 3 4 3)

Clojure 的 (flatten ) 函数类似于 Groovy 的:

(flatten [[1 2 3] [4 5 6] [7 8 9]])
; (1 2 3 4 5 6 7 8 9)

折叠/缩减

在 3 种 Java 下一代语言中,第三个常见函数在名称上拥有最多变体和许多细微的区别。foldLeft 和 reduce 是一个名为 catamorphism 的列表操作概念上的特定变体,该概念是列表折叠的一种泛化。在此示例中,“折叠左侧” 表示:

使用一个二进制函数或运算符将列表的第一个元素与第二个元素相结合,创建一个新的第一个元素。
重复第一步,直到列表用完且您得到一个单一元素。

请注意,这是您在对一组数字求和时所做的操作:从 0 开始,加第一个元素,将结果与第二个元素相加,一直执行此操作,直到列表元素被用完为止。

Scala

Scala 拥有最丰富的折叠运算集合,这是因为它在一定程度上简化了动态类型的 Groovy 和 Clojure 中没有的多种类型场景。缩减函数常用于执行求和:

List.range(1, 10) reduceLeft((a, b) => a + b)
// 45

提供给 reduce() 的函数通常是一个接受两个参数,并返回单个结果的函数或运算符,以便可以使用一个列表。您可以使用 Scala 的语法糖来缩短函数定义:

List.range(1, 10).reduceLeft(0)(_ + _)
// 45

reduceLeft() 函数假设第一个元素是运算的左侧。对于相加等运算符,操作数的位置无关紧要,但放置顺序对相除等运算至关重要。如果希望反转运算符应用的顺序,可以使用 reduceRight():

List.range(1, 10) reduceRight(_ - _)
// 5

了解何时可使用缩减等高级抽象是掌握函数编程的一个关键。此示例使用 reduceLeft() 来确定集合中最常的单词:

words.reduceLeft((a, b) => if (a.length > b.length) a else b)
// jumped

缩减和折叠运算拥有重叠的功能,它们具有细微的差别,但这不属于本文的讨论范围。但是,通常可以看到它们的一个明显区别。在 Scala 中,签名 reduceLeft[B >:A](op:(B, A) => B):B 表明惟一想要的参数就是组合元素的函数。初始值应该是集合中的第一个值。相对而言,签名 foldLeft[B](z:B)(op:(B, A) => B):B 表示结果的一个初始种子值,所以您可以返回与列表元素类型不同的类型。

以下是一个使用 foldLeft 对集合求和的示例:

List.range(1, 10).foldLeft(0)(_ + _)
// 45

Scala 支持运算符重叠,所以两个常见的折叠操作 foldLeft 和 foldRight 分别拥有相应的运算符:/: 和 :\。因此,您可以使用 foldLeft 创建 sum 的简洁版本:

(0 /: List.range(1, 10)) (_ + _)
// 45

类似地,要找到一个列表中每个元素的级联区别(求和运算的反向操作,无可否认这种需求很少见),您可以使用 foldRight() 函数或 :\ 运算符:

(List.range(1, 10) :\ 0) (_ - _)
// 5

Groovy

Groovy 通过使用重叠来支持与 Scala 的 reduce() 和 foldLeft() 选项相同的功能,从而进入缩减类别。该函数的一个版本接受一个初始值。此示例使用 inject() 方法生成一个集合的总和:

(1..10).inject {a, b -> a + b}
// 55

替代形式接受一个初始值:

(1..10).inject(0, {a, b -> a + b})
// 55

Groovy 拥有一个比 Scala 或 Clojure 小得多的函数库 — Groovy 是一种不强调函数式编程的多范例编程,看到这种情况毫不奇怪。

Clojure

Clojure 主要是一种函数式编程语言,所以它支持 (reduce )。(reduce ) 函数接受一个可选的初始值,以便同时涵盖 Scala 所处理的 reduce() 和 foldLeft() 情形。(reduce ) 函数没有给用户带来任何惊喜。它接受一个需要两个参数的函数和一个集合:

(reduce + (range 1 11))
; 55

Clojure 将对类似 reduce 的功能的高级支持包含在一个名为 reducers 的库中,后面的一期文章将会介绍这个库。

结束语

学习不同范例(比如函数式编程)的部分挑战在于学习新术语。在不同社区使用不同的词汇时,这一过程会变得更加复杂。但一旦掌握了相似性,您就会看到,所有 3 种 Java 下一代语言都以令人惊奇的方式在语法上提供了重叠的功能。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 简单了解java函数式编码结构及优势

    前言 当垃圾回收成为主流时,它消除了所有类别的难以调试的问题,使运行时能够为开发人员管理复杂的.容易出错的进程.函数式编程旨在为您编写的算法实现同样的优化,这样您就可以从一个更高的抽象层面开展工作,同时运行时执行复杂的优化. Java 下一代语言并不都占用从命令式到函数式的语言频谱的同一位置,但都展现出函数功能和习语.函数式编程技术有明确定义,但语言有时为相同的函数式概念使用不同的术语,使得我们很难看到相似之处.在本期文章中,我比较了 Scala.Groovy 和 Clojure 的函数式编码风

  • 不同Java泛型构造函数的详解

    1.概述 我们之前讨论过Java Generics的基础知识.在本文中,我们将了解Java中的通用构造函数. 泛型构造函数是至少需要有一个泛型类型参数的构造函数.我们将看到泛型构造函数并不都是在泛型类中出现的,而且并非所有泛型类中的构造函数都必须是泛型. 2.非泛型类 首先,先写一个简单的类:Entry,它不是泛型类: public class Entry { private String data; private int rank; } 在这个类中,我们将添加两个构造函数:一个带有两个参数的

  • java.net.MalformedURLException异常的解决方法

    java.net.MalformedURLException at java.net.URL.<init>(URL.java:619) at java.net.URL.<init>(URL.java:482) at java.net.URL.<init>(URL.java:431) 代码中URL url = new URL(someUrl);这一行出现java.net.MalformedURLException异常 解决方法是,对someUrl中的参数名和参数值都URL

  • Java客户端调用.NET的WebService实例

    项目需要去调用.NET的WebSrevice,本身是Java,研究了半天,终于有些头绪,记下来. 1,新建.NET WebService.只在原方法上加上一个string类型的参数str [WebMethod] public string HelloWorld(string str) { return "Hello World"; } 2,新建Java的WebService客户端,lib引入以下5个jar包(我是用idea生成的WebService客户端,会下载7个包,我试着删掉了lo

  • java利用java.net.URLConnection发送HTTP请求的方法详解

    一.前言 如何通过Java发送HTTP请求,通俗点讲,如何通过Java(模拟浏览器)发送HTTP请求. Java有原生的API可用于发送HTTP请求,即java.net.URL.java.net.URLConnection,这些API很好用.很常用,但不够简便: 所以,也流行有许多Java HTTP请求的framework,如,Apache的HttpClient. 目前项目主要用到Java原生的方式,所以,这里主要介绍此方式. 二.运用原生Java Api发送简单的Get请求.Post请求步骤

  • Android 中出现java.net.BindException: bind failed: EADDRINUSE 问题解决办法

    Android 中出现java.net.BindException: bind failed: EADDRINUSE 问题解决办法 看下问题: try{ DatagramSocket udpSocket = new DatagramSocket(DEFAULT_PORT ); } catch (Exception e) { e.printStackTrace(); } //java.net.BindException: bind failed: EADDRINUSE (Address alrea

  • java.net.ConnectException: Connection refused问题解决办法

    Socket异常 客户端异常 java.net.ConnectException: Connection refused: connect. 该异常发生在客户端进行new Socket(ip, port)操作时,该异常发生的原因是或者具有ip地址的机器不能找到(也就是说从当前机器不存在到指定ip路由),或者是该ip存在,但找不到指定的端口进行监听.出现该问题,首先检查客户端的ip和port是否写错了,如果正确则从客户端ping一下服务器看是否能ping通,如果能ping通(服务服务器端把ping

  • 学习不同 Java.net 语言中类似的函数结构

    前言 函数式编程语言包含多个系列的常见函数.但开发人员有时很难在语言之间进行切换,因为熟悉的函数具有不熟悉的名称.函数式语言倾向于基于函数范例来命名这些常见函数.从脚本背景衍生而来的语言倾向于使用更具描述性的名称(有时是多个名称,包含多个指向同一个函数的别名). 在本期文章中,我将继续探讨 3 种重要函数(过滤.映射和缩减)的实用性,展示来自每种 Java 下一代语言的实现细节.文中的讨论和示例旨在减轻 3 种语言对类似函数结构使用的不一致名称时可能引起的混淆. 过滤 在过滤 函数中,您可指定一

  • Go语言学习函数+结构体+方法+接口

    目录 1. 函数 1.1 函数返回值 同一种类型返回值 带变量名的返回值 函数中的参数传递 函数变量 1.2 匿名函数——没有函数名字的函数 在定义时调用匿名函数 将匿名函数赋值给变量 匿名函数用作回调函数 可变参数——参数数量不固定的函数形式 1.3 闭包 1.4 defer语句 处理运行时发生的错误 1.5 宕机恢复(recover)——防止程序崩溃 2. 结构体 2.1 定义与给结构体赋值 3. 方法 3.1 结构体方法 3.2 接收器 指针接收器 非指针类型接收器 4. 接口 4.1 声

  • Swift语言中的函数学习教程

    函数是一个组织在一起语句集合,以执行特定任务.Swift 函数类似于简单 C 函数以及复杂的 Objective C 语言函数. 它使我们能够通过函数调用内部的局部和全局参数值. 像其他任何语言一样 swift 函数也遵循相同的步骤. 函数声明:它告诉编译器有关的函数的名称,返回类型和参数. 函数定义:它提供函数的实际主体. Swift 函数包含参数类型和返回类型. 函数定义 在Swift 语言中函数是由 "func" 关键字来定义.当一个新定义函数时,它可能需要一个或几个值作为函数输

  • C语言编程中函数的基本学习教程

    C 语言中的函数等价于 Fortran 语言中的子程序或函数,也等价于 Pascal 语言中的过程或函数.函数为计算的封装提供了一种简便的方法,此后使用函数时不需要考虑它是如何实现的.使用设计正确的函数,程序员无需考虑功能是如何实现的,而只需知道它具有哪些功能就够了.在 C 语言中可以简单.方便.高效地使用函数.我们经常会看到在定义后仅调用了一次的短函数,这样做可以使代码段更清晰易读. 到目前为止,我们所使用的函数(如 printf.getchar 和 putchar 等)都是函数库中提供的函数

  • java程序设计语言的优势及特点

    java语言是一种面向对象的程序设计语言吗 java语言是面向对象的程序设计语言 支持部分或绝大部分面向对象特性(类和实例.封装性.继承.多态)的语言即可称为基于对象的或面向对象的语言.Java跟C#是目前最流行的两门面向对象语言. 面向对象语言可以归类为: 1.基于对象的程序设计语言: 2.面向对象的程序设计语言. 面向对象编程具有以下优点: 1.易维护 采用面向对象思想设计的结构,可读性高,由于继承的存在,即使改变需求,那么维护也只是在局部模块,所以维护起来是非常方便和较低成本的. 2.易扩

  • python中print()函数的“,”与java中System.out.print()函数中的“+”功能详解

    python中的print()函数和java中的System.out.print()函数都有着打印字符串的功能. python中: print("hello,world!") 输出结果为:hello,world! java中: System.out.print("hello,world!"); 输出结果为:hello,world! 我们可以看到,这两个函数的用法是一样的 print()函数还有这种用法: print("1+1=",1+1) 输出结

  • c++语言中虚函数实现多态的原理详解

    前言 自上一个帖子之间跳过了一篇总结性的帖子,之后再发,今天主要研究了c++语言当中虚函数对多态的实现,感叹于c++设计者的精妙绝伦 c++中虚函数表的作用主要是实现了多态的机制.首先先解释一下多态的概念,多态是c++的特点之一,关于多态,简而言之就是 用父类的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数,这种方法呢,可以让父类的指针具有多种形态,也就是说不需要改动很多的代码就可以让父类这一种指针,干一些很多子类指针的事情,这里是从虚函数的实现机制层面进行研究 在写这篇帖子之前

  • java 中类似js encodeURIComponent 函数的实现案例

    我就废话不多说了,大家还是直接看代码吧~ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; /** * Utility class for JavaScript compatible UTF-8 encoding and decoding. * * @see http://stackoverflow.com/questions/607176/ja

  • C语言使用strcmp()函数比较两个字符串的实现

    C语言 strcmp() 函数用于对两个字符串进行比较(区分大小写). 头文件:string.h 语法/原型: int strcmp(const char* stri1,const char* str2); 参数 str1 和 str2 是参与比较的两个字符串. strcmp() 会根据 ASCII 编码依次比较 str1 和 str2 的每一个字符,直到出现不到的字符,或者到达字符串末尾(遇见\0). 返回值: 如果返回值 < 0,则表示 str1 小于 str2. 如果返回值 > 0,则表

  • c语言中回调函数的使用以及实际作用详析

    目录 前言 一.通过这节课程你能掌握以下知识: 二.程序架构的核心理念和需求 三.回调函数的作用 1.输出型 2.输入型 四.掌握回调函数的程序编写 总结 前言 今天给大家讲一下芯片/模块厂家写SDK必须会使用的一种技术:回调函数. 回调函数这个知识点其实并不是很难,难是难在网上很多讲解回调函数的都说的太学术化了化了,一点也不亲民. 很多人即使知道怎么写回调函数也根本就搞不懂它们在实际产品中也有什么用,什么时候用. 所以这节课呢我们会以程序架构的需求为出发点,讲解回调函数是怎么满足它这个需求的.

随机推荐