给Java开发者的Scala教程

author:Michel Schinz,Philipp Haller

1. 简介

本文将该要的介绍Scala语言和其编译。这里假设读者已经有一定的java开发经验,需要概要的了解他们可以用Scala 做些什么。

2. 第一个例子

我们用全世界最著名的代码来作为开始。虽然没什么用,但是可以很好地直观的了解Scala:

object HelloWorld { def main(args: Array[String]): Unit = { println("Hello, world!") } }

没什么复杂的。只有一点,这个包含了main方法的object声明。这个object的声明是定义了一个单例。所以,上面 的声明定义了一个类和这个类的实例,这个实例也叫做HelloWorld。这个实例在第一次使用到的时候被创建。

细心的读者会发现,这个main方法并没有并声明为static的。这是因为static成员在Scala中是不存在的。需要 静态成员的时候,就把这些成员都放在单例中(object声明)。

2.1 编译这个例子

要编译使用scalac命令。

>scalac HelloWorld.scala

编译之后生成HelloWorld.class文件。

2.2 运行这个例子

编译之后Scala代码可以使用scala命令来运行。这些命令的使用和java非常类似。

>scala -classpath . HelloWorld

3 和Java互操作

一个scala的优势是可以很容易的使用java代码。java.lang包的全部类都是默认被import的,其他的也可以显示引入。 下面的代码会表明这一点。

import java.util.{Date, Locale} import java.text.DateFormat import java.text.DateFormat._ /** * Created by home on 3/11/15. */ object FrenchDate { def main(args: Array[String]): Unit = { val now = new Date val df = getDateInstance(LONG, Locale.FRANCE) println(df format now) } }

Scala的import语句和java的几乎一样,不过,更加灵活。一个包得多个类可以在一个import语句里用大括号 括起来一次引用进来。另一个不同是,使用下划线而不是星号引入包中的全部类。所以,第三行import语句引入了 DateFormat的全部成员。

在main函数中,首先创建了一个Java的Date类实例,默认包含当前时间。然后用静态方法gerDateInstance 定义了一个日期格式。最后打印格式化以后的法国的时期。最后的df format now是一个很有意思的Scala语法。 只需要一个参数的方法可以用中缀语法。按照一般的写法是这样的:df.format(now)

最后需要注意的是,Scala可以直接从java继承一个类,也可以实现java的接口。

4 什么都是对象

Scala是一个纯的面向对象的语言,所以其内部的全部都是对象。包括数字和方法。在这一帮面,Scala和java有 很大的不同。因为,java区分值类型和引用类型,也不允许把方法当做值来处理。

4.1 数字和对象

因为Scala的数字就是对象,所以可以包含方法。如:

1 + 2 * 3 / x

还原出真实的方法调用后:

(1).(((2).*(3))./(x))

这同时也说明+, *等符号是Scala的可用的标示符。

4.2 方法也是对象

这个也许更加让java的开发者吃惊。方法也是Scala的对象。因此可以把方法作为参数传递,把他们赋值给变量,从 另外方法中返回。这也就赋予了Scala另外一个能力:函数式编程。

下面就使用一个例子来演示这一点。有一个每一秒需要执行一次的方法oncePerSecond,并且有一个回调方法作为参数。 () => Unit的写法是定义了一个无参数,返回Unit的方法(Unit就相当于c/c++的void)。代码:

object Timer { def oncePerSecond(callback: () => Unit): Unit = { while (true) { callback(); Thread sleep 1000 } } def timeFlies(): Unit = { println("time flies like an arrow...") } def main(args: Array[String]): Unit = { oncePerSecond(timeFlies) } }

这里为了输出字符串,我们使用了预定义的println方法,而不是System.out。

4.2.1 匿名方法

因为上面的代码非常容易理解,所以可以精简一点。首先,方法timeFlies定义出来只是为了作为参数传递 给方法oncePerSecond。给这个方法命名之后,也只使用一次。非常没有必要。使用Scala的匿名方法可以 省去这些麻烦。如:

