面向对象的功能编程

    技术2024-04-09  67

    您永远不会忘记自己的初恋。

    对我来说,她的名字叫塔宾达(宾迪)可汗。 那是我青年时期的太平年,确切的说是七年级,她美丽,聪明,最重要的是,她嘲笑我笨拙的十几岁男孩的笑话。 大部分七年级和八年级我们都在“出去”(当时称为)。 但是到了九年级,我们就分开了,这是一种礼貌的方式,她说她已经厌倦了连续两年都听过同样笨拙的十几岁男孩的笑话。 我永远不会忘记她(特别是因为我们在十年高中的同学聚会上再次相遇); 但更重要的是,我将永远不会失去那些珍贵的回忆(即使有些夸张)。

    关于本系列

    Ted Neward深入探讨了Scala编程语言,并带您一起学习。 在这个新的developerWorks 系列文章中 ,您将了解最新的炒作内容,并了解Scala在实践中的语言能力。 无论有什么比较之处,都将并排显示Scala代码和Java代码,但是(正如您将发现的那样),Scala中的许多内容与您在Java编程中发现的任何内容都没有直接关联,而这正是Scala的魅力所在! 毕竟,如果Java代码可以做到,为什么还要学习Scala?

    Java编程和面向对象是许多程序员的初恋,我们对待它的方式与我对Bindi的尊重和直接崇拜一样。 一些开发人员会告诉您Java编程从内存管理和C ++的地狱般的困境中拯救了他们。 其他人会说Java编程使他们摆脱了程序绝望的深渊。 甚至对于某些开发人员来说,使用Java代码进行面向对象的编程也仅仅是“我们总是做事的方式”。 (嘿,如果它对我的父亲以及他父亲在他之前起作用!)

    时间最终战胜了所有的初恋,但是还有一段继续前进的时间。 感情已经改变,故事中的演员已经成熟(并希望学到一些新的笑话)。 但更重要的是,我们周围的世界已经改变。 许多Java开发人员已经意识到,就像我们热爱Java编程一样,现在该赶上我们开发环境中的新机遇,并看看我们能从中获得什么。

    我会永远爱你 ...

    在过去的十年中,对Java语言的不满情绪开始上升。 尽管有些人可能将Ruby on Rails的增长作为主要因素,但我认为RoR(Ruby cognoscenti众所周知)是结果,而不是原因。 或者,也许更准确地说,使用Ruby的Java开发人员是更深层,更隐蔽的原因的结果。

    简而言之,Java编程正在显示其时代。

    或者,更准确地说,Java 语言正在显示其时代。

    考虑一下:Java语言最初诞生时,Clinton(第一个)就职于办公室,并且Internet仍仅被真正的极客定期使用,这在很大程度上是因为拨号上网是唯一的途径。 博客尚未发明,每个人都认为继承是重用的基本方法。 我们还认为,物体是模拟世界的最佳方式,摩尔定律将永远成指数规律。

    实际上,行业中许多人特别关注的是摩尔定律。 自2002/2003年以来,微处理器的增长趋势一直是创建具有多个“核心”的CPU:本质上,单个芯片中包含多个CPU。 这消除了摩尔定律,该定律说CPU速度每18个月将翻一番。 使多线程环境同时在两个CPU上执行,而不是在单个CPU上执行标准的循环循环,这意味着代码要生存就必须具有坚如磐石的线程安全性。

    关于这个特殊问题,学术界一直在进行大量研究,从而产生了许多新语言。 关键缺陷在于,这些语言大多数都建立在其自己的虚拟机或解释器之上,因此它们代表了(如Ruby一样)向新平台的过渡。 并发性紧缩是一个真正的问题,某些新语言提供了有力的答案,但是仅仅十年前,有太多的公司和企业记得从C ++迁移到Java平台。 迁移到新平台是许多公司不会认真考虑的风险。 实际上,许多人仍在护理从最后一次迁移到Java平台以来的伤疤。

    输入Scala。

    可缩放语言

    为什么选择Scala?

    学习一种新的编程语言始终是一项艰巨的任务,特别是对于那些对解决问题具有全新思维的人,例如Scala所接受的功能方法,尤其是当它与另一种方法(例如Scala如何融合对象)混合使用时,更是如此。功能概念的定位。 学习Scala需要时间,而在您本来已经很忙的计划中隐约可见,您可能无法一眼就能看出您的投资回报。 让我向您保证,Scala提供了许多有趣的功能,其中许多将在本系列的后续文章中介绍。 以下是部分列表,以帮助您了解使用该语言的好处。 使用Scala,您将能够:

    由于Scala在标识符方面的灵活性,因此可以创建内部DSL (例如Ruby)。 借助Scala的默认状态不变, 创建高度可扩展的并发数据处理器 。 借助Scala的各种语法功能(例如闭包和隐式定义), 将等效的Java代码减少一半或三分之二。 由于Scala鼓励功能设计,因此可以利用并行硬件架构 (例如多核CPU)。 由于Scala简化了某些类型规则,因此理解了更大的代码库 ,本质上要求“一切都是对象”。

    当然,Scala代表了一种强大而新颖的编程方式。 它可以编译并在JVM上运行的事实使得将其用于“实际工作”变得非常容易。

    Scala是一种功能对象混合语言,它具有以下几个重要因素:

    首先,Scala编译为Java字节码,这意味着它可以在JVM上运行。 除了使您能够继续利用丰富的Java开源生态系统之外,Scala可以以零迁移的努力集成到现有的IT环境中。 其次,Scala基于Haskell和ML的功能原理,但仍大量借鉴了Java程序员喜欢的熟悉的面向对象的概念。 结果,它可以将两个方面的优势融合为一个整体,从而在不牺牲我们所依赖的熟悉度的情况下提供巨大的收益。 最后,Scala由Martin Odersky开发,可能在Java社区中以Pizza和GJ语言而闻名,后者后来成为Java 5泛型的有效原型。 因此,它带有一种“严肃”的感觉。 这种语言不是一时兴起的,也不会以同样的方式被抛弃。

    就像Scala的名字所暗示的那样,它也是一种高度可扩展的语言。 一旦我们对该系列有更深入的了解,我将对此进行更多的解释。

    下载并安装Scala

    您可以从Scala网站下载 Scala捆绑包。 截至撰写本文时,当前版本为2.6.1-final。 它提供Java安装程序版本,RPM和Debian软件包,可以简单地在目标目录中解包的gzip / bz2 / zip捆绑包以及可以从头开始构建的源tarball。 (2.5.0-1版可通过简单的“ apt-get安装”从Debian网站上供Debian用户使用。2.6版有一些细微的差异,因此建议直接从Scala网站下载和安装。 )

    将Scala安装到您选择的目标目录中-撰写本文时,我处于Windows®环境中,因此我的目录将为C:/Prg/scala-2.6.1-final。 将环境变量SCALA_HOME定义为该目录,并将SCALA_HOME\bin放在PATH以方便从命令行调用。 要测试您的安装,只需从命令提示符启动“ scalac -version ”。 它应该以Scala版本2.6.1-final响应。

    功能概念

    在开始之前,我将列出一些功能概念,这些概念是理解Scala为什么外观和行为方式所必需的。 如果您花了一些时间使用功能性语言(Haskell,ML或最近进入功能性语言F#),则可以跳至下一部分 。

    函数式语言之所以得名,是因为程序的行为应类似于数学函数。 换句话说,给定一组输入,函数应始终返回相同的输出。 这不仅意味着每个函数都必须返回一个值,而且函数从一个调用到下一个调用必须固有地没有任何内在状态。 这种无状态的内在观念在功能/对象世界中默认表示为不可变对象,这是为什么功能语言被誉为疯狂并发世界的伟大救星的很大一部分。

    与最近在Java平台上开始开拓自己空间的许多动态语言不同,Scala是静态类型的,就像Java代码一样。 但是,与Java平台不同,Scala大量使用类型推断 ,这意味着编译器会在无需程序员干预的情况下深入分析代码以确定特定值是什么类型。 类型推断需要较少的冗余类型代码。 例如,考虑声明局部变量并为其分配所需的Java代码,如清单1所示:

    清单1. javac的天才(叹气)
    class BrainDead { public static void main(String[] args) { String message = "Why does javac need to be told message is a String?" + "What else could it be if I'm assigning a String to it?"; } }

    Scala不需要这样的操作,稍后我会告诉您。

    Scala语言还引入了许多其他功能(例如模式匹配),但将它们全部列出将再次超越故事。 Scala还添加了Java编程中当前缺少的许多功能,例如运算符重载(事实证明,这根本不像大多数Java开发人员所想象的那样),具有“上下限类型”的泛型,视图, 和更多。 这些功能以及其他功能使Scala在处理某些任务(例如处理或生成XML)方面极为强大。

    但是足够多的抽象概述:程序员喜欢看代码,所以让我们看一下Scala可以做什么。

    开始了解你

    根据计算机科学之神的要求,我们的第一个Scala程序将是标准的演示程序Hello World:

    清单2. Hello.Scala
    object HelloWorld { def main(args: Array[String]): Unit = { System.out.println("Hello, Scala!") } }

    使用scalac Hello.scala对此进行编译,然后使用Scala启动器( scala HelloWorld )或使用传统的Java启动器运行结果代码,注意将Scala核心库包括在JVM的类路径中( java -classpath %SCALA_HOME%\lib\scala-library.jar;. HelloWorld )。 无论哪种方式,都应该出现传统的问候语。

    清单2中的某些元素确实是您熟悉的,但是这里也有一些非常新的元素在起作用。 例如,从对System.out.println的熟悉调用开始,展示了Scala对底层Java平台的忠诚度。 Scala竭尽全力使Java平台的全部功能可用于Scala程序。 (实际上,它甚至允许Scala类型从Java类继承,反之亦然,但稍后会对此进行更多介绍。)

    另一方面,如果您观察的话,您会注意到System.out.println调用结束时缺少分号。 这不是错字。 与Java平台不同,如果终止在行尾很明显,则Scala不需要分号来终止语句。 但是,仍然支持分号,例如,如果在同一物理行上出现多个语句,则分号有时是必需的。 在大多数情况下,新芽的Scala程序员可以简单地放弃分号,并且Scala编译器会在必要时轻轻地提醒他(或她)(通常带有明显的错误消息)。

    同样,尽管这是次要的,但Scala不需要包含类定义的文件来镜像类的名称。 有些人会发现这是Java编程的一个令人耳目一新的变化。 那些没有的人可以继续使用Java类到文件的命名约定而不会出现问题。

    现在,让我们看一下Scala真正开始与传统的Java /面向对象代码有所不同的地方。

    功能和形式,最后在一起

    首先,Java爱好者会注意到,使用关键字object定义了HelloWorld而不是“ class ”。 这是Scala对Singleton模式的普遍性的致敬– object关键字告诉Scala编译器这将是一个singleton对象,因此,Scala将确保仅存在一个HelloWorld实例。 出于同样的原因,请注意main并未定义为静态方法,就像Java编程中那样。 实际上,Scala完全避免使用“ static ”。 如果应用程序需要同时具有类型的实例和某种“全局”实例,那么Scala应用程序将允许同名的class定义和object定义。

    接下来,注意main的定义,与Java代码一样,它是Scala程序可接受的入口点。 尽管它的定义看起来与Java不同,但它的定义是相同的:main将String数组作为参数,并且不返回任何内容。 但是,在Scala中,此定义看起来与Java版本有点不同。 args参数的定义定义为args: Array[String] 。

    在Scala中,数组表示为泛型Array类的实例,因此它还揭示了Scala使用方括号(“ []”)而不是尖括号(“ <>”)来表示参数化类型。 另外,为了保持一致,这种“ name: type ”模式在整个语言中都会出现。

    与其他传统函数式语言一样,Scala要求函数(在这种情况下为方法)必须始终返回值。 因此,它返回称为Unit的“非值”值。 出于所有实际目的,Java开发人员可以至少暂时认为Unit与void相同。

    方法定义的语法看起来有些有趣,因为它使用“ = ”运算符,几乎就像是将后面的方法主体分配给标识符main 。 实际上,这正是正在发生的事情:在函数式语言中,函数是一流的概念,例如变量和常量,因此在语法上也是如此。

    你说闭包吗?

    函数作为一流概念的一个含义是,必须以某种方式将它们识别为独立的构造(也称为闭包) ,Java社区最近一直在争论这一点。 在Scala中,这很容易做到。 在演示闭包的强大功能之前,请考虑清单3中的简单Scala程序。 在这里,函数oncePerSecond()重复一次其逻辑(在这种情况下,将打印到System.out)。

    清单3. Timer1.scala
    object Timer { def oncePerSecond(): Unit = { while (true) { System.out.println("Time flies when you're having fun(ctionally)...") Thread.sleep(1000) } } def main(args: Array[String]): Unit = { oncePerSecond() } }

    不幸的是,这个特定的代码并没有全部功能……甚至没有用。 例如,如果我想更改显示的消息,则必须修改oncePerSecond方法的主体。 传统的Java程序员可以通过将String参数定义为oncePerSecond来包含要显示的消息来完成此操作。 但这甚至受到了oncePerSecond限制:任何其他定期任务(例如ping远程服务器)都将需要它们自己的版本的oncePerSecond ,这明显违反了“请勿重复自己”规则。 如清单4所示,闭包提供了一种灵活而强大的替代方法:

    清单4. Timer2.scala
    object Timer { def oncePerSecond(callback: () => Unit): Unit = { while (true) { callback() Thread.sleep(1000) } } def timeFlies(): Unit = { Console.println("Time flies when you're having fun(ctionally)..."); } def main(args: Array[String]): Unit = { oncePerSecond(timeFlies) } }

    现在事情开始变得有趣。 在清单4中,函数oncePerSecond具有一个参数,但是其类型很奇怪。 正式地,称为callback的参数将一个函数作为参数。 只要传入的函数不接受任何参数(由“()”表示),并且不返回任何参数(由“ =>”表示)(由功能值“ Unit”指示),则这是正确的。 请注意,然后在循环主体中,我使用callback来调用传递的参数函数对象。

    幸运的是,我在程序的其他位置具有这样的功能,称为timeFlies 。 因此,我只是将其从main内部传递给了oncePerSecond函数。 (您还将注意到timeFlies使用了Scala引入的类Console ,该类的基本目的与System.out或新的java.io.Console类相同。这纯粹是一个美学问题;无论是System.out还是Console可以在这里工作。)

    匿名函数,您的功能是什么?

    现在,这个timeFlies函数似乎有些浪费—毕竟,除了传递给oncePerSecond函数之外,它实际上没有其他用途。 因此,我根本不会正式定义它,如清单5所示:

    清单5. Timer3.scala
    object Timer { def oncePerSecond(callback: () => Unit): Unit = { while (true) { callback() Thread.sleep(1000) } } def main(args: Array[String]): Unit = { oncePerSecond(() => Console.println("Time flies... oh, you get the idea.")) } }

    在清单5中,main函数将一个任意代码块作为参数传递给oncePerSecond ,以寻找整个世界,就像Lisp或Scheme中的lambda表达式一样,这实际上是另一种闭包。 这个匿名函数再次展示了将函数视为一等公民的能力,使您可以在继承之外的全新维度上泛化代码。 (“战略”模式的拥护者可能已经开始无法控制地垂涎三尺。)

    实际上, oncePerSecond仍然过于具体:它设置了不合理的限制,即每秒将调用一次回调。 我可以通过使用第二个参数来进一步泛化它,该参数指示调用传递的函数的频率,如清单6所示:

    清单6. Timer4.scala
    object Timer { def periodicCall(seconds: Int, callback: () => Unit): Unit = { while (true) { callback() Thread.sleep(seconds * 1000) } } def main(args: Array[String]): Unit = { periodicCall(1, () => Console.println("Time flies... oh, you get the idea.")) } }

    这是函数式语言中的一个常见主题:创建一个执行一件事的高级抽象函数,让它以一个代码块(一个匿名函数)作为参数,然后从高级函数中调用该代码块。 例如,漫步在一组对象上。 函数库不是在for循环中使用传统的Java迭代器对象,而是在集合类上定义一个函数(通常称为“ iter”或“ map”),该集合类接受一个带有单个参数的函数(要迭代的对象)过度)。 因此,例如,已经提到的Array类具有一个函数filter ,它在清单7中定义:

    清单7. Array.scala的部分清单
    class Array[A] { // ... def filter (p : (A) => Boolean) : Array[A] = ... // not shown }

    清单7声明p是一个函数,该函数采用A指定的泛型类型的参数并返回一个布尔值。 Scala文档指出, filter “返回由满足谓词p的该数组的所有元素组成的数组”。 这意味着如果我想回到我的Hello World程序一会儿,然后找到所有以字母“ G”开头的命令行参数,编写起来就像清单8一样简单:

    清单8.嗨,G们!
    object HelloWorld { def main(args: Array[String]): Unit = { args.filter( (arg:String) => arg.startsWith("G") ) .foreach( (arg:String) => Console.println("Found " + arg) ) } }

    在这里, filter接受谓词,这是一个匿名函数,它隐式返回一个布尔值( startsWith()调用的结果),并使用“ args ”数组中的每个元素调用谓词。 如果谓词返回true,则将其添加到结果数组。 遍历整个数组后,它将获取结果数组并返回它,然后立即将其用作“ foreach”调用的源,该调用的含义完全相同: foreach接受另一个函数并将该函数应用于该函数中的每个元素数组(在这种情况下,仅显示每个数组)。

    很难想象与上面的HelloG.scala等效的Java会是什么样子,并且不难发现Scala版本已经很多,更短,也更清晰。

    Wrapping up

    在Scala中进行编程非常令人熟悉,并且同时又有所不同。 相似之处在于,您可以使用多年来已经认识并喜欢的相同核心Java对象,但是在考虑将程序分解为各个部分的方式上却明显不同。 在忙碌的Java开发人员指南Scala的第一篇文章中,我向您简要介绍了Scala可以做什么。 还有更多的事情要做,但是现在,快乐的功能化!


    翻译自: https://www.ibm.com/developerworks/java/library/j-scala01228/index.html

    相关资源:微信小程序源码-合集6.rar
    Processed: 0.010, SQL: 9