scala基础(一)

2018-12-10  本文已影响4人  我是嘻哈大哥

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文件,然后先使用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) //调用主构造器
  }
}
上一篇下一篇

猜你喜欢

热点阅读