{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## 无参数方法\n", "定义无参数方法可以省略括号。**一旦定义无参数方法时省略了括号,那么调用这些方法时必须省略括号。**假如在定义无参数方法时添加了空括号,那么调用方可以选择省略或保留括号。\n", "\n", "Scala社区已经养成了这样一个习惯:定义那些无副作用的无参方法时省略括号。而定义具有副作用的方法时则添加括号,这样便能提醒读者某些对象可能会发送变化,需要额外小心。\n", "\n", "加入运行scala或scalac时添加了`-Xlint`选项,那么在定义那些会产生副作用(例如方法中有IO操作)的无参方法时,省略括号将会出现一条警告信息。" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "24\n", "24\n", "24\n", "24" ] }, { "data": { "text/plain": [ "defined \u001b[32mfunction \u001b[36misEven\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// 合理考虑是否使用括号有助于构建更具有表现力的方法调用链\n", "def isEven(n: Int) = (n % 2) == 0\n", "\n", "List(1, 2, 3, 4).filter((i: Int) => isEven(i)).foreach((i: Int) => print(i))\n", "println\n", "List(1, 2, 3, 4).filter(i => isEven(i)).foreach(i => print(i))\n", "println\n", "List(1, 2, 3, 4).filter(isEven).foreach(print)\n", "println\n", "List(1, 2, 3, 4) filter isEven foreach print" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Scala的for推导式\n", "推导式起源于函数式编程,它表示:遍历一个或多个集合,对集合中的元素进行“推导”,并从中计算出新的事物,新推导出的事物往往是另一个集合。" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Yorkshire Terrier\n", "Scottish Terrier\n" ] }, { "data": { "text/plain": [ "\u001b[36mdogBreeds\u001b[0m: \u001b[32mList\u001b[0m[\u001b[32mString\u001b[0m] = \u001b[33mList\u001b[0m(\n", " \u001b[32m\"Doberman\"\u001b[0m,\n", " \u001b[32m\"Yorkshire Terrier\"\u001b[0m,\n", " \u001b[32m\"Dachshund\"\u001b[0m,\n", " \u001b[32m\"Scottish Terrier\"\u001b[0m,\n", " \u001b[32m\"Great Dane\"\u001b[0m,\n", " \u001b[32m\"Portuguese Water Dog\"\u001b[0m\n", ")" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "val dogBreeds = List(\"Doberman\", \"Yorkshire Terrier\", \"Dachshund\",\n", " \"Scottish Terrier\", \"Great Dane\", \"Portuguese Water Dog\")\n", "\n", "// 使用if守护式(guard)\n", "for(breed <- dogBreeds \n", " if breed.contains(\"Terrier\"))\n", " println(breed)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Scottish Terrier\n" ] }, { "data": { "text/plain": [] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "for(breed <- dogBreeds\n", " if breed.contains(\"Terrier\")\n", " if !breed.startsWith(\"Yorkshire\"))\n", " println(breed)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "List(Scottish Terrier)\n" ] }, { "data": { "text/plain": [ "\u001b[36mfilteredBreeds\u001b[0m: \u001b[32mList\u001b[0m[\u001b[32mString\u001b[0m] = \u001b[33mList\u001b[0m(\u001b[32m\"Scottish Terrier\"\u001b[0m)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// 使用yield关键字能在for表达式中生成新的集合\n", "val filteredBreeds = for {\n", " breed <- dogBreeds \n", " if breed.contains(\"Terrier\") && !breed.startsWith(\"Yorkshire\")\n", "} yield breed\n", "\n", "println(filteredBreeds)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mdogBreeds2\u001b[0m: \u001b[32mList\u001b[0m[\u001b[32mOption\u001b[0m[\u001b[32mString\u001b[0m]] = \u001b[33mList\u001b[0m(\n", " Some(Doberman),\n", " None,\n", " Some(Yorkshire Terrier),\n", " Some(Dachshund),\n", " None,\n", " Some(Scottish Terrier),\n", " Some(Great Dane),\n", " None,\n", " Some(Portuguese Water Dog)\n", ")" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "val dogBreeds2 = List(Some(\"Doberman\"), None, Some(\"Yorkshire Terrier\"), Some(\"Dachshund\"), None,\n", " Some(\"Scottish Terrier\"), Some(\"Great Dane\"), None, Some(\"Portuguese Water Dog\"))" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "DOBERMAN\n", "YORKSHIRE TERRIER\n", "DACHSHUND\n", "SCOTTISH TERRIER\n", "GREAT DANE\n", "PORTUGUESE WATER DOG\n" ] }, { "data": { "text/plain": [] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "for {\n", " Some(breed) <- dogBreeds2 \n", " upcasedBreed = breed.toUpperCase()\n", "} println(upcasedBreed)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "上式中,在for表达式中定义upcasedBreed,然后在后面的表达式中使用该值。\n", "\n", "由于dogBreeds2列表中包含都是Option对象,在for推导式中使用模式匹配提取出Some类型,而不处理None元素。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**在大部分语言中,可以使用break或continue跳出循环。而Scala并未提供该功能。编写地道的Scala代码,可以使用条件表达式或者使用递归判断循环是否应该继续。**在一开始对集合进行过滤以消除循环中的复杂条件就更好了,不过为了满足break的需求,Scala提供`scala.util.control.Breaks`对象来实现break功能。" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[32mimport \u001b[36mutil.control.Breaks._\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// break/continue的例子\n", "import util.control.Breaks._" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "==== BREAK EXAMPLE ====\n", "1\n", "2\n", "3\n", "4\n", "5\n", "==== CONTINUE EXAMPLE ====\n", "Found 9 p's in the string.\n" ] }, { "data": { "text/plain": [ "\u001b[36msearchMe\u001b[0m: \u001b[32mString\u001b[0m = \u001b[32m\"peter piper picked a peck of pickled peppers\"\u001b[0m\n", "\u001b[36mnumPs\u001b[0m: \u001b[32mInt\u001b[0m = \u001b[32m9\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "println(\"==== BREAK EXAMPLE ====\")\n", "breakable {\n", " for (i <- 1 to 10) {\n", " println(i)\n", " if (i > 4) break // break out of the for loop\n", " }\n", "}\n", "\n", "println(\"==== CONTINUE EXAMPLE ====\")\n", "val searchMe = \"peter piper picked a peck of pickled peppers\"\n", "var numPs = 0\n", "for (i <- 0 until searchMe.length) {\n", " breakable {\n", " if (searchMe.charAt(i) != 'p') {\n", " break // break out of the 'breakable', continue the outside loop\n", " } else {\n", " numPs += 1\n", " }\n", " }\n", "}\n", "println(\"Found \" + numPs + \" p's in the string.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 异常处理\n", "Scala推崇通过使用函数式结构和强类型以减少对异常和异常处理的依赖的编码范式。\n", "\n", "Scala将异常处理作为另一类模式匹配来处理。" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[32mimport \u001b[36mscala.io.Source\u001b[0m\n", "\u001b[32mimport \u001b[36mscala.util.control.NonFatal\u001b[0m\n", "defined \u001b[32mfunction \u001b[36mcountLines\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// 资源管理场景中处理异常\n", "// 统计文件行数\n", "import scala.io.Source\n", "import scala.util.control.NonFatal\n", "\n", "def countLines(fileName: String) = {\n", " var source: Option[Source] = None\n", " try {\n", " source = Some(Source.fromFile(fileName))\n", " val size = source.get.getLines.size\n", " println(s\"file $fileName has $size lines\")\n", " } catch {\n", " case NonFatal(ex) => println(s\"Non fatal exception! $ex\")\n", " } finally {\n", " for (s <- source) {\n", " println(s\"Closing $fileName ...\")\n", " s.close\n", " }\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "file fileBulkReaderTestFile.txt has 3 lines\n", "Closing fileBulkReaderTestFile.txt ...\n", "Non fatal exception! java.io.FileNotFoundException: nonexistentFile.txt (系统找不到指定的文件。)\n" ] }, { "data": { "text/plain": [ "\u001b[36mfiles\u001b[0m: \u001b[32mArray\u001b[0m[\u001b[32mString\u001b[0m] = \u001b[33mArray\u001b[0m(\u001b[32m\"fileBulkReaderTestFile.txt\"\u001b[0m, \u001b[32m\"nonexistentFile.txt\"\u001b[0m)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "val files = Array(\"fileBulkReaderTestFile.txt\", \"nonexistentFile.txt\")\n", "files foreach (fileName => countLines(fileName))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 将变量source声明为Option类型,在finally子句中能够分辨出source对象是否是真正实例,通过for推导式从Some类型中提取Source实例\n", "- 假如文件不存在,source.fromFile方法将抛出java.io.FileNotFoundException类型的异常,否则将方法返回值封装在Some对象中,然后调用source变量的get方法得到Source实例\n", "- catch子句中,使用模式匹配来捕捉所希望捕获的异常" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 惰性求值\n", "当希望以延迟的方式初始化某值,并且表达式不会被重复计算,则需要使用惰性求值。\n", "\n", "常见场景:\n", "- 表达式执行代价昂贵(比如打开数据库连接),因此我们希望能推迟该操作,知道我们需要表达式结果值时才使用它\n", "- 为了缩短模块启动的时间,可以将当前不需要的某些工作推迟执行\n", "- 为了确保对象中其他的字段的初始化过程能优先执行,需要将某些字段惰性化" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mobject \u001b[36mExpensiveResource\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "object ExpensiveResource {\n", " lazy val resource: Int = init()\n", " def init(): Int = {\n", " Thread.sleep(3000)\n", " 0\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mres14\u001b[0m: \u001b[32mInt\u001b[0m = \u001b[32m0\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ExpensiveResource.resource " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "惰性求值与方法调用的区别:\n", "- 方法调用每次都会执行\n", "- 惰性求值首次使用该值时,初始化代码体,才会执行一次\n", "- lazy关键字只修饰val量" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 枚举\n", "Scala在标准库中专门定义Enumeration类来实现枚举,并未提供任何特殊语法支持枚举。\n", "\n", "**值得一提的是,Scala常常使用case class来代替枚举值,其有两点好处:**\n", "- case类允许添加方法和字段,我们能够对枚举值应用模式匹配,为用户提供更好的灵活性\n", "- case类适用于包含未知枚举值的场景。只要有需要,用户代码可以将更多的case类添加到基本集合中" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mobject \u001b[36mBreed\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "object Breed extends Enumeration {\n", " type Breed = Value\n", " val doberman = Value(\"Doberman Pinscher\")\n", " val yorkie = Value(\"Yorkshire Terrier\")\n", " val scottie = Value(\"Scottish Terrier\")\n", " val dane = Value(\"Great Dane\")\n", " val portie = Value(\"Portuguese Water Dog\")\n", "}" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "ID\tBreed\n", "0\tDoberman Pinscher\n", "1\tYorkshire Terrier\n", "2\tScottish Terrier\n", "3\tGreat Dane\n", "4\tPortuguese Water Dog\n", "\n", "Just Terriers:\n", "Yorkshire Terrier\n", "Scottish Terrier\n", "\n", "Terriers Again:\n", "Yorkshire Terrier\n", "Scottish Terrier\n" ] }, { "data": { "text/plain": [ "\u001b[32mimport \u001b[36mBreed._\u001b[0m\n", "defined \u001b[32mfunction \u001b[36misTerrier\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import Breed._\n", "\n", "println(\"ID\\tBreed\")\n", "for (breed <- Breed.values) println(s\"${breed.id}\\t$breed\")\n", "\n", "println(\"\\nJust Terriers:\")\n", "Breed.values filter (_.toString.endsWith(\"Terrier\")) foreach println\n", "\n", "def isTerrier(b: Breed) = b.toString.endsWith(\"Terrier\")\n", "\n", "println(\"\\nTerriers Again:\")\n", "Breed.values filter isTerrier foreach println" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 枚举类型中包含了许多Value类型值,每一个枚举值都调用了接收单一字符串参数的Value方法,调用Value.toString方法将返回该字符串。\n", "- Breed类型是一个别名(Value类型的别名)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scala提供了很多个Value的重载方法:\n", "- 接受单一字符串的Value方法\n", "- 无参Value方法,将对象名作为输入字符串\n", "- 输入参数是一个ID值,该方法在使用默认字符串(变量名)的同时会将我们显式指定的整数值作为ID\n", "- 同时接收整数和字符串输入的Value方法" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mobject \u001b[36mWeekDay\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "object WeekDay extends Enumeration {\n", " type WeekDay = Value\n", " val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value\n", "}" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Mon\n", "Tue\n", "Wed\n", "Thu\n", "Fri\n" ] }, { "data": { "text/plain": [ "\u001b[32mimport \u001b[36mWeekDay._\u001b[0m\n", "defined \u001b[32mfunction \u001b[36misWorkingDay\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import WeekDay._\n", "\n", "def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun)\n", "\n", "WeekDay.values filter isWorkingDay foreach println" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Trait:接口和混入\n", "Scala从函数式编程思想中汲取trait,使用它来代替Java的接口。\n", "\n", "在trait中可以声明或定义类型,并且trait允许真正意义上的组合行为(“混入”模式)。" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "ServiceImportance: Doing important work! 1\n", "Result: 2\n", "ServiceImportance: Doing important work! 2\n", "Result: 3\n", "ServiceImportance: Doing important work! 3\n", "Result: 4\n" ] }, { "data": { "text/plain": [ "defined \u001b[32mclass \u001b[36mServiceImportance\u001b[0m\n", "\u001b[36mservice1\u001b[0m: \u001b[32m$user\u001b[0m.\u001b[32mServiceImportance\u001b[0m = cmd19$$user$ServiceImportance@19e3ffe" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// 在应用中混入日志\n", "class ServiceImportance(name: String) {\n", " def work(i: Int): Int = {\n", " println(s\"ServiceImportance: Doing important work! $i\")\n", " i + 1\n", " }\n", "}\n", "\n", "val service1 = new ServiceImportance(\"uno\")\n", "(1 to 3) foreach (i => println(s\"Result: ${service1.work(i)}\"))" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mtrait \u001b[36mLogging\u001b[0m\n", "defined \u001b[32mtrait \u001b[36mStdoutLogging\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// 在服务中混入标准日志库,使用println输出日志\n", "// 定义日志抽象\n", "trait Logging {\n", " def info(message: String): Unit\n", " def warning(message: String): Unit\n", " def error(message: String): Unit\n", "}\n", "\n", "// 定义日志信息输出到标准输出\n", "trait StdoutLogging extends Logging {\n", " def info(message: String) = println(s\"INFO: $message\")\n", " def warning(message: String) = println(s\"WARNING: $message\")\n", " def error(message: String) = println(s\"ERROR: $message\")\n", "}" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mservice2\u001b[0m: \u001b[32mServiceImportance\u001b[0m with \u001b[32mStdoutLogging\u001b[0m = cmd21$$user$$anonfun$1$$anon$1@110bafd" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "val service2 = new ServiceImportance(\"dos\") with StdoutLogging {\n", " override def work(i: Int): Int = {\n", " info(s\"Starting work: i = $i\")\n", " val result = super.work(i)\n", " info(s\"Ending work: i = $i, result = $result\")\n", " result\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "INFO: Starting work: i = 1\n", "ServiceImportance: Doing important work! 1\n", "INFO: Ending work: i = 1, result = 2\n", "Result: 2\n", "INFO: Starting work: i = 2\n", "ServiceImportance: Doing important work! 2\n", "INFO: Ending work: i = 2, result = 3\n", "Result: 3\n", "INFO: Starting work: i = 3\n", "ServiceImportance: Doing important work! 3\n", "INFO: Ending work: i = 3, result = 4\n", "Result: 4\n" ] }, { "data": { "text/plain": [] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "(1 to 3) foreach (i => println(s\"Result: ${service2.work(i)}\"))" ] } ], "metadata": { "kernelspec": { "display_name": "Scala 2.11", "language": "scala211", "name": "scala211" }, "language_info": { "codemirror_mode": "text/x-scala", "file_extension": ".scala", "mimetype": "text/x-scala", "name": "scala211", "pygments_lexer": "scala", "version": "2.11.6" } }, "nbformat": 4, "nbformat_minor": 0 }