原文地址

本文只是带你进入 Scala 的世界,包括安装、不可变量 val、可变量 var、定义类、集合(包括列表(list)、集(set)、映射(map))以及集合遍历和集合库(能达到并行/并发效果)。

题外话,如果 Java 争气的话,还就真不会出现像 Scala 这些语言。对于函数式编程风格的支持,尤其是对于 Lambda 表达式的支持,能减少必须要编写的逻辑无关的样板代码,让程序员集中精力任务本身。而 Java 对 Lamdba 表达式的支持到 JavaSE8 才实现(你可以查一下 Java SE8 的发布时间,和其他语言在语言层面上何时支持的匿名函数、Lambda 表达式、函数式编程、并行编程……)。

Scala,一门强类型定义的静态类型语言,结合了面向对象编程与函数式编程思想,语法简洁,完全兼容 Java,运行在 JVM 上。JVM 上的其他语言还有 Groovy、JRuby、Clojure,而 Scala 有什么不同?——能同时提供函数式风格和良好并发支持的强类型语言,只有 Scala。JRuby 和 Groovy 都是动态语言(Scala 是静态类型语言),它们不是函数式的,也无法提供比 Java 更好的并发解决方案。另一方面,Clojure 是一种混合型的函数式语言,它天生就是动态的,因此不是静态类型。而且它的语法类似 Lisp,除非你很熟悉,否则这可不是一种易于掌握的语言(Lisp 是号称高智商的人才能使用的语言,如果你看过《黑客与画家》,应该记得作者的一句话,大意是,如果竞争对手采用 Lisp 开发 Web,那就应该小心了,言下之意是,Lisp 跟其他语言相比,生产效率太高了,很容易实现一个想法)。

总结起来,Scala 特点体现在以下几方面:

  • 也运行在 JVM 上,使 Scala 可以和现存的应用同时运行;
  • 可以直接使用 Java 类库,使开发人员可以利用现有的框架和遗留代码;
  • 与 Java 一样都是静态类型语言。遵循相同的编程哲学;
  • 语法与 Java 比较接近,使得开发人员可以快速掌握语言基础(看怎么写,我觉得Scala的简洁程度,会让初学者崩溃);
  • 既支持面向对象范型,也支持函数式编程范型,开发人员可以逐步运用函数式编程的思想。

Scala 对 Java 的不同:

  • 类型推断。Java 中必须声明变量、实参或形参的类型。而在 Scala 中则不用,它会推断出变量的类型;
  • 函数式编程。Scala 将函数式编程的重要概念引入 Java,包括代码块、高阶函数(high-order function)以及复杂的集合库;
  • 不变量。Java 也允许使用不变量,不过是通过一个很少使用的修饰符实现的。另外,Java 的 String 也是不变量的一个实现,而 Scala 会让你决定一个变量是否可变,比如 val,还是 var。这将对程序在并发环境中的行为,产生巨大影响;
  • 高级程序构造。Scala 很好地使用了基础语言,并将有用的概念分层。包括并发 Actor 模型、使用高阶函数 Ruby 风格的集合以及作为一等对象类型(first-class)的 XML 处理。

文中代码本人在 Scala 2.11 上编译并运行通过。

第一步,先安装好 Scala Typesafe stack,打开命令行窗口,键入“scala”,将启动 REPL(读入-运算 输出 循环)交互式编码环境。然后就可以写下你的第一行 Scala 代码:

scala> val columbus: Int = 1492

 

columbus: Int = 1492

 

scala>

声明了一个类型为 Int 变量,初始值为 1492,就像在Java里 Int columbus = 1492; 一样。

Scala 把类型放在变量之后(反向声明方式),使用 val 显式地把变量声明为不可变的。如果想修改这个变量,将报错:

scala> columbus=1500

 

<console>:8: error: reassignment to val

 

       columbus=1500

 

               ^

 

scala>

错误消息精确地指出了错误位于行的位置。

