编程实践 | Scala亮瞎Java的眼(一)

这是我在11月15日成都OpenParty分享的一个题目,确有标题党的嫌疑。Scala自然不是无所不能,Java也没有这么差劲,我只希望给Java程序员提供另外一条可能的选择。在Java 8后,我对Java的怨念已经没有那么强烈了,然而,Scala的优势仍然存在。

比较Java 8,我重点讲解了Scala的如下优势:

  • 简洁代码
  • 支持OO与FP
  • 高阶函数
  • 丰富的集合操作
  • Stream支持
  • 并发支持

简洁代码

Scala提供的脚本特性以及将函数作为一等公民的方式,使得它可以去掉不少在Java中显得冗余的代码,例如不必要的类定义,不必要的main函数声明。Scala提供的类型推断机制,也使得代码精简成为可能。Scala还有一个巧妙的设计,就是允许在定义类的同时定义该类的主构造函数。在大多数情况下,可以避免我们声明不必要的构造函数。

Scala还提供了一些非常有用的语法糖,如伴生对象,样例类,既简化了接口,也简化了我们需要书写的代码。例如如下代码:

case class Person(name: String, age: Int) val l = List(Person("Jack", 28), Person("Bruce", 30))

这里的List和Person都提供了伴生对象,避免再写冗余的new。这种方式对于DSL支持也是有帮助的。Person是一个样例类,虽然只有这么一行代码,蕴含的含义却非常丰富——它为Person提供了属性,属性对应的访问器,equals和hashcode方法,伴生对象,以及对模式匹配的支持。在Scala 2.11版本中,还突破了样例类属性个数的约束。由于样例类是不变的,也能实现trait,因而通常作为message而被广泛应用到系统中。例如在AKKA中,actor之间传递的消息都应该尽量定义为样例类。

支持OO与FP

将面向对象与函数式编程有机地结合,本身就是Martin Odersky以及Scala的目标。这二者的是非,我从来不予以置评。个人认为应针对不同场景,选择不同的设计思想。基于这样的思想,Scala成为我的所爱,也就是顺其自然的事情了。

演讲中,我主要提及了纯函数的定义,并介绍了应该如何设计没有副作用的纯函数。纯函数针对给定的输入,总是返回相同的输出,且没有任何副作用,就使得纯函数更容易推论(这意味着它更容易测试),更容易组合。从某种角度来讲,这样的设计指导思想与OO阵营中的CQS原则非常一致,只是重用的粒度不一样罢了。

我给出了Functional Programming in Scala一书中的例子。如下代码中的declareWinner函数并非纯函数:

object Game { def printWinner(p: Player): Unit = println(p.name + " is the winner!") def declareWinner(p1: Player, p2: Player): Unit = if (p1.score > p2.score) printWinner(p1) else printWinner(p2) }

这里的printWinner要向控制台输出字符串,从而产生了副作用。(简单的判断标准是看函数的返回值是否为Unit)我们需要分离出专门返回winner的函数:

def winner(p1: Player, p2: Player): Player = if (p1.score > p2.score) p1 else p2

消除了副作用,函数的职责变得单一,我们就很容易对函数进行组合或重用了。除了可以打印winner之外,例如我们可以像下面的代码那样获得List中最终的获胜者:

val players = List(Player("Sue", 7), Player("Bob", 8), Player("Joe", 4))

val finalWinner = players.reduceLeft(winner)

函数的抽象有时候需要脑洞大开,需要敏锐地去发现变化点与不变点,然后提炼出函数。例如,当我们定义了这样的List之后,比较sum与product的异同:

sealed trait MyList[+T] case object Nil extends MyList[Nothing] case class Cons[+T](h: T, t: MyList[T]) extends MyList[T] object MyList { def sum(ints: MyList[Int]):Int = ints match { case Nil => 0 case Cons(h, t) => h + sum(t) } def product(ds: MyList[Double]):Double = ds match { case Nil => 1.0 case Cons(h, t) => h * product(t) } def apply[T](xs: T*):MyList[T] = if (xs.isEmpty) Nil else Cons(xs.head, apply(xs.tail: _*)) }

sum与product的相同之处都是针对List的元素进行运算,运算规律是计算两个元素,将结果与第三个元素进行计算,然后依次类推。这就是在函数式领域中非常常见的折叠(fold)计算:

def foldRight[A, B](l: MyList[A], z: B)(f: (A, B) => B):B = l match { case Nil => z case Cons(x, xs) => f(x, foldRight(xs, z)(f)) }

在引入了foldRight函数后,sum和product就可以重用foldRight了:

def sum(ints: MyList[Int]):Int = foldRight(ints, 0)(_ + _) def product(ds: MyList[Double]):Double = foldRight(ds, 0.0)(_ * _)

在函数式编程的世界里,事实上大多数数据操作都可以抽象为filter,map,fold以及flatten几个操作。查看Scala的集合库,可以验证这个观点。虽然Scala集合提供了非常丰富的接口,但其实现基本上没有超出这四个操作的范围。

原文发布于微信公众号 - 逸言(YiYan_OneWord)

原文发表时间:2014-11-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏小樱的经验随笔

BZOJ 3670: [Noi2014]动物园【KMP变形 】

3670: [Noi2014]动物园 Time Limit: 10 Sec  Memory Limit: 512 MB Submit: 2738  Solve...

3177
来自专栏aCloudDeveloper

string 之 strchr函数 和 strstr函数(BF算法和KMP算法的应用)

Author: bakari  Date: 2012/8/9 继上篇。。。。。 下面是我写的代码与源码作的一些比较,均已严格测试通过,分别以“string 之”...

1979
来自专栏怀英的自我修炼

Java漫谈6

今天这篇想聊聊数组。 在聊数组之前先聊个别的,如果想在Java中实现一个 数字-月份 转换,那我该怎么做呢?就比如数字1代表了一月份,数字2代表了二月份…数字...

35011
来自专栏软件开发

JavaSE学习总结(三)——Java语言编程练习、格式化字符与常量

一、变量、常量、字面量 package com.zhangguo.chapter2_3; /** * 1、银行利率为5%,问存款100美元5年的收益细节? ...

1958
来自专栏鹅厂优文

深入浅出Lua虚拟机

本文标题是”深入浅出 Lua 虚拟机”,其实重点在浅出这两字上。毕竟作者的技术水平有限。但是听说名字要起的屌一点文章才有人看,故而得名。

75712
来自专栏灯塔大数据

技术 | Python从零开始系列连载(十八)

但是有一种情况是递归时不断调用自身,达到不了最简单的情况(例如俄罗斯套娃一层层打开到最内层的),所以一直找不到递归的出口。

823
来自专栏工科狗和生物喵

【计算机本科补全计划】王道单科--栈的实现以及一些性质

正文之前 这几天深受《C++ Primer》困扰,昨晚看拷贝构造函数以及析构函数那一块真是看到快哭了。所以今天找点别的看看,缓解下因为看不懂Primer的书籍而...

2907
来自专栏java初学

top K 问题

34716
来自专栏写代码的海盗

分水岭 golang入坑系列

第三式开篇语有些负面, 所以这里就不贴了。有兴趣的自己可以去看看 https://andy-zhangtao.gitbooks.io/golang/conten...

3764
来自专栏专知

【专知-关关的刷题日记16】Leetcode 88. Merge Sorted Array

题目 Given two sorted integer arrays nums1 and nums2, merge nums2 into nums1 as on...

34410

扫描关注云+社区