object TimerAnonymous { def oncePerSecond(callback: () => Unit): Unit = { while (true) { callback(); Thread sleep 1000 } } def main(args: Array[String]): Unit = { oncePerSecond(() => println("time flies like an arrow...")) } }

上面的匿名函数使用=>区分函数的参数列表和函数体。此例中,函数的参数列表是空,所以括号是空的。函数体 和上面例子中的timeFlies的一样。

5 类

如上所述,Scala是一个面向对象的语言,所以也有的概念。Scala的类声明和java的基本一样,只不过 Scala的类定义中可以包含参数,如下:

class Comlex(real: Double, imaginary: Double) { def re() = real def im() = imaginary }

这个Complex类需要两个参数。这些参数必须在创建实例的时候提供,如:new Complex(1.5, 2.3)。这个类 包含两个方法成员,re和im,可以取得两个对应的参数值。

两个成员方法的返回类型并没有显示给出。编译器会自动推断。编译器并不是总能自动推断出返回类型,而且也没有 什么规律可以知道什么时候编译器就不能自动推断。这倒是不会造成什么困扰,因为推断不出的时候编译器会给出 warning。对于初级开发人员最好是给出一个类型,来看看编译器是不是同意(不同意就会warning)。

5.1 无参数方法

上面的例子还有一个小问题。在调用上面re,im方法的时候不得不写上两个空括号。

def main(args: Array[String]): Unit = { val c = new Complex(1.3, 4.6) println("Imaginary part: " + c.im()) }

这很不方便。这个在Scala是可以的。

class Complex(real: Double, imaginary: Double) { def re = real def im = imaginary } object TimerAnonymous { def main(args: Array[String]): Unit = { val c = new Complex(1.3, 4.6) println("Imaginary part: " + c.im) } }

括号要不加就全部不加。

5.2 继承和override

Scala的类全部都继承自一个超类。如果没有显示的给出超类,如上面的Complex类,那么则默认的继承自scala.AnyRef。 在Scala中也可以override超类的方法。在override的时候需要用override关键字明确的标明。如:

class Complex(real: Double, imaginary: Double) { def re = real def im = imaginary override def toString() = "" + re + (if (im < 0) "" else "+") +im + "i" }

6 case类和模式匹配

程序中经常出现的一种数据结构是Tree。如,解析器和编译器内部,程序作为树呈现;XML文档是树,等。我们将通过 一个小计算器程序看看Scala中有多少树需要处理。这个程序是用来处理非常简单的,包含了常量和变量的代数 表达式。两个例子是1 + 2和(x + x) + (7 + y)。

首先我们需要明确,这类的表达式要如何展现。很明显的是一个树:运算符是一个节点,这个节点的叶子就是常量 或者变量。

在java里,这样的tree会使用一个抽象超类来处理。然后,这个抽象超类的子类来处理一个节点或者叶子。在函数式 编程语言中可以使用代数式的类型来达到同样的目的。Scala的case class是间于两者之间的一个概念。如:

abstract class Tree case class Sum(l: Tree, r: Tree) extends Tree case class Var(n: String) extends Tree case class Const(v: Int) extends Tree

类Sum、Var和Const被声明为case class,表明他们在某些方面和标准类是不一样的:

  • new关键字在创建实例的时候不是必须的(如:Const(5),不用写new Const(5)),
  • getter方法自动根据构造函数的参数(如Const类的实例c,从c中取构造函数参数v的值可以用c.v来取得),
  • 提供默认的toString方法,并按照最基本的调用方法打印。如:x+1打印为:Sum(Var(x), Const(1)),
  • 这些类的实例可以用模式匹配来分解,这个下面会讲到。

我们已经定义好了表达代数式的数据类型。接下来可以定义他们之上的操作。我们来定义一个方法来推导这个表达式 在某些条件下的值。这些“条件”就是给定变量某些特定的值。比如,表达式 x+1 在某一种条件下(x的值为5的时候) 推导的值是6。