再尝试声明这个变量,这次用 var,让其可变。编译器能推断出 1492 是一个整数,所以,无需指定类型:

scala> var columbus = 1492

 

columbus: Int = 1492

 

scala> columbus = 1500

 

columbus: Int = 1500

 

scala>

接下来,定义一个类,名为 Employee,有三个不可变的字段:nameagecompany,拥有各自的缺省值。

scala> case class Employee(name:String="guest",

 

     | age:Int=30,

 

     | company:String="DevCode")

 

defined class Employee

 

scala>

不需要像 Java 中那样还要定义字段以及 setter 和 getter 方法。这些对 Scala 来说,是样板代码,完全是多余的。

case 关键字相当于 Java 的 switch 语句,不过更灵活。它说明该类具有模式匹配的额外机制,以及其他一些特性,包括用来创建实例的工厂方法(不需要使用 new 关键字来构造),同样也不需要创建缺省的 getter 方法。

与 Java 不同,默认访问控制是 public(而不是 protected),Scala 为 public 变量自动创建一个 getter 方法。当然也可以把字段定义成可变且/或私有(private)的,只要在前面使用 var(例如,case class Person(private var name:String)),这样你就不能通过 guest.name 来访问 name 字段。

接下来,用不同方式创建一些实例,看看其他的特性,如命名参数和缺省参数(从Scala2.8开始引入):

scala> val guest=Employee()

 

guest: Employee = Employee(guest,30,DevCode)

 

scala> val guestAge=guest.age

 

guestAge: Int = 30

 

scala> val anna=Employee("Anna")

 

anna: Employee = Employee(Anna,30,DevCode)

 

scala> val thomas=Employee("Thomas",41)

 

thomas: Employee = Employee(Thomas,41,DevCode)

 

scala> val luke=Employee("Luke",company="LucasArt")

 

luke: Employee = Employee(Luke,30,LucasArt)

 

scala> val yoda=luke.copy("Yoda",age=800)

 

yoda: Employee = Employee(Yoda,800,LucasArt)

 

scala>

对于你定义的类,Scala 自动也提供了用来克隆类实例的 copy 方法。克隆方法,也是样板代码。从此,你就不用自己写了。

不过,下面的写法是不行的(可不是因为 Darth 不是 DevCode 的雇员!)。

scala> val darth=Employee("Darth","DevCode")

 

<console>:9: error: type mismatch;

 

 found   : String("DevCode")

 

 required: Int

 

       val darth=Employee("Darth","DevCode")

 

                                  ^

 

scala>

因为构造函数在这个位置需要的 age 作为参数,而你又没有显性地进行命名,company=”DevCode”。

现在我们再来看集合,这才是真正让人兴奋的地方。Scala 集合类型包括列表(list)、集(set)和映射(map)。

有了泛型(Java5 以上),Java 遍历一个列表,比如整数型列表,代码如下所示:

List<Integer> numbers = new arrayList<Integer>();

numbers.add(1);

numbers.add(2);

numbers.add(3);

for(Integer n:numbers) {

    System.out.println("Number "+n);

}

运行结果:

Number 1

 

Number 2

 

Number 3

Scala 对于可变集合和不可变集合进行了系统性地区别处理。但鼓励使用不可变集合,因此在缺省情况下创建的是不可变集合,通过模拟的方式实现添加、更新和删除。注意,这些操作不是修改集合,而是返回新的集合。

与前面 Java 代码等价的 Scala 代码可能像下面这样:

scala> val numbers=List(1,2,3)

 

numbers: List[Int] = List(1, 2, 3)

 

scala> for(n<-numbers) println("Number "+n)

 

Number 1

 

Number 2

 

Number 3

 

scala>

