scala基础(一)

Scala与Java的关系
Scala与Java的关系是非常紧密的!!
因为Scala是基于Java虚拟机,也就是JVM的一门编程语言。所有Scala的代码,都需要经过编译为字节码,然后交由Java虚拟机来运行。
所以Scala和Java是可以无缝互操作的。Scala可以任意调用Java的代码。所以Scala与Java的关系是非常非常紧密的。
安装Scala
·从Scala官方网站下载,http://www.scala-lang.org/download/,windows版本的安装包是scala-2.11.7.msi。
·使用下载下来的安装包安装Scala。
·在PATH环境变量中,配置$SCALA_HOME/bin目录。
·在windows命令行内即可直接键入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这个变量,以及它存放的值。
例如,2.0 * res0,返回res1: Double = 4.0 例如,"Hi, " + res0,返回res2: String = Hi, 2

自动补全:在scala>命令行内,可以使用Tab键进行自动补全。
·例如,输入res2.to,敲击Tab键,解释器会显示出以下选项,toCharArray,toLowerCase,toString,toUpperCase。因为此时无法判定你需要补全的是哪一个,因此会提供给你所有的选项。
·例如,输入res2.toU,敲击Tab键,直接会给你补全为res2.toUpperCase。
声明变量
声明val变量:可以声明val变量来存放表达式的计算结果。
例如,val result = 1 + 1

后续这些常量是可以继续使用的
例如,2 * result

但是常量声明后,是无法改变它的值的,例如,result = 1,会返回error: reassignment to val的错误信息。
声明var变量:如果要声明值可以改变的引用,可以使用var变量。
例如,val myresult = 1,myresult = 2

但是在scala程序中,通常建议使用val,也就是常量,因此比如类似于spark的大型复杂系统中,需要大量的网络传输数据,如果使用var,可能会担心值被错误的更改。
在Java的大型复杂系统的设计和开发中,也使用了类似的特性,我们通常会将传递给其他模块 / 组件 / 服务的对象,设计成不可变类(Immutable Class)。在里面也会使用java的常量定义,比如final,阻止变量的值被改变。从而提高系统的健壮性(robust,鲁棒性),和安全性。
指定类型:无论声明val变量,还是声明var变量,都可以手动指定其类型,如果不指定的话,scala会自动根据值,进行类型的推断。
例如,val name: String = null 例如,val name: Any = "leo"

声明多个变量:可以将多个变量放在一起进行声明。
例如,val name1, name2:String = null 例如,val num1, num2 = 100

数据类型与操作符
基本数据类型:
Byte、Char、Short、Int、Long、Float、Double、Boolean

乍一看与Java的基本数据类型的包装类型相同,但是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函数
基本操作符:scala的算术操作符与java的算术操作符也没有什么区别,比如+、-、*、/、%等,以及&、|、^、>>、<<等。
但是,在scala中,这些操作符其实是数据类型的函数比如1 + 1,可以写做1.+(1)例如,1.to(10),又可以写做1 to 10
scala中没有提供++、--操作符,我们只能使用+和-,比如counter = 1,counter++是错误的,必须写做counter += 1.
函数调用与apply()函数
函数调用方式:在scala中,函数调用也很简单。
例如,import scala.math._,sqrt(2),pow(2, 4),min(3, Pi)。

·不同的一点是,如果调用函数时,不需要传递参数,则scala允许调用函数时省略括号的,例如,"Hello World".distinct
apply函数
Scala中的apply函数是非常特殊的一种函数,在Scala的object中,可以声明apply函数。而使用“类名()”(严格来讲应该是“对象名()”)的形式,其实就是“类名.apply()”(严格来讲应该是“对象名.apply()”)的一种缩写。通常使用这种方式来构造类的对象,而不是使用“new 类名()”的方式。
例如,"Hello World"(6),因为在StringOps类中有def apply(n: Int): Char的函数定义,所以"Hello World"(6),实际上是"Hello World".apply(6)的缩写。
例如,Array(1, 2, 3, 4),实际上是用Array object的apply()函数来创建Array类的实例,也就是一个数组。
if表达式
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,但是通常使用上一种写法
if表达式的类型推断:由于if表达式是有值的,而if和else子句的值类型可能不同,此时if表达式的值是什么类型呢?Scala会自动进行推断,取两个类型的公共父类型。
例如,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 ()。
将if语句放在多行中:默认情况下,REPL只能解释一行语句,但是if表达式通常需要放在多行。
可以使用{}的方式,比如以下方式,或者使用:paste和ctrl+D的方式。
if(age > 18) { "adult" } else if(age > 12) "teenager" else "children"