所以我们需要找到一种可以代表这种条件。你会想到hash table这种数据结构,但是在scala中我们可以直接用方法 一个特定的条件无非就是一个变量和这个变量此时拥有的值。上面说到的 {x = 5} 的条件可以简单的表达为:

{case "x" => 5}

上面的表达式定义了一个函数。当参数是x的字符串时,返回数值5,其他情况下抛出异常。

在继续往下之前,我们给这一条件类型一个名称。我们当然可以用String => Int。但是如果我们给出个名称的 话会简单很多。在Scala中可以这样:

type Environment = String => Int

从现在开始,type Environment可以代表String到Int的函数了。

我们现在可以给出推导函数了。概念非常简单:两个表达式的和就是这两个表达式的值的和。一个常量的值就是这个常量 本身。概念转化为Scala非常容易:

def eval(t: Tree, env: Environment): Int = t match { case Sum(l, r) => eval(l, env) + eval(r, env) case Var(n) => env(n) case Const(v) => v }

这个推导方法使用模式匹配的方式处理树。上面的表达式的含义已经很明显:

  1. 检查表达式是不是Sum,如果是则其包含了左子树和右子树,分别为lr。之后继续推导箭头后面的 表达式;
  2. 如果第一个表达是没有成功执行,也许这个树不是一个Sum。继续检查如果t(形参)是否为Var,如果是则将Var节点 包含的值和n变量绑定并继续处理右手表达式。
  3. 如果第二个检查也失败了,那么t既不是Sum也不是一个Var。检查t是否为一个Const,如果是则将Const节点包含的 值和v变量绑定并继续处理右手表达式。
  4. 最后,如果全部检查失败,则抛出一个异常。在这里,只会在更多的Tree子类被定义的时候发生。

你会发现,模式匹配就是把一个值和一系列的模式进行匹配。一旦匹配了,就提取值的不同部分,并给其命名。最后 使用这些命名的组件做最后的推导。

一个经验丰富的面向对象开发者会问为什么我们不把eval定义成类Tree和他的子类的一个方法呢。我们可以这么做, 因为Scala允许在case class中定义方法。什么时候使用模式匹配,什么时候使用方法就是个人口味的事了。 但是,也有一些很明显的经验可以借鉴:

  • 当使用方法时,添加新的节点更加容易:只需要定义一个新的Tree子类。另一方面来说:给树添加一种新的操作 (比如加法之外的减、乘除等)就比较麻烦了。因为,需要给每一个子类都做出相应的修改。
  • 使用模式匹配的时候,情况正好相反。添加一个新的节点需要修改全部的模式匹配方法。添加一个操作就很简单了 ,只需要定义另外的一个方法。

7 接口(Traits)

除了可以继承自一个超类,一个Scala类还可以实现一个或者多个traits

对于一个java开发者来说最容易理解trait的方法是告诉他,这就是java的interface。java的interface也可以 包含代码。在Scala中,一个类继承了trait的时候,他实现了trait的接口,也继承了这个trait的全部代码。

我们使用一个经典案例来演示trait的特性。对对象列表排序是一个经常使用的功能。java比较的时候会实现Comparable 接口。Scala可以比单纯的实现一个Comparable的trait做的更好一些。这里我们定义一个trait为Ord。 声明如下:

trait Ord { def <(that: Any): Boolean def <=(that: Any): Boolean = (this < that) || (this == that) def >(that: Any): Boolean = !(this <= that) def >=(that: Any): Boolean = !(this < that) }

上面的声明创建了一个新的类型Ord,和java的Comparable接口功能一样,并给了三个比较的默认实现和一个抽象方法。 等于和不等于比较没有出现,这是因为那些是任何对象都有的默认实现。Any类型是Scala中全部其他类型的超类。 其中包括基本类型Int、Float等。

作为示例我们会定义一个Date类来实现上面的接口,以达到比较的目的。这个Date类使用格林威治时间,包含day、 month和year,这些全部都是Int型。

class Date(y: Int, m: Int, d: Int) extends Ord { def year = y def month = m def day = d override def toString(): String = year + "-" + month + "-" + day }