这里的 for 循环语法结构非常接近于 Java 的命令式编程风格。在 Scala(以及 Java 虚拟机上其他很多语言如:Groovy、JRuby 或 JPython)里还有另外一种方式来实现上面的逻辑。这种方式使用一种更加偏向函数编程的风格,引入了 Lambda 表达式(有时也称为闭包——closure)。简单地说,Lambda 表达式就是你可以拿来当作参数传递的函数。这些函数使用参数作为输入(在这个例子中就是 n 整型变量),返回语句作为函数体的最终语句。他们的形式如下:

functionName { input =>

    body

}

scala> numbers.foreach {n:Int=> println("Number "+n) }

 

Number 1

 

Number 2

 

Number 3

 

scala>

上面的例子中,函数体只有一条语句(println……),返回“空结果”Unit,大致相当于 Java 中的 void。

除了打印列表外,我们更想对列表中元素做些处理和变换。让我们尝试一些例子:

scala> val reversedList=numbers.reverse

 

reversedList: List[Int] = List(3, 2, 1)

 

scala> val numbersLessThan3=numbers.filter {n=>n<3}

 

numbersLessThan3: List[Int] = List(1, 2)

 

scala> val oddNumbers=numbers.filterNot {n=>n%2==0}

 

oddNumbers: List[Int] = List(1, 3)

 

scala> val highterNumbers=numbers.map {n=>n+10}

 

highterNumbers: List[Int] = List(11, 12, 13)

 

scala>

看到了吧,作用在集合上函数,你可以不写圆括号,少了很多样板代码。

变换 map 非常有用,它对列表的每个元素应用闭包,返回一个新的列表。

我们在这里还想介绍最后的一个方法,就是 foldLeft,它把状态从一个元素传播到另一个元素。比如,要算出一个列表里所有元素的和,你需要累加,并在切换元素的时候保存中间的计数:

scala> val sumOfNumbers=numbers.foldLeft(0) {( total,element)=>

 

     | total+element

 

     | }

 

sumOfNumbers: Int = 6

 

scala>

作为第一个变量传递给 foldLeft 的值0,是初始值(即在把函数用到第一个列表元素的时候 total=0)。而 (total,element) 代表了一个 Tuple2 二元组。

其实,Scala 已经提供了一个累加的方法,因此,上面可以写成:

scala> val sumOfNumbers=numbers.sum

 

sumOfNumbers: Int = 6

 

scala>

其他集合方法,可以参见 scaladoc API。这些方法组合起来(例如:numbers.reverse.filter……)使用会让代码更加简洁,不过这样会影响可读性。

最后,{ n => n + 10 } 可以简单地写成 (_ + 10),也就是说,n 是个匿名变量,叫什么都行,还不用声明。那么,下划线的地方表示需要用你列表中的每个元素来填补的空白。(与“_”的功能类似,Groovy 保留了关键字 it,Python 则使用的是 self)。

其实,用 (_ + 10) 来代替 { n => n + 10 },就是将 n=>n 换成 _,因为,n 不需要声明的内部匿名变量,有重复的嫌疑。这更说明 Scala 是何等简洁。

scala> val hightNumbers=numbers.map (_+10)

 

hightNumbers: List[Int] = List(11, 12, 13)

 

scala>

介绍了对整数集合的基本处理后,可以进入下一阶段,如何对复杂集合的变换,例如,使用上面定义的 Employee 类:

scala> val allEmployees=List(luke, anna, guest, yoda, thomas)

 

allEmployees: List[Employee] = List(Employee(Luke,30,LucasArt), Employee(Anna,30

 

,DevCode), Employee(guest,30,DevCode), Employee(Yoda,800,LucasArt), Employee(Tho

 

mas,41,DevCode))

 

scala>

对这个员工集合列表,可以应用匿名方法,用一个条件来过滤,查找 compay 为 DevCode 的员工:

scala>  val devCodeEmployees=allEmployees.filter {_.company=="DevCode"}

 

devCodeEmployees: List[Employee] = List(Employee(Anna,30,DevCode), Employee(gues

 

t,30,DevCode), Employee(Thomas,41,DevCode))

 