语句终结符、块表达式
默认情况下,scala不需要语句终结符,默认将每一行作为一个语句
一行放多条语句:如果一行要放多条语句,则必须使用语句终结符
例如,使用分号作为语句终结符,var a, b, c = 0; if(a < 10) { b = b + 1; c = c + 1 }
通常来说,对于多行语句,还是会使用花括号的方式
if(a < 10) { b = b + 1 c = c + 1 }

块表达式:块表达式,指的就是{}中的值,其中可以包含多条语句,最后一个语句的值就是块表达式的返回值。
例如,var d = if(a < 10) { b = b + 1; c + 1 }

输入和输出
print和println:print打印时不会加换行符,而println打印时会加一个换行符。
例如,print("Hello World"); println("Hello World")

printf:printf可以用于进行格式化
例如,printf("Hi, my name is %s, I'm %d years old.\n", "Leo", 30)

**readLine: ** readLine允许我们从控制台读取用户输入的数据,类似于java中的System.in和Scanner的作用。
综合案例:游戏厅门禁
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) }

循环
while do循环:**Scala有while do循环,基本语义与Java相同。
var n = 10 while(n > 0) { println(n) n -= 1 }

Scala没有for循环,只能使用while替代for循环,或者使用简易版的for语句
简易版for语句:var n = 10; for(i <- 1 to n) println(i)
或者使用until,表式不达到上限:for(i <- 1 until n) println(i)
也可以对字符串进行遍历,类似于java的增强for循环,for(c <- "Hello World") print(c)
跳出循环语句
scala没有提供类似于java的break语句。
但是可以使用boolean类型变量、return或者Breaks的break函数来替代使用。
import scala.util.control.Breaks._ breakable { var n = 10 for(c <- "Hello World") { if(n == 5) break; print(c) n -= 1 } }

高级for循环
多重for循环:九九乘法表
for(i <- 1 to 9; j <- 1 to 9) { if(j == 9) { println(i * j) } else { print(i * j + " ") } }

if守卫:取偶数
for(i <- 1 to 100 if i % 2 == 0) println(i)

for推导式:构造集合**
for(i <- 1 to 10) yield i

函数的定义与调用
在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就可以自己根据右侧的表达式推断出返回类型。
在代码块中定义包含多行语句的函数体
单行的函数:
def sayHello(name: String) = print("Hello, " + name)

如果函数体中有多行代码,则可以使用代码块的方式包裹多行代码,代码块中最后一行的返回值就是整个函数的返回值。与Java中不同,不是使用return返回值的。
比如如下的函数,实现累加的功能:
def sum(n: Int) = { var sum = 0; for(i <- 1 to n) sum += i sum }

递归函数与返回类型
如果在函数体内递归调用函数自身,则必须手动给出函数的返回类型。
例如,实现经典的斐波那契数列:
9 + 8; 8 + 7 + 7 + 6; 7 + 6 + 6 + 5 + 6 + 5 + 5 + 4; .... def fab(n: Int): Int = { if(n <= 1) 1 else fab(n - 1) + fab(n - 2) }

默认参数
在Scala中,有时我们调用某些函数时,不希望给出参数的具体值,而希望使用参数自身默认的值,此时就定义在定义函数时使用默认参数。
def sayHello(firstName: String, middleName: String = "William", lastName: String = "Croft") = { firstName + " " + middleName + " " + lastName }

如果给出的参数不够,则会从左往右依次应用默认参数。
def sayHello(name: String, age: Int = 20) { print("Hello, " + name + ", your age is " + age) } sayHello("leo")

函数调用时带名参数
在调用函数时,也可以不按照函数定义的参数顺序来传递参数,而是使用带名参数的方式来传递。
sayHello(firstName = "Mick", lastName = "Nina", middleName = "Jack")

还可以混合使用未命名参数和带名参数,但是未命名参数必须排在带名参数前面。
sayHello("Mick", lastName = "Nina", middleName = "Jack")

变长参数
在Scala中,有时我们需要将函数定义为参数个数可变的形式,则此时可以使用变长参数定义函数。
def sum(nums: Int*) = { var res = 0 for (num <- nums) res += num res } sum(1, 2, 3, 4, 5)

