给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的官网查阅更多的资料。原文点击这里

 

欢迎加群互相学习,共同进步。QQ群:iOS: 58099570 | Android: 572064792 | Nodejs:329118122 做人要厚道,转载请注明出处!











本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sunshine-anycall/p/4935597.html ,如需转载请自行联系原作者

相关文章
|
9天前
|
IDE Oracle Java
java基础教程(1)-Java概述和相关名词解释
【4月更文挑战第1天】Java是1995年Sun Microsystems发布的高级编程语言,以其跨平台特性著名。它介于编译型和解释型语言之间,通过JVM实现“一次编写,到处运行”。Java有SE、EE和ME三个版本,分别针对标准、企业及嵌入式应用。JVM是Java虚拟机,确保代码在不同平台无需重编译。JRE是运行环境,而JDK包含开发工具。要安装Java开发环境,可从Oracle官网下载JDK,设置JAVA_HOME环境变量并添加到PATH。
|
10天前
|
Java C# 开发者
【干货】Java开发者快速上手.NET指南
【干货】Java开发者快速上手.NET指南
|
17天前
|
Web App开发 前端开发 Java
《手把手教你》系列技巧篇(九)-java+ selenium自动化测试-元素定位大法之By name(详细教程)
【4月更文挑战第1天】 这篇教程介绍了如何使用Selenium Webdriver通过name属性来定位网页元素,作为系列教程的一部分,之前讲解了id定位,后续还会有其他六种定位方法。文中以百度搜索为例,详细说明了定位搜索框(name=&quot;wd&quot;)并输入关键词“北京宏哥”的步骤,包括手动操作流程、编写自动化脚本以及代码实现。此外,还提供了查看和理解Selenium源码的方法,强调了`open implementation`选项用于查看方法的具体实现。整个过程旨在帮助读者学习Selenium的元素定位,并实践自动化测试。
37 0
|
29天前
|
Web App开发 存储 JavaScript
《手把手教你》系列技巧篇(八)-java+ selenium自动化测试-元素定位大法之By id(详细教程)
【2月更文挑战第17天】本文介绍了Web自动化测试的核心——元素定位。文章首先强调了定位元素的重要性,指出找不到元素则无法进行后续操作。Selenium提供八种定位方法,包括By id、name、class name等。其中,By id是最简单快捷的方式。文章还阐述了自动化测试的步骤:定位元素、操作元素、验证结果和记录测试结果。此外,讨论了如何选择定位方法,推荐优先使用简单稳定的方式,如id,其次考虑其他方法。最后,作者提供了Chrome浏览器的开发者工具作为定位元素的工具,并给出了通过id定位的代码示例。
51 0
|
12天前
|
前端开发 Java 测试技术
《手把手教你》系列技巧篇(十二)-java+ selenium自动化测试-元素定位大法之By link text(详细教程)
【4月更文挑战第4天】本文介绍了link text在自动化测试中的应用。Link text是指网页中链接的文字描述,点击可跳转至其他页面。文章列举了8种常用的定位方法,其中着重讲解了link text定位,并通过实例展示了如何使用Java代码实现点击百度首页的“奥运奖牌榜 最新排名”链接,进入相应页面。如果link text不准确,则无法定位到元素,这说明linkText是精准匹配,而非模糊匹配。文章还提到了partial link text作为link text的模糊匹配版本,将在后续内容中介绍。
35 4
|
11天前
|
XML 前端开发 Java
《手把手教你》系列技巧篇(十四)-java+ selenium自动化测试-元素定位大法之By xpath上卷(详细教程)
【4月更文挑战第6天】按宏哥计划,本文继续介绍WebDriver关于元素定位大法,这篇介绍定位倒数二个方法:By xpath。xpath 的定位方法, 非常强大。使用这种方法几乎可以定位到页面上的任意元素。xpath 是XML Path的简称, 由于HTML文档本身就是一个标准的XML页面,所以我们可以使用Xpath 的用法来定位页面元素。XPath 是XML 和Path的缩写,主要用于xml文档中选择文档中节点。基于XML树状文档结构,XPath语言可以用在整棵树中寻找指定的节点。
42 0
|
30天前
|
Web App开发 安全 Java
《手把手教你》系列技巧篇(七)-java+ selenium自动化测试-宏哥带你全方位吊打Chrome启动过程(详细教程)
【2月更文挑战第16天】本文介绍了如何通过查看源码理解Selenium启动Chrome浏览器的过程。首先,展示了启动Chrome的Java代码,包括设置系统属性、创建WebDriver实例、最大化窗口、设置隐性等待、打开网站、获取页面标题以及关闭浏览器。文章还讲解了包(package)、import导入、setProperty设置系统属性、WebDriver接口、driver实例、manage方法、get方法加载网页以及quit方法退出浏览器的基本概念和作用。适合没有Java基础的读者了解Selenium与Java的交互方式。
45 3
|
3天前
|
存储 Java
Java基础教程(7)-Java中的面向对象和类
【4月更文挑战第7天】Java是面向对象编程(OOP)语言,强调将事务抽象成对象。面向对象与面向过程的区别在于,前者通过对象间的交互解决问题,后者按步骤顺序执行。类是对象的模板,对象是类的实例。创建类使用`class`关键字,对象通过`new`运算符动态分配内存。方法包括构造函数和一般方法,构造函数用于对象初始化,一般方法处理逻辑。方法可以有0个或多个参数,可变参数用`类型...`定义。`this`关键字用于访问当前对象的属性。
|
6天前
|
存储 Java 编译器
Java基础教程(4)-Java中的操作符
【4月更文挑战第4天】Java中的String是常用类,字符串是不可变对象,用双引号表示。String对象在编译期长度受限于65535,运行期不超过Int范围。字符串方法如length()、substring()、replace()、equals()等提供了多种操作。可变字符串可使用StringBuffer或StringBuilder。String对象通过字符串池优化内存,池中已有相同内容字符串则返回其引用。
|
8天前
|
前端开发 JavaScript Java
《手把手教你》系列技巧篇(十七)-java+ selenium自动化测试-元素定位大法之By css上卷(详细教程)
【4月更文挑战第9天】本文介绍了CSS定位方式的使用,包括它的优势和8种常用的定位方法。CSS定位相比XPath定位更快、更稳定。文章通过示例详细讲解了如何使用CSS定位元素,包括通过id、name、class name、tag name、link text、partial link text以及XPath进行定位。还提供了Java代码示例来演示如何在自动化测试中使用这些定位方法。
38 1