scala> val oldEmployees=allEmployees.filter(_.age>100).map(_.name)

 

oldEmployees: List[String] = List(Yoda)

 

scala>

假设 allEmployees 集合是使用 SQL 查询获得的,类似 SELECT * FROM employees WHERE company = ‘DevCode’ 。现在用 groupBy 函数按 company 分组,就会得到一个以 company 为键、Employee 为值的 Map

scala> val sortedEmployees=allEmployees.groupBy(_.company)

 

sortedEmployees: scala.collection.immutable.Map[String,List[Employee]] = Map(Dev

 

Code -> List(Employee(Anna,30,DevCode), Employee(guest,30,DevCode), Employee(Tho

 

mas,41,DevCode)), LucasArt -> List(Employee(Luke,30,LucasArt), Employee(Yoda,800

 

,LucasArt)))

 

scala>

假设计算某个公司 company 的雇员平均年龄。即 age 字段的累加,然后除以雇员数量。先计算一下 DevCode 公司:

scala> devCodeEmployees

 

res3: List[Employee] = List(Employee(Anna,30,DevCode), Employee(guest,30,DevCod

 

), Employee(Thomas,41,DevCode))

 

scala> val devCodeAges=devCodeEmployees.map(_.age)

 

devCodeAges: List[Int] = List(30, 30, 41)

 

scala> val devCodeAverageAge=devCodeAges.sum / devCodeAges.size

 

devCodeAverageAge: Int = 33

 

scala>

回到上面的公司分组,Map (key:String ->value:List[Employee]),如果想计算分组内员工的平均年龄,只有几行代码:

scala> val averageAgeByCompany = sortedEmployees.map{ case(key,value)=>

 

     | value(0).copy(name="average",age=(value.map(_.age).sum)/value.size)}

 

averageAgeByCompany: scala.collection.immutable.Iterable[Employee] = List(Employ

 

ee(average,33,DevCode), Employee(average,415,LucasArt))

 

scala>

这里的“case(key,value)”说明了Scala提供的模式匹配机制是多么强大。请参考Scala的文档来获取更多的信息。

到这里我们的任务就完成了。我们实现的是一个简单的Map-Reduce算法。由于每个公司雇员的归并是完全独立于其他公司,这个算法非常直观地实现了并行计算。

在后面的附录里给出了此算法的等价的实现,分为Java版本和Scala版本。

参考资料


附录


Map Reduce.Java

public class Employee {

 

    final String name;

    final Integer age;

    final String company;

 

    public Employee(String name, Integer age, String company) {

        this.name = name == null ? "guest" : name;

        this.age = age == null ? 30 : age;

        this.company = company == null ? "DevCode" : company;

    }

 

    public String getName() {

        return name;

    }

 

    public int getAge() {

        return age;

    }

 

    public String getCompany() {

        return company;

    }

 

    @Override

    public String toString() {

        return "Employee [name=" + name + ", age=" + age + ",

               company="

               + company + "]";

    }

}

 

class Builder {

    String name, company;

    Integer age;

 

    Builder(String name) {

        this.name = name;

 

    }

 

    Employee build() {

        return new Employee(name, age, company);

    }

 

    Builder age(Integer age) {

        this.age = age;

        return this;

    }

 

    Builder company(String company) {

        this.company = company;

        return this;

    }

}

 

import java.util.ArrayList;

import java.util.Collection;

import java.util.List;

import com.google.common.base.Function;

import com.google.common.collect.ImmutableListMultimap;

import com.google.common.collect.ImmutableSet;

import com.google.common.collect.Multimaps;

 

public class MapReduce {

 

