热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Scala学习指南:从零开始掌握基础

本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。
一、Scala解释器的使用 REPL:Read(取值)-> Evaluation(求值)-> Print(打印)-> Loop(循环) scala解释器也被称为REPL,会快速编译scala代码为字节码,然后交给JVM来执行。 计算表达式:在scala>命令行内,键入scala代码,解释器会直接返回结果。 如果你没有指定变量来存放这个值,那么值默认的名称为res,而且会 显示结果的数据类型,比如Int、Double、String等等。 例如,输入1 + 1,会看到res0: Int = 2 内置变量:在后面可以继续使用res这个变量,以及它存放的值。 例如,"Hi, " + res0,返回res2: String = Hi, 2 自动补全:在scala>命令行内,可以使用Tab键进行自动补全。 二、声明变量 声明val常量:可以声明val常量来存放表达式的计算结果。 例如,val result = 1 + 1 但是常量声明后,是无法改变它的值的,否则会返回error: reassignment to val的错误信息。 声明var变量:如果要声明值可以改变的引用,可以使用var变量。 例如,var myresult = 1,myresult = 2 但是在Scala程序中,通常建议使用val,也就是常量。 因为在Spark的大型复杂系统中,需要大量的网络传输数据, 如果使用var,值可能被错误的更改,所以建议多使用val。 三、数据类型与操作符 1》基本数据类型: Byte、 Char、 Short、 Int、 Long、 Float、 Double、 Boolean。 Scala的数据类型统一都是类。 Scala自己会负责基本数据类型和引用类型的转换操作。 使用以上类型, 直接就可以调用大量的函数, 例如, 1.toString(), 1.to(10)。 类型的加强版类型: Scala使用很多加强类给数据类型增加了上百种增强的功能或函数。 ·例如, String类通过StringOps类增强了大量的函数, "Hello".intersect(" World")。 ·例如, Scala还提供了RichInt、 RichDouble、 RichChar等类型, RichInt就提供了to函数, 1.to(10), 此处Int先隐式转换为RichInt,然后再调用其to函数。 2》基本操作符: Scala的算术操作符与Java的算术操作符也没有什么区别, 比如+、 -、 *、 /、 %等, 以及&、 |、 ^、 >>、 <<等。 但是, 在Scala中, 这些操作符其实是数据类型的函数, 比如1 + 1, 可以写做1.+(1) 例如, 1.to(10), 又可以写做1 to 10 注:Scala中没有提供++、--操作符, 我们只能使用+-, 比如counter = 1counter++是错误的, 必须写做counter += 1 3》除了方法之外,Scala还提供函数 数学函数:sqrt() pow() min() 引入特定包时使用import 包名._; import scala.math._ ,_是通配符,类似Java中的* 四、流程控制结构 1、if表达式的定义: 在Scala中, if表达式是有值的, 就是if或者else中最后一行语句返回的值。 例如, val age = 30; if (age > 18) 1 else 0 可以将if表达式赋予一个变量, 例如, val isAdult = if (age > 18) 1 else 0 另外一种写法, var isAdult = -1; if(age > 18) isAdult = 1 else isAdult = 0, 但是通常使用上一种写法 2、if表达式的类型推断: 由于if表达式是有值的, 而if和else子句的值类型可能不同, 此时if表达式的值是什么类型呢? Scala会自动进行推断, 取两个类型的公共父类型Any。 例如, if(age > 18) 1 else 0, 表达式的类型是Int, 因为1和0都是Int 例如, if(age > 18) "adult" else 0, 此时if和else的值分别是String和Int, 则表达式的值是Any, Any是String和Int的公共父类型。 如果if后面没有跟else, 则默认else的值是Unit, 也用()表示, 类似于Java中的void或者null。 例如, val age = 12; if(age > 18) "adult"。 此时就相当于if(age > 18) "adult" else ()。 3、将if语句放在多行中: 默认情况下, REPL只能解释一行语句, 但是if表达式通常需要放在多行。 可以使用{}的方式, 比如以下方式, 或者使用:paste和ctrl+D的方式。 if(age > 18) { "adult" } else if(age > 12) "teenager" else "children" 注:默认情况下, Scala不需要语句终结符, 默认将每一行作为一个语句 4、一行放多条语句: 如果一行要放多条语句, 则必须使用语句终结符 例如, 使用分号作为语句终结符, var a, b, c = 0; if(a <10) { b = b + 1;c = c + 1 } 通常来说, 对于多行语句, 还是会使用花括号的方式 var a, b, c = 0;if(a <10) { b = b + 1;c = c + 1} 5、块表达式: 块表达式, 指的就是{}中的值, 其中可以包含多条语句, 最后一个语句的值就 是块表达式的返回值。 例如, var d = if(a <10) { b = b + 1; c + 1 } 6、输入和输出 print和println: print打印时不会加换行符, 而println打印时会加一个换行符。 printf可以用于进行格式化,例如, printf("Hi, my name is %s, I'm %d years old .", "Leo", 30) readLine: readLine允许我们从控制台读取用户输入的数据, 类似于Java中的System.in和Scanner的作用。 综合案例:使用paste和ctrl+D val name = readLine("Welcome to Game House. Please tell me your name: ") print("Thanks. Then please tell me your age: ") val age = readInt() if(age > 18) { printf("Hi, %s, you are %d years old, so you are legel to come here!", name, age) } else { printf("Sorry, boy, %s, you are only %d years old. you are illegal to come here!", name, age) } 五、循环 1、Scala拥有与Java相同的while和do-while循环 但没有与for(初始化变量;判断条件;更新变量)循环直接对应的对构。 Scala中的for:for(i<-表达式),让变量i遍历<-右边表达式的所有值。 注意: 1、在for循环的变量之前并没有val或var的指定,该变量的类型是集合的元素类型。 2、循环变量的作用域一直持续到循环结束 3、to 和 until,两者得到都是集合,区别如下: scala> 1 to 10 (包含10) res8: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) scala> 1 until 10 (不包含10) res10: scala.collection.immutable.Range = Range(1, 2, 3, 4, 5, 6, 7, 8, 9) 4、循环的几种遍历方式 一、直接遍历------遍历字符串
二、求和---------1到8的和
三、以 变量<-表达式 的形式提供多个生成器,用分号将它们隔开(嵌套循环) for(i<- 1 to 9;j<- 1 to i){if(i==j) println(j+"*"+i+"="+j*i)else print(j+"*"+i+"="+j*i+"\t")}

