热门标签 | 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

推荐阅读
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • golang常用库:配置文件解析库/管理工具viper使用
    golang常用库:配置文件解析库管理工具-viper使用-一、viper简介viper配置管理解析库,是由大神SteveFrancia开发,他在google领导着golang的 ... [详细]
  • 深入解析JVM垃圾收集器
    本文基于《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版,详细探讨了JVM中不同类型的垃圾收集器及其工作原理。通过介绍各种垃圾收集器的特性和应用场景,帮助读者更好地理解和优化JVM内存管理。 ... [详细]
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 本文详细探讨了Java中的24种设计模式及其应用,并介绍了七大面向对象设计原则。通过创建型、结构型和行为型模式的分类,帮助开发者更好地理解和应用这些模式,提升代码质量和可维护性。 ... [详细]
  • 数据管理权威指南:《DAMA-DMBOK2 数据管理知识体系》
    本书提供了全面的数据管理职能、术语和最佳实践方法的标准行业解释,构建了数据管理的总体框架,为数据管理的发展奠定了坚实的理论基础。适合各类数据管理专业人士和相关领域的从业人员。 ... [详细]
  • 本文介绍了如何使用JQuery实现省市二级联动和表单验证。首先,通过change事件监听用户选择的省份,并动态加载对应的城市列表。其次,详细讲解了使用Validation插件进行表单验证的方法,包括内置规则、自定义规则及实时验证功能。 ... [详细]
  • 本文深入探讨了 Java 中的 Serializable 接口,解释了其实现机制、用途及注意事项,帮助开发者更好地理解和使用序列化功能。 ... [详细]
  • 基于KVM的SRIOV直通配置及性能测试
    SRIOV介绍、VF直通配置,以及包转发率性能测试小慢哥的原创文章,欢迎转载目录?1.SRIOV介绍?2.环境说明?3.开启SRIOV?4.生成VF?5.VF ... [详细]
  • 本文总结了Java程序设计第一周的学习内容,涵盖语言基础、编译解释过程及基本数据类型等核心知识点。 ... [详细]
  • 深入理解Redis的数据结构与对象系统
    本文详细探讨了Redis中的数据结构和对象系统的实现,包括字符串、列表、集合、哈希表和有序集合等五种核心对象类型,以及它们所使用的底层数据结构。通过分析源码和相关文献,帮助读者更好地理解Redis的设计原理。 ... [详细]
  • MySQL DateTime 类型数据处理及.0 尾数去除方法
    本文介绍如何在 MySQL 中处理 DateTime 类型的数据,并解决获取数据时出现的.0尾数问题。同时,探讨了不同场景下的解决方案,确保数据格式的一致性和准确性。 ... [详细]
  • Java 数组及其常用操作
    本文详细介绍了 Java 中的数组类型、定义方法以及常见操作,帮助开发者更好地理解和使用 Java 数组。 ... [详细]
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社区 版权所有