    public static final void main(String[] args) {

        Employee guest = new Builder("Guest").build();

        Employee anna = new Builder("Anna").build();

        Employee thomas = new Builder("Thomas").age(41).build();

        Employee luke = new

            Builder("Luke").company("LucasArt").build();

        Employee yoda = new

            Builder("Yoda").age(800).company("LucasArt").build();

 

        Collection<Employee> employees = new ArrayList<Employee>();

        employees.add(guest);

        employees.add(anna);

        employees.add(thomas);

        employees.add(luke);

        employees.add(yoda);

 

        ImmutableListMultimap<String, Employee>

            personsGroupByCompany = Multimaps.index(employees, new Function<Employee,String>() {

 

                public String apply(Employee person) {

                   return person.getCompany();

                 }

 

              });

 

        ImmutableSet<String> companyNamesFromMap =

            personsGroupByCompany.keySet();

 

        List<Employee> averageAgeByCompany = new

            ArrayList<Employee>();

 

        for(String company: companyNamesFromMap) {

             List<Employee> employeesForThisCompany =

                personsGroupByCompany.get(company);

             int sum = 0;

             for(Employee employee: employeesForThisCompany) {

                 sum+= employee.getAge();

             }

             averageAgeByCompany.add(new

                Employee("average",sum/employeesForThisCompany.size(),company));

     }

     System.out.println("Result: "+averageAgeByCompany);

 

    }

}

MapReduce.scala:

case class Employee(name: String = "guest", age: Int = 30, company: String = "DevCode")

 

    object MapReduce {

        def main(args: Array[String]): Unit = {

 

        val guest = Employee()

        val anna = Employee("Anna")

        val thomas = Employee("Thomas", 41)

        val luke = Employee("Luke", company = "LucasArt")

        val yoda = luke.copy("Yoda", age = 800)

 

        val allEmployees = List(luke, anna, guest, yoda, thomas)

        val sortedEmployees = allEmployees.groupBy(_.company)

        val averageAgeByCompany = sortedEmployees.map { case (key, value) =>

            value(0).copy(name = "average", age = (value.map(_.age).sum) / value.size)

      }

        println("Result: "+averageAgeByCompany)

    }

}

关于作者

Thomas Alexandre是DevCode的高级咨询顾问,专注于Java和Scala软件开发。他热爱技术,热衷于分享知识,永远在寻求方法、采用新的开源软件和标准来实现更加有效的编程。在十四年的Java开发经验之外,过去几年他集中精力在新的编程语言和Web框架上,例如Groovy/Grails和Scala/Lift。Thomas从法国里尔大学获得了计算机科学博士学位,在卡耐基梅隆大学度过了两年的博士后研究生涯,研究方向是安全和电子商务。