四、在循环中使用变量 for(i<- 1 to 6;tem=2*i-1;j<- 1 to tem){ print("*");if(j==tem) {println()}}
五、守卫式,即在for循环中添加过滤条件if语句 for(i<- 1 to 3;j<- 1 to 3 if i!=j) print((10*i+j)+" ")
六、推导式 如果for循环的循环体以yield开始,则该循环会构造出一个集合,每次迭代生成集合中的一个值。
六、函数 1、函数的分类 单行函数:def sayHello(name: String) = print("Hello, " + name) 多行函数:如果函数体中有多行代码, 则可以使用代码块的方式包裹多行代码, 代码块中最后一行 的返回值就是整个函数的返回值。 与Java中不同, 不能使用return返回值。 比如如下的函数, 实现累加的功能: def sum(n: Int) :Int= { var sum = 0; for(i <- 1 to n) sum += i sum}
2、函数的定义与调用 在Scala中定义函数时, 需要定义函数的函数名、 参数、 函数体。 def sayHello(name: String, age: Int) = { if (age > 18) { printf("hi %s, you are a big boy\n", name); age } else { printf("hi %s, you are a little boy\n", name); age}} 调用:sayHello("leo", 30) 注:Scala要求必须给出所有参数的类型, 但是不一定给出函数返回值的类型。 只要右侧的函数体中不包含递归的语句, Scala就可以自己根据右侧的表达式推断出返回类型。 3、递归函数与返回类型 如果在函数体内递归调用函数自身, 则必须给出函数的返回类型。 例如, 实现经典的斐波那契数列: def feibo(n:Int):Int={ if(n<=2) 1 else feibo(n-1)+feibo(n-2)} 例如如下阶乘:
4、参数 1》默认参数 在Scala中, 有时我们调用某些函数时, 不希望给出参数的具体值, 而希望使用参数自身默认的值, 此时就在定义函数时使用默认参数。 def sayHello(firstName: String, middleName: String = "William", lastName: String = "Croft") = firstName + " " + middleName + " " + lastName 如果给出的参数不够, 则会从左往右依次应用参数。 Java与Scala实现默认参数的区别如下: ----------------------------------------------------------------------------------- Java: public void sayHello(String name, int age) { if(name == null) { name = "defaultName" } if(age == 0) { age = 18 }} sayHello(null, 0) ----------------------------------------------------------------------------------- Scala: def sayHello(name: String, age: Int = 20) { print("Hello, " + name + ", your age is " + age)} sayHello("leo") 2》带名参数 在调用函数时, 也可以不按照函数定义的参数顺序来传递参数, 而是使用带名参数的方式来 传递。如:sayHello(firstName = "Mick", lastName = "Nina", middleName = "Jack") 还可以混合使用未命名参数和带名参数, 但是未命名参数必须排在带名参数前面。如下: 正确:sayHello("Mick", lastName = "Nina", middleName = "Jack") 错误:sayHello("Mick", firstName = "Nina", middleName = "Jack") 3》使用序列调用变长参数 在如果要将一个已有的序列直接调用变长参数函数, 是不对的。 比如val s = sum(1 to 5)。 此时需要使用Scala特殊的语法将参数定义为序列, 让Scala解释器能够识别。 这种语法非常有用!Spark的源码中大量地使用。 例如:val s = sum(1 to 5 : _*) 通过:_*转换成参数序列 案例: 使用递归函数实现累加,如下: def sum2(nums: Int*): Int = { if (nums.length == 0) 0 else nums.head + sum2(nums.tail: _*) } 调用:sum2(1,2,3,4,5) 或是 sum2(1 to 5 :_*) 注:1、定义nums为一个变长参数,定义函数时用*,调用函数需要表示一个参数序列时用:_* 2、head 表示集合中的第一个元素,tail 表示集合中除了第一个元素外的其他元 七、过程 定义:在Scala中, 定义函数时, 如果函数体直接包裹在了花括号里面, 而没有使用=连接, 则函数的返回值类型就是Unit, 这样的函数就被称之为过程。过程通常用于不需要返回值的函数。 过程还有一种写法, 就是将函数的返回值类型定义为Unit。比较如下: def sayHello(name: String) = "Hello, " + name def sayHello(name: String) { print("Hello, " + name); "Hello, " + name } def sayHello(name: String): Unit = "Hello, " + name 八、lazy值 在Scala中, 提供了lazy值的特性, 也就是说, 如果将一个变量声明为lazy, 则只有在第一次使用 该变量时, 变量对应的表达式才会发生计算。这种特性对于特别耗时的计算操作特别有用, 比如打开文件进行IO, 进行网络IO等。 1、import scala.io.Source._ lazy val lines = fromFile("C://Users//Administrator//Desktop//test.txt").mkString 即使文件不存在, 也不会报错, 只有第一次使用变量时会报错, 证明了表达式计算的lazy特性 val lines = fromFile("C://Users//Administrator//Desktop//test.txt").mkString 这句会报错 2、val lines=sc.textFile("file:///home/tg/datas/ws") val rdd1=lines.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_) val rdd2=rdd1.collect 算子: flatMap() map() reduceByKey()转换类型的算子(transformation) collect()行动类型的算子(action) 3、val rdd=lines.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).map(m=>(m._2,m._1)) .sortByKey(true).map(m=>(m._2,m._1)).collect 注:转换类型的算子就是lazy类型,当遇到action行动类型的算子时,才会触发执行。 总结下划线的用法: 1、导包时,导入包中所有内容 import scala.io.Source._ 2、将数列(集合)转换成参数序列 val result=sum(1 to 10:_*) 3、表示Spark算子操作的每一个元素 lines.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_) 八、异常 在Scala中, 异常处理和捕获机制与Java是非常相似的。 try { throw new IllegalArgumentException("x should not be negative") } catch { case _:IllegalArgumentException => println("Illegal Argument!") } finally { print("release resources!") } try { throw new IOException("user defined exception") } catch { case e1:IllegalArgumentException => println("illegal argument") case e2:IOException => println("io exception") } 九、数组 若数组长度固定使用Array,若数组长度不固定则使用ArrayBuffer 1、定长数组的两种创建方式 1》val nums=new Array[Int](10); //10个整数的数组 2》val array=Array("hello","jack") 省略关键字new创建数组的方式,实际上调用的是 Array.scala中的apply()方法。源码如下: def apply[T: ClassTag](xs: T*): Array[T] = { val array = new Array[T](xs.length) var i = 0 for (x <- xs.iterator) { array(i) = x; i += 1 } array} 2、变长数组:数组缓冲 注:scala.collection.mutable._ 可变 scala.collection.immutable._ 不可变对于那种长度按需要变化的数组,Java有ArrayList,Scala有ArrayBuffer变长数组ArrayBuffer使用时要导包 import scala.collection.mutable.ArrayBuffervar arr1=ArrayBuffer[Int]()变长数组操作:1、arr1+=1 2、arr1+=(2,5,6) 3、arr1 ++=Array(3,4)4、arr1.trimEnd(5) 返回值为空,需再次调用arr1来查看删除后的数据5、arr1.insert(1,3,4) 指定1的位置添加3,4元素 6、arr1.remove(3)
7、arr1.remove(3,2) 指定位置删除指定数量的元素
3、变长数组与定长数组之间的转换 变长数组→定长数组:.toArray (不改变原来的数组,系统会自动创建一个新的arry) 定长数组→变长数组:.toBuffer 4、遍历数组 until是RichInt类的方法, 返回所有小于( 不包括) 上限的数字。
5、数组常用算法,除了sum求和,max最大值,min最小值以外还有如下:
6、数组的quickSort()快速排序方法scala.util.Sorting.quickSort(array)十、高阶函数filter :把一个函数作为参数的函数
for(i <- 0 until arr1.length if(arr1(i)%2==0)) print(arr1(i)+" ")array.filter(m=>m%2==0) 这里的m可以省去简写成 array.filter(_%2==0)m=>m%2==0 匿名函数 m=>{m%2==0}算子: spark中的算子有一部分是和scala中的高阶函数是一致的,但是有一部分是scala中没有的,比如reducekey简写用下划线代替m: val rdd=lines.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)lines.flatMap(m=>m.split(" ")).map(word=>(word,1)).reduceByKey((x,y)=>x+y)十一、映射(Map)1、Scala映射就是键值对的集合Map。默认情况下,Scala中使用不可变的映射。如果想使用可变集合Map,必须导入scala.collection.mutable.Map不可变:val map=Map("tom"->20,"jack"->23,"marray"->22)val map2=Map(("tom",20),("jack",23),("marray",22))可变:val map3=scala.collection.mutable.Map(("tom",20),("jack",23),("marray",22))val map4=new scala.collection.mutable.HashMap[String,Int]映射这种数据结构是一种将键映射到值的函数。 区别在于通常的函数计算值, 而映射只是做查询。2、获取映射中的值
注:1》如果映射并不包含请求中使用的键, 则会抛出异常。2》要检查映射中是否有某个指定的键, 可以用contains方法。3》getOrElse方法, 若包含相应的键, 就返回这个键所对应的值, 否则返加0。4》映射.get(键)这样的调用返回一个Option对象, 要么是Some(键对应的值), 要么是None。3、修改Map的元素更新可变Map集合:1》更新Map的元素 ages("Leo") = 312》增加多个元素 ages += ("Mike" -> 35, "Tom" -> 40)3》 移除元素 ages -= "Mike"更新不可变Map集合:1》 添加不可变Map的元素, 产生一个新的集合Map, 原Map不变val ages2 = ages + ("Mike" -> 36, "Tom" -> 40)2》移除不可变Map的元素, 产生一个新的集合Map, 原Map不变val ages3 = ages - "Tom"4、遍历Map操作//遍历map的entrySet for ((key, value) <- ages) println(key + " " + value)// 遍历map的key for (key <- ages.keySet) println(key)// 遍历map的value for (value <- ages.values) println(value)// 生成新map, 反转key和value for ((key, value) <- ages) yield (value, key)5、SortedMap和LinkedHashMap// SortedMap可以自动对Map的key的排序val ages = scala.collection.immutable.SortedMap("leo" -> 30, "alice" -> 15, "jen" -> 25)// LinkedHashMap可以记住插入entry的顺序val ages = new scala.collection.mutable.LinkedHashMap[String, Int]ages("leo") = 30ages("alice") = 15ages("jen") = 256、Java Map与Scala Map的隐式转换import scala.collection.JavaConversions.mapAsScalaMapval javaScores = new java.util.HashMap[String, Int]()javaScores.put("Alice", 10)javaScores.put("Bob", 3)javaScores.put("Cindy", 8)val scalaScores: scala.collection.mutable.Map[String, Int] = javaScores===========================================================import scala.collection.JavaConversions.mapAsJavaMapimport java.awt.font.TextAttribute._val scalaAttrMap = Map(FAMILY -> "Serif", SIZE -> 12)val fOnt= new java.awt.Font(scalaAttrMap)7、元组(tuple)概念:元组是不同类型的值的聚集,对偶是元组的最简单形态,元组的索引从1开始,而不是0
Tuple拉链操作:Tuple拉链操作指的就是zip操作,zip操作是Array类的方法, 用于将两个Array, 合并为一个Array比如 Array(v1)和Array(v2), 使用zip操作合并后的格式为Array((v1,v2)),合并后的Array的元素类型为Tuple。例子如下:val students = Array("Leo", "Jack", "Jen")val scores = Array(80, 100, 90)val studentScores = students.zip(scores)for ((student, score) <- studentScores) println(student + " " + score)注:如果Array的元素类型是Tuple, 调用Array的toMap方法, 可以将Array转换为Map如,studentScores.toMap