首先extends了Ord,并定义了相应的年月日参数。之后我们重定义equals方法,这样在比较两个时间的时候 就是在比较时间的不同部分了。

override def equals(that: Any): Boolean = that.isInstanceOf[Date] && { val o = that.asInstanceOf[Date] o.day == day && o.month == month && o.year == year }

这个方法使用了内置的isInstanceOfasInstanceOf。第一个isInstanceOf相当于java的 instanceof方法。在对象为某了类型的实例的时候返回true。第二个asInstanceOf相当于java的 类型转换操作符。如果一个实例是某类型的,则可以转换。否则,抛出ClassCastException异常。

最后一个需要实现的方法是小于比较。这会用到另外的一个预定义的方法error,这个方法会抛出一个你给定消息 的异常。

def <(that: Any): Boolean = { if (!that.isInstanceOf[Date]) sys.error("cannot compare " + that + " and a Date") val o = that.asInstanceOf[Date] (year < o.year) || (year == o.year && (month < o.month || (month == o.month && day < o.day))) }

这样就完成了Date类的定义。这个类的实例也已被看做日期,也可以被视为可比较的对象。Traits比上面例子中 展现的更加灵活。读者可以深入研究。

8 泛型

本文最后要讨论的Scala特性是泛型。泛型可以编写类型为参数的代码。最典型的例子就是C++的STL。在Java1.5以前 STL里的链表等库只能接受Object为参数,在特定的类型下取出对象之后再强制类型转换。Scala可以定义泛型类 和方形方法来把你从一堆的类型转换中拯救出来。下面就定义一个最简单的容器类,可以为空也可以指向某一类型的对象。

class Reference[T] { private var contents: T = _ def set(value: T) { contents = value } def get: T = contents }

这个类的类型参数叫做T。这个类型在类的内部修饰contents成员,以及set方法的value和get方法的返回类型。 很有意思的是这一句private var contents: T = _。下划线表示给contents赋了一个默认值。数字类型的 默认值是0,逻辑类型的默认值是false,Unit的默认值是(),其他的对象的默认值为null

下面看看如何使用。

class IntegerReference { def main(args: Array[String]): Unit = { val cell = new Reference[Int] cell.set(13) println("Reference contains the half of " + (cell.get * 2)) } }

get之后直接计算,无需再做任何的类型转换。

9 结语

本文只是给你可以快速的索引,让你更快的了解Scala这个语言。有兴趣的读者可以到Scala的官网查阅更多的资料。原文点击这里

时间: 2024-10-07 05:06:52

给Java开发者的Scala教程的相关文章

Scala学习笔记及与Java不同之处总结-从Java开发者角度

Scala与Java具有很多相似之处,但又有很多不同.这里主要从一个Java开发者的角度,总结在使用Scala的过程中所面临的一些思维转变. 这里仅仅是总结了部分两种语言在开发过程中的不同,以后会陆续更新一些切换后在开发过程中值得注意的地方.以下列举了部分,但令人印象深刻的Scala语言的不同之处,具体的代码演示样例及具体阐述见下文. ? Scala中可直接调用Java代码,与Java无缝连接. 语句能够不用";"结束.且推荐不适用";". 变量声明时以var或va

从Java开发者的视角解释JavaScript

我们无法在一篇博文里解释JavaScript的所有细节.如果你正或多或少地涉及了web应用程序开发,那么,我们的Java工具和技术范围报告揭示了,大多数(71%)Java开发者被归到了这一类,只是你对JavaScript遇到了阻碍. 毫无疑问,你已经知道了Java和JavaScript,不管它们有着多么类似的命名,彼此没有共享太多共通之处.Java的静态类型.符合直接规律的简单语法和冗长,与JavaScript的动态.缺乏一致性原则和怪异,有着巨大的不同. 然而,JavaScript是web的编

Java开发者写SQL时常犯的10个错误