Scala 基础入门【翻译】的更多相关文章

  1. Scala基础入门-4

    Scala学习——类 简单类和无参方法 class Counter { private var value = 0 // 必须初始化字段 def increment() { value += 1 } ...

  2. Scala基础入门-2

    简单类和无参方法 class Counter { private var value = 0 // 必须初始化字段 def increment() { value += 1 } // 方法默认公有 d ...

  3. Scala基础入门-1

    首先需要Scala开发环境的搭建,网上自己找教程. 声明常量与变量 val foo = 0 // 常量 var bar = 0 // 变量 在Scala中,更加鼓励使用val来进行声明,也就是推荐使用 ...

  4. Scala基础入门-3

    学习Scala——映射和元组 映射和和元组,也就是Maps和Tuples.Map这东西应该都挺明白的,就是键值对的集合.而元组,tuple,这东西并不是每个语言都有(Python中是有的,不过当时学的 ...

  5. Scala基础入门-代码碎片

    import scala.util.control._ import java.util.Date object Test { def main(args: Array[String]) { // v ...

  6. scala基础入门

    1.scala当中申明值和变量 scala当中的变量申明可以使用两种方式,第一种使用val来申明变量.第二种使用var来申明变量. 申明变量语法 val/var 变量名 [:变量类型] = 变量值 其 ...

  7. Scala快速入门 - 基础语法篇

    本篇文章首发于头条号Scala快速入门 - 基础语法篇,欢迎关注我的头条号和微信公众号"大数据技术和人工智能"(微信搜索bigdata_ai_tech)获取更多干货,也欢迎关注我的 ...

  8. Cloudera Manager、CDH零基础入门、线路指导 http://www.aboutyun.com/thread-9219-1-1.html (出处: about云开发)

    Cloudera Manager.CDH零基础入门.线路指导http://www.aboutyun.com/thread-9219-1-1.html(出处: about云开发) 问题导读:1.什么是c ...

  9. SQLAlchemy 教程 —— 基础入门篇

    SQLAlchemy 教程 -- 基础入门篇 一.课程简介 1.1 实验内容 本课程带领大家使用 SQLAlchemy 连接 MySQL 数据库,创建一个博客应用所需要的数据表,并介绍了使用 SQLA ...

随机推荐

  1. 《HTML5秘籍》学习总结--2016年7月24日

    前段时间因为工作中看到同事使用了一个type为date的<input>元素,直接就形成了一个日期选择的表单控件,当时觉得很神奇,以为是什么插件,就问了同事是怎么做出来的,同事告诉我这是HT ...

  2. 1296: [SCOI2009]粉刷匠

    Description windy有 N 条木板需要被粉刷. 每条木板被分为 M 个格子. 每个格子要被刷成红色或蓝色. windy每次粉刷,只能选择一条木板上一段连续的格子,然后涂上一种颜色. 每个 ...

  3. 10个经典的C语言面试基础算法及代码

    10个经典的C语言面试基础算法及代码作者:码农网 – 小峰 原文地址:http://www.codeceo.com/article/10-c-interview-algorithm.html 算法是一 ...

  4. SpringMVC中使用DWR

    SpringMVC中使用DWR重点在其配置当中. 1.  web.xml文件的配置 在DispatcherServlet中增加dwr的拦截来取代DwrServlet. 更改配置如下: <serv ...

  5. android listview用adapter.notifyDataSetChanged()无法刷新每项的图标

    http://blog.csdn.net/caizhegnhao/article/details/41318575 今天在开发中遇到一个很奇怪的listview的问题. 这个问题情景是我的应用需要做一 ...

  6. 今天在Mac机器上使用了Flex Builder编辑了一个源代码文件,保存后使用vim命令去打开时发现系统自动在每一行的结尾添加了^M符号,其实^M在Linux/Unix中是非常常见的,也就是我们在Win中见过的/r回车符号。由于编辑软件的编码问题,某些IDE的编辑器在编辑完文件之后会自动加上这个^M符号。看起来对我们的源代码没有任何影响,其实并不然,当我们把源代码文件Check In到svn之类

    今天在Mac机器上使用了Flex Builder编辑了一个源代码文件,保存后使用vim命令去打开时发现系统自动在每一行的结尾添加了^M符号,其实^M在Linux/Unix中是非常常见的,也就是我们在W ...

  7. MacOS 10.8更新SVN到1.8.4的问题和解决方法

    因为要导入以前的项目,但以前项目里内含有的svn信息,所以xcode默认安装的svn1.6是无法删除svn信息,据说需要svn1.7才能清除掉svn信息.所以必须要升级svn的版本. 我在网上找了各种 ...

  8. 更改vsts源代码绑定服务器地址方法

    第一步找到更改源代码管理 第二步首先选中第一个,滚动条拉至最后一项,点击最后一项(全选). 第三步点击绑定,vsts会自动签出所有项目,而后签入就可以更改成功了.

  9. 一致性Hash算法在Redis分布式中的使用

    由于redis是单点,但是项目中不可避免的会使用多台Redis缓存服务器,那么怎么把缓存的Key均匀的映射到多台Redis服务器上,且随着缓存服务器的增加或减少时做到最小化的减少缓存Key的命中率呢? ...

  10. JavaScript原生DOM操作API总结

    最近面试的时候被这个问题给卡了,所以抽时间好好复习一下. 原文:http://www.cnblogs.com/liuxianan/p/javascript-dom-api.html 几种对象 Node ...