序列作为变长参数
在如果想要将一个已有的序列直接调用变长参数函数,是不对的。比如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: _*) }

过程
在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等。
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 lazy val lines = fromFile("C://Users//Administrator//Desktop//test.txt").mkString

相当于定义了一个方法,只有在调用该方法时才会去执行方法体:
def lines = fromFile("C://Users//Administrator//Desktop//test.txt").mkString

异常
在Scala中,异常处理和捕获机制与Java是非常相似的。
try { throw new IllegalArgumentException("x should not be negative") } catch { case _: IllegalArgumentException => println("Illegal Argument!") } finally { print("release resources!") }

Import java.io._ try { throw new IOException(“io exception!!!") } catch { case _: IllegalArgumentException => println("illegal argument") }try { throw new IOException("user defined exception") } catch { case e1: IllegalArgumentException => println("illegal argument") case e2: IOException => println("io exception") }

Array
在Scala中,Array代表的含义与Java中类似,也是长度不可改变的数组。此外,由于Scala与Java都是运行在JVM中,双方可以互相调用,因此Scala数组的底层实际上是Java数组。例如字符串数组在底层就是Java的String[],整数数组在底层就是Java的Int[]。
// 数组初始化后,长度就固定下来了,而且元素全部根据其类型初始化 val a = new Array[Int](10) a(0) a(0) = 1 val a = new Array[String](10) // 可以直接使用Array()创建数组,元素类型自动推断 val a = Array("hello", "world") a(0) = "hi" val a = Array("leo", 30)

ArrayBuffer
在Scala中,如果需要类似于Java中的ArrayList这种长度可变的集合类,则可以使用ArrayBuffer。
// 如果不想每次都使用全限定名,则可以预先导入ArrayBuffer类
import scala.collection.mutable.ArrayBuffer // 使用ArrayBuffer()的方式可以创建一个空的ArrayBuffer val b = ArrayBuffer[Int]() // 使用+=操作符,可以添加一个元素,或者多个元素 // 这个语法必须要谨记在心!因为spark源码里大量使用了这种集合操作语法! b += 1 b += (2, 3, 4, 5) // 使用++=操作符,可以添加其他集合中的所有元素 b ++= Array(6, 7, 8, 9, 10) // 使用trimEnd()函数,可以从尾部截断指定个数的元素 b.trimEnd(5) // 使用insert()函数可以在指定位置插入元素 // 但是这种操作效率很低,因为需要移动指定位置后的所有元素 b.insert(5, 6) b.insert(6, 7, 8, 9, 10) // 使用remove()函数可以移除指定位置的元素 b.remove(1) b.remove(1, 3) // Array与ArrayBuffer可以互相进行转换 b.toArray a.toBuffer

遍历Array和ArrayBuffer
// 使用for循环和until遍历Array / ArrayBuffer // 使until是RichInt提供的函数 for (i <- 0 until b.length) println(b(i)) // 跳跃遍历Array / ArrayBuffer for(i <- 0 until (b.length, 2)) println(b(i)) // 从尾部遍历Array / ArrayBuffer for(i <- (0 until b.length).reverse) println(b(i)) // 使用“增强for循环”遍历Array / ArrayBuffer for (e <- b) println(e)

数组常见操作
// 数组元素求和 val a = Array(1, 2, 3, 4, 5) val sum = a.sum // 获取数组最大值 val max = a.max // 对数组进行排序 scala.util.Sorting.quickSort(a) // 获取数组中所有元素内容 a.mkString a.mkString(", ") a.mkString("<", ",", ">") // toString函数 a.toString b.toString

使用yield和函数式编程转换数组
// 对Array进行转换,获取的还是Array val a = Array(1, 2, 3, 4, 5) val a2 = for (ele <- a) yield ele * ele // 对ArrayBuffer进行转换,获取的还是ArrayBuffer val b = ArrayBuffer[Int]() b += (1, 2, 3, 4, 5) val b2 = for (ele <- b) yield ele * ele // 结合if守卫,仅转换需要的元素 val a3 = for (ele <- if ele % 2 == 0) yield ele * ele // 使用函数式编程转换数组(通常使用第一种方式) a.filter(_ % 2 == 0).map(2 * _) a.filter { _ % 2 == 0 } map { 2 * _ }

算法案例:移除第一个负数之后的所有负数
// 构建数组 val a = ArrayBuffer[Int]() a += (1, 2, 3, 4, 5, -1, -3, -5, -9) // 每发现一个第一个负数之后的负数,就进行移除,性能较差,多次移动数组 var foundFirstNegative = false var arrayLength = a.length var index = 0 while (index < arrayLength) { if (a(index) >= 0) { index += 1 } else { if (!foundFirstNegative) { foundFirstNegative = true; index += 1 } else { a.remove(index); arrayLength -= 1 } } }

算法案例:移除第一个负数之后的所有负数(改良版)
// 重新构建数组 val a = ArrayBuffer[Int]() a += (1, 2, 3, 4, 5, -1, -3, -5, -9) // 每记录所有不需要移除的元素的索引,稍后一次性移除所有需要移除的元素 // 性能较高,数组内的元素迁移只要执行一次即可 var foundFirstNegative = false val keepIndexes = for (i <- 0 until a.length if !foundFirstNegative || a(i) >= 0) yield { if (a(i) < 0) foundFirstNegative = true i } for (i <- 0 until keepIndexes.length) { a(i) = a(keepIndexes(i)) } a.trimEnd(a.length - keepIndexes.length)

创建Map
// 创建一个不可变的Map val ages = Map("Leo" -> 30, "Jen" -> 25, "Jack" -> 23) ages("Leo") = 31 // 创建一个可变的Map val ages = scala.collection.mutable.Map("Leo" -> 30, "Jen" -> 25, "Jack" -> 23) ages("Leo") = 31 // 使用另外一种方式定义Map元素 val ages = Map(("Leo", 30), ("Jen", 25), ("Jack", 23)) // 创建一个空的HashMap val ages = new scala.collection.mutable.HashMap[String, Int]

访问Map的元素
// 获取指定key对应的value,如果key不存在,会报错 val leoAge = ages("Leo") val leoAge = ages("leo") // 使用contains函数检查key是否存在 val leoAge = if (ages.contains("leo")) ages("leo") else 0 // getOrElse函数 val leoAge = ages.getOrElse("leo", 0)

修改Map的元素
// 更新Map的元素 ages("Leo") = 31 // 增加多个元素 ages += ("Mike" -> 35, "Tom" -> 40) // 移除元素 ages -= "Mike" // 更新不可变的map val ages2 = ages + ("Mike" -> 36, "Tom" -> 40) // 移除不可变map的元素 val ages3 = ages - "Tom"

遍历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) 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") = 30 ages("alice") = 15 ages("jen") = 25

Map的元素类型—Tuple
// 简单Tuple val t = ("leo", 30) // 访问Tuple t._1 // zip操作 val names = Array("leo", "jack", "mike") val ages = Array(30, 24, 26) val nameAges = names.zip(ages) for ((name, age) <- nameAges) println(name + ": " + age)

定义一个简单的类
// 定义类,包含field以及方法 class HelloWorld { private var name = "leo" def sayHello() { print("Hello, " + name) } def getName = name }// 创建类的对象,并调用其方法 val helloWorld = new HelloWorld helloWorld.sayHello() print(helloWorld.getName) // 也可以不加括号,如果定义方法时不带括号,则调用方法时也不能带括号

getter与setter
1、定义不带private的 var field,此时scala生成class时,会自动生成一个private[this]的成员字段(名称与field不同),并还生成一对getter和setter方法,分别叫做field和 field_=,并且getter和setter方法的访问修饰符与field定义相同
2、而如果使用private修饰field,则只生成的getter和setter,且访问修饰也是private的
3、如果定义val field,则只会生成getter方法
4、 如果不希望生成setter和getter方法,则将field声明为private[this]
class Student { var name = "leo" }

// 调用getter和setter方法,分别叫做name和name_=
val leo = new Student print(leo.name) leo.name = "leo1" //实际上会调用 leo.name_=("leo1")方法

自定义getter与setter
// 如果只是希望拥有简单的getter和setter方法,那么就按照scala提供的语法规则,根据需求为field选择合适的修饰符就好:var、val、private、private[this]
// 但是如果希望能够自己对getter与setter进行控制,则可以自定义getter与setter方法
// 自定义setter方法的时候一定要注意scala的语法限制,签名、=、参数间不能有空格
class Student { private var myName = "leo" //默认会生成一对private getter(myName)、 setter(myName _=)方法 def name = "your name is " + myName //自定义myName 成员变量getter方法 def name_=(newValue: String){//自定义myName 成员变量的setter方法 print("you cannot edit your name!!!") } } val leo = new Student print(leo.name) leo.name = "leo1" //会去调用 name_+ 自定义setter 方法

仅暴露field的getter方法
// 如果你不希望field有setter方法,则可以定义为val,但是此时就再也不能更改field的值了
// 但是如果希望能够仅仅暴露出一个getter方法,并且还能通过某些方法更改field的值,那么需要综合使用private以及自定义getter方法。此时,由于field是private的,所以setter和getter都是private,对外界没有暴露;自己可以实现修改field值的方法;自己可以覆盖getter方法
class Student { private var myName = "leo" def updateName(newName: String) { //更改field的其他方法(命名约束的不满足setter方法) if(newName == "leo1") myName = newName else print("not accept this new name!!!") } def name = "your name is" + myName//覆盖自动生成的私有getter方法 }

private[this]的使用
// 如果将field使用private来修饰,那么代表这个field是类私有的,在类的方法中,可以直接访问类的其他对象的private field
// 这种情况下,如果不希望field被其他对象访问到,那么可以使用private[this],意味着对象私有的field,只有本对象内可以访问到(子类对象中也是不可以访问的,因为私有的是不能被继承的)
class Student { private var myAge = 0 //试着修改成private[this] def age_=(newValue: Int) { if (newValue > 0) myAge = newValue else print("illegal age!") } def age = myAge def older(s: Student) = { myAge > s.myAge //修改成private[this]后,就会报错 } }

private[this]还可以用为修改方法
Java风格的getter和setter方法
// Scala的getter和setter方法的命名与java是不同的,是field和field_=的方式
// 如果要让scala自动生成java风格的getter和setter方法,只要给field添加@BeanProperty注解即可
// 此时会生成4个方法,name: String、name_=(newValue: String): Unit、getName(): String、setName(newValue: String): Unit
import scala.reflect.BeanProperty class Student { @BeanProperty var name: String = _ } class Student(@BeanProperty var name: String) val s = new Student s.setName("leo") s.getName()

辅助constructor
// Scala中,可以给类定义多个辅助constructor,类似于java中的构造函数重载
// 辅助constructor之间可以互相调用,而且必须第一行调用主constructor或其他辅构造器
class Student { private var name = "" private var age = 0 def this(name: String) { this() this.name = name } def this(name: String, age: Int) { this(name) this.age = age } }

主constructor
// Scala中,主constructor是与类名放在一起的,有且只有一个,与java不同
// 而且类中,没有定义在任何方法中的代码(包括成员字段),都属于主constructor的代码,且执行的顺序与代码书写的顺序一致。这其实与Java是一样的,在Java中方法之外的代码(成员以及代码块)会在构造器调用之前最先执行,姑且将这些代码看作也是放到了一个主构造器中进行执行的,只不过这种主构造器不能带构造参数
//主构造器与类定义是在一起的,如果有参数,则在类名后面跟括号即可:
class Student(val name: String, val age: Int) { println("your name is " + name + ", your age is " + age) }

当然没有参数的主构造器也可以带括号:
class Student() {} // 主constructor中还可以通过使用默认参数,来给参数默认的值 class Student(val name: String = "leo", val age: Int = 30) { println("your name is " + name + ", your age is " + age) }

// 如果主constrcutor传入的参数什么修饰都没有,比如name: String,那么如果类内部除主constrcutor方法外其它方法也使用到了,则会自动将该参数修饰为private[this] name以便其它方法使用:
class Student(name: String) { def f(){print(name)} def f(s:Student){print(s.name)}//编译出错 }class Student(val name: String) { def f(){print(name)} def f(s:Student){print(s.name)}//编译正确,证明没有使用var或val修饰时,且在除主构造器中使用外,则使用private[this]来修饰 }

类中没有定义的在任何方法中的代码都属于主构造器,并且执行顺序与书写顺序一致:
内部类
// Scala中,同样可以在类中定义内部类;但是与java不同的是,每个外部类的对象的内部类,都属于不同的类
import scala.collection.mutable.ArrayBuffer class Class { class Student(val name: String) {} val students = new ArrayBuffer[Student] def getStudent(name: String) ={ new Student(name) } }val c1 = new Class val s1 = c1.getStudent("leo") c1.students += s1 val c2 = new Class val s2 = c2.getStudent("leo") c1.students += s2//异常

object
1、object,相当于class的单个实例(但与从class实例化出来的对象的内容决不一样),通常在里面放一些class层面上共享的内容,如Java中的静态field或者method即在定义在object中(注:Scala中没有Java的静态概念,所以延伸出了object这个东东)
2、你可以将object看作是一个类class,只是这个类在内存中只有一个单例,且定义的object名就是实例名,不需我们自己实例化,运行时JVM已帮我们new出来了
3、第一次调用object的方法时,就会执行object的constructor,也就是object内部不在method中的代码;但是object不能定义接受参数的constructor
4、注意,object的constructor只会在其第一次被调用时执行一次,以后再次调用就不会再次执行constructor了
5、object通常用于作为单例模式的实现,或者放class的静态成员,比如工具方法
object Person { private var eyeNum = 2 println("this Person object!") def getEyeNum = eyeNum }

伴生对象
// 如果有一个class,还有一个与class同名的object,那么就称这个object是class的伴生对象,class是object的伴生类
// 伴生类和伴生对象必须存放在一个.scala文件之中
// 伴生类和伴生对象,最大的特点就在于,互相可以访问private field
object Person { private val eyeNum = 2 def getEyeNum = eyeNum } class Person(val name: String, val age: Int) { def sayHello = println("Hi, " + name + ", I guess you are " + age + " years old!" + ", and usually you must have " + Person.eyeNum + " eyes.") }

让object继承抽象类
// object的功能其实和class类似,除了不能定义接受参数的constructor之外
// object也可以继承抽象类,并覆盖抽象类中的方法
abstract class Hello(var message: String) { def sayHello(name: String): Unit } object HelloImpl extends Hello("hello") { override def sayHello(name: String) = { println(message + ", " + name) } }

apply方法
// object中非常重要的一个特殊方法,就是apply方法
// 通常在伴生对象中实现apply方法,并在其中实现构造伴生类的对象的功能,一般用作工厂方法
// 而创建伴生类的对象时,通常不会使用new Class的方式,而是使用Class()的方式,隐式地调用伴生对象得apply方法,这样会让对象创建更加简洁
// 比如,Array类的伴生对象的apply方法就实现了接收可变数量的参数,并创建一个Array对象的功能
val a = Array(1, 2, 3, 4, 5)

// 比如,定义自己的伴生类和伴生对象
class Person(val name: String) object Person { def apply(name: String) = new Person(name) }

另外,如果直接在一个对象后面接小括号,则会去调用这个对象所对应类中相应的apply方法:
class Person { def apply(name: String) = println(name) } scala> val p = new Person scala> p("Persion") Persion

main方法
// 就如同java中,如果要运行一个程序,必须编写一个包含main方法类一样;在scala中,如果要运行一个应用程序,那么必须有一个main方法,作为入口
// scala中的main方法定义为def main(args: Array[String]),而且必须定义在object中
object HelloWorld { def main(args: Array[String]) { println("Hello World!!!") } }

// 除了自己实现main方法之外,还可以继承App Trait,然后将需要在main方法中运行的代码,直接作为object的constructor代码;而且用args可以接受传入的参数
object HelloWorld extends App { if (args.length > 0) println("hello, " + args(0)) else println("Hello World!!!") }

【scala基础(一)】// 如果要运行上述代码,需要将其放入.scala文件,然后先使用scalac编译,再用scala执行
scalac HelloWorld.scala scala -Dscala.time HelloWorld

// App Trait的工作原理为:App Trait继承自DelayedInit Trait,scalac命令进行编译时,会把继承App Trait的object的constructor代码都放到DelayedInit Trait的delayedInit方法中,然后由App Trait的main方法去调用执行
用object来实现枚举功能
// Scala没有直接提供类似于Java中的Enum这样的枚举特性,如果要实现枚举,则需要用object继承Enumeration类,并且调用Value方法来初始化枚举值
object Season extends Enumeration { val SPRING, SUMMER, AUTUMN, WINTER = Value }

// 还可以通过Value传入枚举值的id和name,通过id和toString可以获取; 还可以通过id和name来查找枚举值
object Season extends Enumeration { val SPRING = Value(0, "spring") val SUMMER = Value(1, "summer") val AUTUMN = Value(2, "autumn") val WINTER = Value(3, "winter") }Season(0)// spring Season.withName("spring") // spring,根据名称找 // 使用枚举object.values可以遍历枚举值 for (ele <- Season.values) println(ele)

extends
// Scala中,让子类继承父类,与Java一样,也是使用extends关键字
// 继承就代表,子类可以从父类继承父类的field和method;然后子类可以在自己内部放入父类所没有,子类特有的field和method;使用继承可以有效复用代码
// 子类可以覆盖父类的field和method;但要注意的是final类是不能被继承的,而且final类型的field和method是无法被覆盖的
class Person { private var name = "leo" def getName = name }class Student extends Person { private var score = "A" def getScore = score }

override和super
// Scala中,如果子类要覆盖一个父类中的非抽象方法,则必须使用override关键字;如果是抽象的方法,则可以省略
// override关键字可以帮助我们尽早地发现代码里的错误,比如:override修饰的父类方法的方法名我们拼写错了;比如要覆盖的父类方法的参数我们写错了;等等
// 此外,在子类覆盖父类方法之后,如果我们在子类中就是要调用父类的被覆盖的方法呢?那就可以使用super关键字,显式地指定要调用父类的方法
class Person { private var name = "leo" def getName = name }class Student extends Person { private var score = "A" def getScore = score override def getName = "Hi, I'm " + super.getName }

重写时需要override关键字,如果是实现则可以省略override关键字
override field
子类可以覆盖父类的同名的非private成员
// Scala中,子类可以覆盖父类的val field,而且子类的val field还可以覆盖父类的val field的getter方法;只要在子类中使用override关键字即可
class Person { /*private*/ val name: String = "Person" } class Student extends Person { override val name: String = "leo" // 重写一定要带上override关键字 }

只有val变量才能被重写,var变量是不能被重写的:
class A{ var f = "a" } class B extends A{ override var f = "b" // error: overriding variable f in class A of type String; //variable f cannot override a mutable variable }

下面也是不行的:
class A{ var f:String = "a" } class B extends A{ override def f:String = "b" override def f_=(x:String) = println(x) // error: overriding variable f in class A of type String; method f_= cannot override a mutable variable }

var变量只能被实现,如果将上面换成抽象的var字段,则是可以的:
abstract class A{ var f:String } class B extends A{ /* override */ def f:String = "b" //由于是实现,所以可以省略override override def f_=(x:String) = println(x) //也可以不省略override } 或者: abstract class A{ var f:String }class B extends A{ var f:String = "" }

val变量只能被val实现,不能被def实现:
abstract class A{ val f:String }class B extends A{ def f:String = ""// error: overriding value f in class A of type String; //method f needs to be a stable, immutable value}

但可以这样:
abstract class A{ val f:String }class B extends A{ val f:String = "" }

val、var override/实现 def
abstract class Person { def id: Int } class Student extends Person{ override var id = 9527//Error: method id_= overrides nothing }

在scala中定义了一个var变量,会自动生成getter和setter方法。由于父类中只定义了一个方法def id: Int,而子类中var变量会自动生成getter(id)与setter方法(id_),但是父类并没有这个setter方法,所以是无法重写的。如下修改即可:
abstract class Person { def id: Int def id_=(value: Int) //父类必须有set方法 } class Student extends Person{ override var id = 9527 //为var变量自动生成get和set方法 }

或者子类定义成val变量:
abstract class Person { def id: Int } class Student extends Person{ override val id = 9527 }

上面是val或var来实现def,下面是val或var来重写def:
class Person { def id: Int = 1 } class Student extends Person{ override val id = 9527 } 或 class Person { def id: Int = 1 def id_=(value: Int) =println(value) } class Student extends Person{ override var id = 9527 }

但是不能使用def重写val或var:
class Person { val sex: String }class Student extends Person { override def sex:String = "" //error: overriding value sex in class Person of type String; //method sex needs to be a stable, immutable value } class Person { var sex: String = "X" } class Student extends Person { override def sex:String = "" override def sex_=(x:String) = println(x) }

也不能使用def实现val:
abstract class Person { val sex: String } class Student extends Person { def sex:String = "" //error: overriding value sex in class Person of type String; //method sex needs to be a stable, immutable value }

但可以使用def实现var:
abstract class Person { var sex: String } class Student extends Person { def sex:String = "" def sex_=(x:String) = println(x) }

成员变量与方法之间重写与实现结论:可以使用val或var来重写或实现def,也可以使用def实现var;但不能使用def重写val或var,也不能使用def实现val
isInstanceOf和asInstanceOf
// 如果我们创建了子类的对象,但是又将其赋予了父类类型的变量。则在后续的程序中,我们又需要将父类类型的变量转换为子类类型的变量,应该如何做?
// 首先,需要使用isInstanceOf判断对象是否是指定类的对象,如果是的话,则可以使用asInstanceOf将对象转换为指定类型
// 注意,如果对象是null,则isInstanceOf一定返回false,asInstanceOf一定返回null
// 注意,如果没有用isInstanceOf先判断对象是否为指定类的实例,就直接用asInstanceOf转换,则可能会抛出异常
class Person class Student extends Person val p: Person =new Student var s: Student = null if (p.isInstanceOf[Student]) s = p.asInstanceOf[Student] scala> p.isInstanceOf[Student] res7: Boolean = true scala> p.isInstanceOf[Person] res8: Boolean = true

getClass和classOf
// isInstanceOf只能判断出对象是否是给定类或其子类的实例对象,而不能精确判断出对象就是给定类的实例对象
// 如果要求精确地判断对象就是指定类的对象,那么就只能使用getClass和classOf了
// 对象.getClass可以精确获取对象所属的类class,classOf[类]可以精确获取类,然后使用==操作符即可判断
class Person class Student extends Person val p: Person = new Student p.isInstanceOf[Person] p.getClass == classOf[Person] p.getClass == classOf[Student]

使用模式匹配进行类型判断
// 但是在实际开发中,比如spark的源码中,大量的地方都是使用了模式匹配的方式来进行类型的判断,这种方式更加地简洁明了,而且代码得可维护性和可扩展性也非常的高
// 使用模式匹配,功能性上来说,与isInstanceOf一样,也是判断主要是该类以及该类的子类的对象即可,也不是精准判断的
class Person class Student extends Person val p: Person = new Student p match { case per: Person => println("it's Person's object") case _=> println("unknown type") }

protected
// 跟java一样,scala中同样可以使用protected关键字来修饰field和method,这样子类就可以继承这些成员或方法
// 还可以使用protected[this],则只能在当前子类对象中访问父类的使用protected[this]修饰的field和method,无法通过其他子类对象访问父类中的这些字段与方法
class Person { protected var name: String = "leo" protected[this] var hobby: String = "game" } class Student extends Person { def sayHello = println("Hello, " + name) def makeFriends(s: Student) { println("my hobby is " + hobby + ", your hobby is " + s.hobby) //此处编译出错 } }

protected[this]修饰的字段只能在本对象或其子对象中使用,不能在其他对象中使用:
class Person { protected var name: String = "leo" protected[this] var hobby: String = "game" def makeFriends(s: Person ){ println("my hobby is " + hobby + ", your hobby is " + s.hobby) //此处编译还是出错 } }

与private[this]一样,protected[this]也可以修饰方法
调用父类的constructor
// Scala中,每个类可以有一个主constructor和任意多个辅助constructor,而每个辅助constructor的第一行都必须是调用其他辅助constructor或者是主constructor;因此子类的辅助constructor是一定不可能直接调用父类的constructor的
// 只能在子类的主constructor中调用父类的constructor,以下这种语法,就是通过子类的主构造函数来调用父类的构造函数(即在extends后面指定需要调用父类哪个构造器)
// 注意!如果是父类中接收的参数,比如name和age,子类中接收时,就不要用任何val或var来修饰了(或者带上修饰了,但将参数名命成不一样也可),否则会认为是子类要覆盖父类的field
class Person(val name: String, val age: Int) class Student(name: String, age: Int, var score: Double) extends Person(name, age) /*调用父类的辅助构造器*/{ def this(name: String) { this(name, 0, 0) //调用主构造器 } def this(age: Int) { this("leo", age, 0) //调用主构造器 } }

调用父类的主构造器:
class Person(val name: String, val age: Int){ def this(){ this("11",11) } } class Student(name: String, age: Int, var score: Double) extends Person/*或 Person()*/ { def this(name: String) { this(name, 0, 0) //调用主构造器 } def this(age: Int) { this("leo", age, 0) //调用主构造器 } }

    推荐阅读