首页 所有文章 资讯 Web 架构 基础技术 书籍 教程 我要投稿 更多频道 » - 导航条 - 首页 所有文章 资讯 Web 架构 基础技术 书籍 教程 我要投稿 更多频道 » - iOS - Python - Android - Web前端 Java开发者写SQL时常犯的10个错误 2015/03/10 | 分类: 基础技术 | 0 条评论 | 标签: SQL 分享到:0 本文由 ImportNew - zer0Black 翻译自 jooq.欢迎加入翻译小组.转载请见文末要求. 我十分惊讶的

Java 8 vs. Scala(一): Lambda表达式

[编者按]虽然 Java 深得大量开发者喜爱,但是对比其他现代编程语言,其语法确实略显冗长.但是通过 Java8,直接利用 lambda 表达式就能编写出既可读又简洁的代码.作者 Hussachai Puripunpinyo 的软件工程师,作者通过对比 Java 8和 Scala,对性能和表达方面的差异进行了分析,并且深入讨论关于 Stream API 的区别,本文由OneAPM 工程师编译整理. 数年等待,Java 8 终于添加了高阶函数这个特性.本人很喜欢 Java,但不得不承认,相比其他现

Java开发者必备十大学习网站

作为开发者来说,必备的除了对编码的热情还要有自己的一套技巧,另外不可缺少的就是平时学习的网站.以下本人收集的Java开发者必备的网站,这些网站可以提供信息,以及一些很棒的讲座, 还能解答一般问题.面试问题等,或许你会认为有些网站适合任何水平的开发者,但是我认为:对于Java开发大牛来说,网站的好坏取决于如何使用它们. Stack overflow Stack overflow.com 可能是编程界中最流行的网站了, 是一个与程序相关的IT技术问答网站,用户可以在网站免费提交问题,浏览问题,索引相

Scala 教程

Scala 教程pythonScala 是一门多范式(multi-paradigm)的编程语言,设计初衷是要集成面向对象编程和函数式编程的各种特性.Scala 运行在Java虚拟机上,并兼容现有的Java程序.Scala 源代码被编译成Java字节码,所以它可以运行于JVM之上,并可以调用现有的Java类库.谁适合阅读本教程?本教程适合想从零开始学习 Scala 编程语言的开发人员.当然本教程也会对一些模块进行深入,让你更好的了解 Scala 的应用. 学习本教程前你需要了解在继续本教程之前,你

JAVA开发者学习必备的十大网站

作为开发者来说,必备的除了对编码的热情还要有自己的一套技巧,另外不可缺少的就是平时学习的网站.以下本人收集的 Java 开发者必备的网站,这些网站可以提供信息.以及一些很棒的讲座 , 还能解答一般问题.面试问题等,或许你会认为有些网站适合任何水平的开发者,但是我认为::对于 Java 开发大牛来说,网站的好坏取决于如何使用它们. 1.Stack overflow Stack overflow.com 可能是编程界中最流行的网站了 , 是一个与程序相关的 IT 技术问答网站,用户可以在网站免费提交

Java开发者易犯错误Top10

摘要:在Java中,有些事物如果不了解的话,很容易就会用错,如数组转换为数组列表.元素删除.Hashtable和HashMap.ArrayList和LinkedList.Super和Sub构造函数等,如果这些对你来说是陌生的,你可以在本文中了解它们. 本文总结了Java开发者经常会犯的前十种错误列表. Top1. 数组转换为数组列表 将数组转换为数组列表,开发者经常会这样做: [java] view plaincopy List<String> list = Arrays.asList(arr

2016年netty/mina/java nio视频教程java游戏服务器设计教程

2016年netty/mina/Javanio视频教程Java游戏服务器设计教程 需要的加qq:1225462853,备注:程序员学习视频 其他视频都可以索要(Netty   NET    C++ 等等) 互联网架构师教程:http://blog.csdn.net/pplcheer/article/details/71887910 netty录制时间为2015.11-2016.2月份  netty教程为加密视频!      netty12个课程已全部录制完成,相信通过这12节课的分析能让大家对n