推荐阅读
  • SQLite数据库CRUD操作实例分析与应用
    本文通过分析和实例演示了SQLite数据库中的CRUD(创建、读取、更新和删除)操作,详细介绍了如何在Java环境中使用Person实体类进行数据库操作。文章首先阐述了SQLite数据库的基本概念及其在移动应用开发中的重要性,然后通过具体的代码示例,逐步展示了如何实现对Person实体类的增删改查功能。此外,还讨论了常见错误及其解决方法,为开发者提供了实用的参考和指导。 ... [详细]
  • 本文探讨了 Java 中 Pair 类的历史与现状。虽然 Java 标准库中没有内置的 Pair 类,但社区和第三方库提供了多种实现方式,如 Apache Commons 的 Pair 类和 JavaFX 的 javafx.util.Pair 类。这些实现为需要处理成对数据的开发者提供了便利。此外,文章还讨论了为何标准库未包含 Pair 类的原因,以及在现代 Java 开发中使用 Pair 类的最佳实践。 ... [详细]
  • 探索偶数次幂二项式系数的求和方法及其数学意义 ... [详细]
  • HBase Java API 进阶:过滤器详解与应用实例
    本文详细探讨了HBase 1.2.6版本中Java API的高级应用,重点介绍了过滤器的使用方法和实际案例。首先,文章对几种常见的HBase过滤器进行了概述,包括列前缀过滤器(ColumnPrefixFilter)和时间戳过滤器(TimestampsFilter)。此外,还详细讲解了分页过滤器(PageFilter)的实现原理及其在大数据查询中的应用场景。通过具体的代码示例,读者可以更好地理解和掌握这些过滤器的使用技巧,从而提高数据处理的效率和灵活性。 ... [详细]
  • 在Android应用开发中,实现与MySQL数据库的连接是一项重要的技术任务。本文详细介绍了Android连接MySQL数据库的操作流程和技术要点。首先,Android平台提供了SQLiteOpenHelper类作为数据库辅助工具,用于创建或打开数据库。开发者可以通过继承并扩展该类,实现对数据库的初始化和版本管理。此外,文章还探讨了使用第三方库如Retrofit或Volley进行网络请求,以及如何通过JSON格式交换数据,确保与MySQL服务器的高效通信。 ... [详细]
  • 本文深入解析了Java面向对象编程的核心概念及其应用,重点探讨了面向对象的三大特性:封装、继承和多态。封装确保了数据的安全性和代码的可维护性;继承支持代码的重用和扩展;多态则增强了程序的灵活性和可扩展性。通过具体示例,文章详细阐述了这些特性在实际开发中的应用和优势。 ... [详细]
  • 每年,意甲、德甲、英超和西甲等各大足球联赛的赛程表都是球迷们关注的焦点。本文通过 Python 编程实现了一种生成赛程表的方法,该方法基于蛇形环算法。具体而言,将所有球队排列成两列的环形结构,左侧球队对阵右侧球队,首支队伍固定不动,其余队伍按顺时针方向循环移动,从而确保每场比赛不重复。此算法不仅高效,而且易于实现,为赛程安排提供了可靠的解决方案。 ... [详细]
  • 深入理解 Java 控制结构的全面指南 ... [详细]
  • 深入解析 Golang 中 Context 的功能与应用
    本文详细探讨了 Golang 中 Context 的核心功能及其应用场景,通过深入解析其工作机制,帮助读者更好地理解和运用这一重要特性,对于提升代码质量和项目开发效率具有重要的参考价值。 ... [详细]
  • 本课程深入探讨了 Python 中自定义序列类的实现方法,涵盖从基础概念到高级技巧的全面解析。通过实例演示,学员将掌握如何创建支持切片操作的自定义序列对象,并了解 `bisect` 模块在序列处理中的应用。适合希望提升 Python 编程技能的中高级开发者。 ... [详细]
  • Objective-C 中的委托模式详解与应用 ... [详细]
  • Python内置模块详解:正则表达式re模块的应用与解析
    正则表达式是一种强大的文本处理工具,通过特定的字符序列来定义搜索模式。本文详细介绍了Python内置的`re`模块,探讨了其在字符串匹配、验证和提取中的应用。例如,可以通过正则表达式验证电子邮件地址、电话号码、QQ号、密码、URL和IP地址等。此外,文章还深入解析了`re`模块的各种函数和方法,提供了丰富的示例代码,帮助读者更好地理解和使用这一工具。 ... [详细]
  • 手指触控|Android电容屏幕驱动调试指南
    手指触控|Android电容屏幕驱动调试指南 ... [详细]
  • 蓝桥杯物联网基础教程:通过GPIO输入控制LED5的点亮与熄灭
    本教程详细介绍了如何利用STM32的GPIO接口通过输入信号控制LED5的点亮与熄灭。内容涵盖GPIO的基本配置、按键检测及LED驱动方法,适合具有STM32基础的读者学习和实践。 ... [详细]
  • 动态壁纸 LiveWallPaper:让您的桌面栩栩如生(第二篇)
    在本文中,我们将继续探讨如何开发动态壁纸 LiveWallPaper,使您的桌面更加生动有趣。作为 2010 年 Google 暑期大学生博客分享大赛 Android 篇的一部分,我们将详细介绍 Ed Burnette 的《Hello, Android》第三版中的相关内容,并分享一些实用的开发技巧和经验。通过本篇文章,您将了解到如何利用 Android SDK 创建引人入胜的动态壁纸,提升用户体验。 ... [详细]
author-avatar
爱与嫉妒咋
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有