Scala基础介绍

Scala是一门主要以Java虚拟机(JVM)为目标运行环境,并将面向对象和函数式编程有机的结合在一起。因为Scala运行于JVM上,所以Scala可以访问任何Java类库,并且能够与Java框架进行互操作。Scala既有动态语言的灵活简洁(通过类型推到),又有静态类型检查带来的安全保证和执行效率(Scala是静态类型语言)。Scala支持处理脚本化的临时任务,也能处理高并发场景下的分布式互联网大数据应用程序。

扩展

动态类型和静态类型

动态类型语言:运行期间才做数据类型检查。在动态类型语言编程中,不用给变量指定数据类型。该语言会在第一次赋值的时候,在内部将数据类型记录下来。比如:Python、JavaScript、PHP等。

静态类型原因:编译期间就做类型检查。在编程期间需要指定数据类型(有些需要手动指定比如Java,有些语言会隐式转换,比如Scala),变量在使用之前必须声明了数据类型。比如Java、Scala、C、C++等。

动态类型语言和静态类型语言最大的区别就是数据类型在编译期间是否确定。

强类型和弱类型

强定义类型语言:强制数据类型定义的语言,一旦将变量赋值给某个数据类型,如果不经过强制类型转换,那么永远都是这个类型。它是类型安全的。比如Java、Scala、C。

弱定义类型语言:数据类型是可以被忽略的,与强制类型语言相反,一个变量可以赋值给不同类型的语言。它是类型不安全的。比如Python、JavaScript等。

强定义类型语言和弱定于类型语言的最大求别就是一个变量能不能被赋值给多个数据类型。

变量

变量定义

Scala提供了两种声明变量的方式:val和var。其中val类似java中的final变量,定义后不可重新赋值(对象类型,对象本身不能重新赋值,但是对象内部是可以重新赋值的),var定义的变量是可重复赋值的变量。

Scala定义变量不需要显示指定类型, 因为Scala内部的类型推断会根据值内容推断,当然也可以指定类型。

代码块

Plain Text

val num1 = 1
va2 num2: Int = 2

提示

在Scala中,变量或函数的类型都是在变量或函数的后面,这使得我们在阅读复杂类型的声明时更加容易。

还有一点需要注意,在变量声明和赋值语句之后,并没有分号。在Scala中仅当同一行存在多条语句时才需要使用分号分隔。

Scala变量定义时必须进行初始化操作(无论val还是var类型变量),var类型变量可以使用占位符在定义时赋值,但是需要指定数据类型。

代码块

Scala

var num: Int = _ //初始化为0
var str: String = _ //初始化为null

基本类型

Scala同Java一样也有七种数值类型:Byte、Short、Int、Long、Float、Double和Boolean。不同的是,这些类型在Scala中都是类,Scala并没有刻意区分基本类型和引用类型,所以从这方面来看Scala面向对象的更加纯粹。这些基本类型都在scala包中,比如scala.Int、scala.Double(Scala默认会加载scala和java.lang包)。

Scala使用底层的java.lang.String类来表示字符串,但是Scala提供了扩展类StringOps为字符串追加了上百种操作,比如:

代码块

Plain Text

"Hello".intersect("World") //求两个字符串的交集。

同理,Scala为基本类型分别都提供了扩展类:RichInt、RichDouble、RichChar等,由基础类型到扩展类型的使用是Scala内部隐式转换完成的,我们不需要做任何操作(在看API时,可以看一下扩展类型提供的方法)。

提示

在Scala中进行类型转换时是使用方法,而不是强制类型转换。比如3.14.toInt 得到3。

懒值

当我们在val类型前使用lazy修饰时,这个变量的初始化会被延迟,直到我们首次使用它。

代码块

Scala

//变量只有在第一次使用时才会打开文件
lazy val words = scala.io.Source.fromFile("/opt/shard/a.log").mkString 

Scala只允许在val类型变量前使用lazy,对于var变量不适用,因为避免程序运行中变量还未使用就被重新赋值。

运算符

Scala也适用+、-、*、/、%进行算术操作,使用&、|、^、>>、<<、~进行位操作,使用<、<=、>、>=、!进行关系运算操作,使用&&、||进行逻辑运算操作(也有短路概念)。与Java不同的是,这些运算符都是方法调用,比如1+ 2实际调用的是 1.+(2),当a.ope(b)的时候ope称做方法,当写成 a ope b的时候ope就称做操作符,这些运算符称为中缀运算符。

Scala并没有提供++和--操作符,可以使用 +=1 和-=1来代替(因为对于Int是一个类,Scala认为没有必要为了这么一个功能,来增加一些特例)。

控制结构

在Java中表达式和语句是两类,表达式有值,而语句执行动作。但在Scala中,几乎所有构造的结构语句都有值。

条件表达式

Scala的if/else语法结构和Java一样,但是Scala中的if/else表达式是有值的。

代码块

Plain Text

//java形式的赋值
int res;
if(x > 0) res = 1 else res = -1;
//scala 简易赋值
val res = if(x > 0) 1 esle -1

Scala还做了一点,就是将Java中if/else和? : 三目运算结合起来了。如果要实现下面功能,java只能通过三目运算符来实现。

代码块

Plain Text

val res = if(x > 0) "yes" : -1
//缺少else语句
val res = if(x > 0) "yes"
val res = if(x > 0) "yes" else ()

if/else语句的两个分支分别为String和Int,它们的超类为Any,所以res的类型为Any类型。因为Scala每个表达式都应该有值,当缺少else语句时表达式的值就不能确定了,Scala引入了Unit类,写作(),来表示“无有用值”的占位符(和java中的void类似,但本质上void表示没有值,而Unit表示无有用的值)。

注意

Scala没有Switch语句,但是提供了功能强大的模式匹配。

循环

Scala提供了和Java一样的while和do-while循环。

代码块

Scala

while(n > 0) {
//循环体
}
do{
  //循环体
}while(n > 0);

Scala也提供了for循环,但是并不是Java中的for循环不一样。用变量i来遍历右侧表达式中的所有值,变量i并没有通过val和var指定,它的类型是集合元素的类型,作用域持续到循环结束。

代码块

Scala

for(i <- 表达式){
  //循环体
}

说明

Scala并没有提供break和continue语句,如果要退出循环,可以使用以下几种方式:

1、使用boolean类型控制变量。

2、使用嵌套函数,从函数中return。

3、使用Breaks对象中的break方法。

代码块

Scala

import scala.util.control.Breaks._
breakable{
  for(...) {
    if(...) break
  }
}

方式3是通过抛出和捕获异常来完成的,所以效率会低一些。

还需要指出的一点,在Java中不能在重叠的作用域中使用同名的两个变量,当在Scala中这是允许的。

代码块

Scala

val n = ...
for(n <- 1 to 10) {
  //使用for语句中的n
}

for循环高级用法

可以在for循环中提供多个生成器,使用分号分开。多个生成器相当于多层for循环,从左到右依次由外到内的多层for循环。

代码块

Scala

for(i <- 1 to 3;j<- 1 to 4) println(s"i: $i,j: $j")

可以为每个生成器提供一个守卫,守卫是以if开头的Boolean表达式。

代码块

Scala

for(i <- 1 to 3;j<- 1 to 4 if i != j) println(s"i: $i,j: $j")

也可以在循环中定义变量,并在循环中使用变量。

代码块

Scala

for(i <- 1 to 3; num = 4 - i ;j<- num to 4 if i != j) println(s"i: $i,j: $j")

可以在for循环体中以yield开头,会构造一个集合,每次迭代的值会成为集合中的元素,这叫做for推导式。

代码块

Scala

for(i <- 1 to 3; num = 4 - i ;j<- num to 4 if i != j) yield i

块表达式

Scala中使用{}块表达式和Java一样,但是Scala的块表达式有有结果的(不应该称为有返回值),{}中的最后一个表达式为块表达式的结果。比如:

代码块

Plain Text

var s = { 
  val a = 0
  val b = 1
  if(b > a) a = 2 else a =0
  a + b
}

这在初始化变量分为多步时是非常有用的。

需要注意的是Scala中赋值语句本身是没有值的,即为Unit类型。比如 {a = 1;b=1;a+b},这个语句的类型为Unit类型(并不是我们想的a+b结果的类型)。所以下面的操作在Scala中是不合法的:

代码块

Plain Text

var a = 0
var b = 0
a = b = 1// b=1的返回类型为Unit类型

输入输出

Scala提供了print和println函数输出,同时提供了C风格的格式化输出。

代码块

Scala

print("Hello World!")
println("Hello World!") //结尾添加一个换行符
printf("Hello %s,you age %d. %n",name,age)//C风格格式化输出,

Scala的print和println也提供了三种插值格式化输出,分别是在输出字符串前添加:s、f、raw。

代码块

Scala

println(f"Hello $name,you age $age. %n")
println(raw"\n Hello $name,you age $age. %n")
println(s"Hello $name,you age ${age + 1}.")

以f为前缀,可以在字符串中添加格式化字符,比如%n换行。以raw为前缀,会将转义字符原封不动输出。以s为前缀,可以转义字符,但是不能有格式化字符。如果不加任何前缀,则插值变量不会被替换,会原封不动输出字符串。当插入变量为表达式时,使用{}将其括起来。

如果要使用Scala的输入,可以使用scala.io.StdIn中的readInt、readDouble、readLine来接受控制台输入。

代码块

Scala

import scala.io.StdIn
val age = StdIn.readInt
var name = StdIn.readLine("you name:")
println("you name $name,you age $age")

函数/方法

在Java中函数和方法时等价的,或者可以说Java中没有函数的概念。由于Scala混合了面向对象和函数式的特性,在函数式编程中,函数是“头等公民”,可以像其它数据类型一样赋值给变量、传递给其它函数等。

方法定义语法

Scala定义函数需要给出方法名称、参数、返回类型(可选)、方法体。

代码块

Scala

def gcd(x: Int,y: Int): Int= {
  if(x % y) y
  else gcd(y,x%y)
}

如果方法不是递归方法,则方法返回类型可以不指定(对于递归函数,如果没有返回类型无法判断递归调用中的返回类型),Scala通过=右侧的表达式类型推断返回类型。还有一种情况需要给出返回类型,就是当使用return语句时也需要给出函数的返回类型。

定义默认参数方法,这样就可以在调用中使用默认参数。

代码块

Scala

def decorate(str: String,left: String = "[",right: String = "]") = {
  left + str + right
}
//使用默认参数调用
decorate("hello")
//不适用默认参数调用
decorate("hello","<<<",">>>")
//指定名称
decorate(left = "<<<",str = "hello",right = ">>>")

定义变长参数方法:

代码块

Scala

def sum(args: Int*) = {
  var result = 0
  for(arg <- args) result += arg
  result
}
//使用变长参数函数
val result = sum(1,2,3)//会生成一个Seq序列
//不能直接使用序列,因为这样编译器理解只传入了一个参数,并且参数类型不对
val result = sum(1 to 5)
//可以通过“: _*”将Range转换成Seq,告诉编译器传递参数是序列
val result = sum(1 to 10: _*)//将1 to 5当做参数序列来处理

如果不写等号和方法体,则定义的就是抽象方法。

代码块

Scala

def sum(num1: Int,num2: Int): Int

许多书籍、博客等,把上面称为也称为函数定义,我理解之所以这样是因为在面向对象编程中,函数和方法是一样的,而Scala本身即是面向对象编程语言又是函数式编程语言,所以这个地方称为方法定义和函数定义都可以说的通吧 (个人理解,有待考证)。我们只需要知道在类中按照上面的形式定义方法即可。

函数

Scala中函数是一个完整的对象,Scala用23个特质(trait)抽象出来了函数概念,这23个特质分别是Function0~Function22。数字代表可以函数可以传递的参数个数,也就是说Scala支持函数传入最多参数个数是22。

代码块

Scala

trait Function10[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, +R] extends AnyRef { self =>
  def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10): R
  ...
}

我们可以使用下面两种方法定义函数,得到的结果都是一样的。

代码块

Scala

val fun1 = new Function2[Int,Int,Int] {
      override def apply(x: Int, y: Int): Int = {
        x + y
      }
    }
val fun2 = (x:Int,y:Int) => x + y
println(fun1)
println(fun2)
//打印结果都为:<function2>

匿名函数

在Scala中函数定义更通用的是使用上面第二种方式,即匿名函数。无需给出函数名,可以将其赋值给变量或传递给另一个函数。

代码块

Scala

//定义匿名函数,在Java中称为Lambda表达式
(x: Double) => x * 0.2
//将匿名函数赋值给变量
val t = (x:Double) => x * 0.2
//t: Double => Double = <function1>  
//将匿名函数传递给其它函数
Array(1,2,3).map((x:Int) => x * 2)

Scala中将函数赋值给变量的函数称为函数字面量(function literal)或值函数。定义标准函数字面量(值函数)的方式:

代码块

Scala

//包括变量名、函数参数、函数映射符、函数体和返回值
val sum =(x: Int,y: Int)=> {
  x + y
} 

值函数不能像普通函数(方法)那样指定返回值类型,而是通过类型推到来确定函数返回值类型。

值函数在使用过程中,有一些简化形式:

1、值函数省略参数类型,通过类型推到得到。

代码块

Scala

Array(1,2,3).map((x) => x + 1)
//等价于
Array(1,2,3).map((x:Int) => x + 1)
//因为我们已经知道Array数组类型为Int,所以传入值函数的参数类型为Int

2、当值函数中只有一个参数时,可以将输入参数的括号去掉。

代码块

Scala

Array(1,2,3).map(x => x + 1)
//等价于
Array(1,2,3).map((x: Int) => x + 1)
//注意只有省略了类型才能省略括号
Array(1,2,3).map(x: Int => x + 1) //不对

3、当值函数的参数在符号 => 右侧只出现一次的时候,可以使用“_”占位符进行简化。

代码块

Scala

Array(1,2,3).map(_+1)

高阶函数

函数可以传递给另一个函数使用,而接受函数类型参数的函数称为高阶函数。

代码块

Scala

//定义高阶函数,函数的参数类型为函数类型:(Double) => Double。该函数的作用是,对传递的函数使用常量100计算函数结果
def higherOrderFunction(f: (Double) => Double) = {
  f(100)
}
//高阶函数的本质还是一个方法,因为它的类型为:(f: Double => Double)Double

高阶函数除了可以将函数作为参数使用,还可以将函数作为返回值使用。

代码块

Scala

def higerOrderFunction(x: Int): Double => Double = {
  (y: Double) => x * y
}
//函数类型为(x: Int)Double => Double
//当我们调用高阶函数时,返回是一个新的函数
val a = higerOrderFunction(10)
//Double => Double,即需要传递一个Double类型,返回结果也为Double类型。

通过个例子来理解一下高阶函数,我们以数组中的Map为例。map方法定义为:def map[B](f: (A) => B): Array[B],将函数f作用于数组中的所有元素,并返回一个新数组Array[B]。函数f的输入类型为A,输出类型为B。

代码块

Scala

//我们对Map传入一个匿名函数 (x: String) => x * 2
Array("Hadoop","Spark").map((x: String) => x * 2)
//对数组每个元素重复一遍,返回结果Array[String] = Array(HadoopHadoop, SparkSpark)
//取出List中元组的第一个值
List("Spark" -> 1,"Hadoop" -> 2).map((x:(String,Int)) => x._1)

偏函数

如果我们想定义一个函数,让换个函数只处理定义域内的子集,对于这个定义域的参数抛出异常,这样的函数称为偏函数(这个函数只处理传递过来的部分函数)。

偏函数的trait为PartialFunction[-A,+B],接受一个类型为A的参数,输出一个类型为B的结果。

代码块

Scala

val isEven: PartialFunction[Int,String] = {
  case x if x % 2 == 0 => x + "is even"
}
isEven(2)
isEvent(3)
//当传入3时会抛出异常,匹配失败:
scala.MatchError: 3 (of class java.lang.Integer)
  at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:253)
  at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:251)
  at $anonfun$1.applyOrElse(<console>:11)
  at $anonfun$1.applyOrElse(<console>:11)
  at scala.runtime.AbstractPartialFunction.apply(AbstractPartialFunction.scala:36)
  ... 32 elided

在实际使用中,我们可能不希望抛出异常,我们可以使用PartialFunction中的isDefinedAt(a: A):Boolean方法先进性判断,如果传入参数在指定的定义域中,就返回true。

代码块

Scala

isEvent.isDefinedAt(3)

闭包

当我们在定义的匿名函数中使用外部变量时,该变量可能会不断的变化,它处于一种开发状态,而当我们执行函数时,这个自由变量就被确定下来了,这时候我们可以认为运行时该变量处于封闭状态,这种存在由开放到封闭的过程,称为闭包。当然引用的外部变量也可是函数,比如高阶函数。这种类型的函数,称为闭包函数。闭包由代码和代码用到的非局部变量构成。

代码块

Scala

var i = 10
val fun = (x: Int) => x + i
//fun(2)的结果为12
i = 20
//fun(2)的结果为22

方法和函数的区别

1、定义方式和返回类型不同,并且函数不能指定返回值类型。

代码块

Scala

def triple(x: Double) = x * 0.2
//类型为(x:Double)Double,这种类型为方法型
val triple = (x: Double) => x * 0.2
//类型为Double => Double,这种为函数类型

异常处理

Scala的异常处理机制和Java类似,通过throw抛出异常,抛出异常的类型必须是java.lang.Throwable的子类。

代码块

Scala

throw new IllegalArgumentException("args is empty!")

与java不同的是不需要“受检”,即在方法声明中通过throws指出可能会抛出的异常。

同样提供了try-catch、try-finaly、try-catch-finaly捕获处理异常。

代码块

Scala

val in = new URL("https://baidu.com")
try{
  process(in)
}catch {
 case _: MalformedURLException(s"Bad URL: $url")//不使用异常变量,可以使用_代替变量名
 case e: IOException => ex.printStackTrace()
}finaly {
  in.close()
} 

 

集合

数组

在Scala中定义定长的数组使用的是Array,可以为其指定长度,也可以直接初始化。

代码块

Scala

//定义数组长度为10的Int型数组,所有元素初始化为0
val a = new Array[Int](10)
//定义数组长度为10的String型数组,所有元素初始化为null
val b = new Array[String](19)
//长度为2的String数组,类型是通过推断得出的。当提供初始值后,不需要new了
val c = Array("Hello","World")
//访问数据使用()
println(c(0))

对于变长数组,在Java中使用ArrayList,Scala提供了ArrayBuffer数组缓冲。

代码块

Scala

import scala.collection.mutable.ArrayBuffer
//一个空的数组缓冲
val array = ArrayBuffer[Int]()
//末尾添加一个元素
array += 1
//在末尾添加多个元素
array += (2,3,4)
//可以使用 ++= 操作符添加任何集合
array ++= Array(5,6,7)

在数组缓冲末端添加元素是高效的。也可以在任意位置插入和删除元素,但这样操作的性能并不高,会平移元素。

代码块

Scala

//在下标2处添加一个元素
array.insert(2,10)
//在下标2处添加任意个元素
array.insert(2,11,12,13)
//移除一个元素
array.remove(2)
//移除多个元素
array.remove(2,3,4)

还有一种操作就是先构建一个数组缓冲,添加完元素后在转换成Array。也可以将Array转换成ArrayBuffer

代码块

Scala

array.toArray
a.toBuffer

在Scala中定义多维数组的形式是Array[Array[Double]]这种形式的。比如我们要构造一个3行4列的数组,可使用ofDim方法。

代码块

Scala

val matrix = Array.ofDim[Double](3,4)//3行4列的2维数组
//访问数组
matrix(1,2)

映射

Scala可以提供了两种类型的映射,一种是可变的Map,一种是不可变的Map。

代码块

Scala

val map1 = Map("Java" -> "Hadoop","Scala" -> "Spark")//默认为不可变Map,scala.collection.imutable.Map
val map2 = scala.collection.mutable.Map[String,String]() //可变空的Map
val map3 = Map(("Java","Hadoop"),("Scala","Spark"))//使用元组创建
//获取映射值
val tc = map1("Java")//如果获取的kv不存在,则会抛出异常
val tc = map1.getOrElse("Java","Nothing")//如果存在则返回对应的value,不存在返回“Nothing”
val tc = map1.get("java")//返回Option对象

可变映射中,可以修改kv或添加新的kv对。

代码块

Scala

map2("Java") = "Kafka"//修改kv
map2 += ("Go" -> "Consul")//新增kv
map2 -= "Java" //移除kv

不能对不可变的映射进行修改、新增操作,但是对于不可变映射有类似的操作,只不过是生成新的Map。

代码块

Scala

val map4 = map1 + ("Go"->"Consul")//新增kv
var map5 = map1 - "Java"//移除kv
map5 += ("Go" -> "Consul") //对于var类型,可以直接更新

新老映射效率不会太低,因为它们共享大部分结构。

Map的迭代方式非常灵活使用,可以直接迭代kv,也可以像java一样取出key的集合或value的集合。

代码块

Scala

for((k,v) <- map1) {//处理}
map1.keSet //获取key集合
for(v <- map1.values) println(v) //遍历所有value
val newMap = ((k,v) <- map1) yield (v,k) //kv反转

Map默认使用键的哈希码来划分定位的,所以它的顺序是不可预期的。如果要按照次序访问键值对,可以使用scala.collection.mutable.SortedMap。如果要按照插入顺序访问键值,可以使用scala.collection.mutable.LinkedHashMap。

元组

元组是不同类型的值的聚集,映射中的键值对偶就是元组的最简单形式。

代码块

Scala

//元组形式
val tup = (1,"Hello",3.14,true)//它的类型(Int, String, Double, Boolean) = (1,Hello,3.14,true)
//访问元组
println(tup._2)//tup._n 访问下标为n的元组,需要注意元组下标从1开始

元组可以用于函数返回不止一个值的情况,比如StringOps.partition()方法,用于返回满足某个条件和不满足某个条件的结果。

代码块

Scala

//返回大写和非大写字符
scala> "Hello World".partition(_.isUpper)
res30: (String, String) = (HW,ello orld)

使用元组可以把多个值绑在一起,以便一起来处理,这通常使用zip来完成。

代码块

Scala

val lan = Array("java","scala")
val tc = Array("Hadoop","spark")
val pair = lan.zip(tc)
//返回结果:Array[(String, String)] = Array((java,Hadoop), (scala,spark))
//可以直接转换成map
pair.toMap

简单类的构造和访问

Scala类的简单形式和Java类似:

代码块

Scala

class Counter {
  private var value = 0
  def increment() = {
    value += 1
  }
  def current() = value
}

构造对象,并且访问类方法。如果使用的无惨构造函数,则创建对象时可以省略括号。同样,在调用无参构造函数时,也可以省略括号。一般对于修改值的方法(改值器)使用(),对于取值器去掉()的编程风格。当声明方法时不带(),则在调用方法时也不能带有()。

代码块

Scala

val myCount = new Counter //或new Counter()
myCount.increment()
println(myCount.current)

带有getter/setter属性

在Java中我们一般将变量声明为私有,然后通过getXxx和setXxx方法来访问和修改变量(这样的好处是我们可以在访问和修改方法中做一些业务判断)。在Scala中当声明变量后会自动生成对应的getXxx和setXxx方法。Scala中的getter和setter分别叫做xxx和xxx_=。

代码块

Scala

class Person {
  var age = 0
  //默认生成
}
println(p.age) //将调用p.age()方法
p.age = 2 //将调用p.age_= (2)方法
//我们也可以显示修改get和set方法
class Person {
  private var privateAge = 0
  def age = privateAge
  def age_= (newAge: Int) = {
    if(newAge > 10) privateAge = newAge
  }
}
//直接使用age
p.age = 10
p.age = 9

Scala的setter和getter的一些使用规则:

  • 如果字段是私有的,则getter和setter方法也是私有的。

  • 如果字段是val类型,则只有getter方法。

  • 如果不需要任何getter和setter方法,可以将字段声明为private[this]。

  • 可以根据需要重写getter和setter方法。

  • Scala的中getter和setter是自动生成的,即便在类中没有看到对应的方法,也可以直接使用。

我们也可以生成Java风格的getter和setter,通过使用@BeanProperty注解来完成。

代码块

Scala

class Person {
  @BeanProperty
  var age = 0
}

使用@BeanProperty除了会生成Scala风格getter和setter方法,还会生成getXxx和setXxx(对于var类型变量)

构造器

主构造器

在Scala中每个类都有主构造器,主构造器的定义形式和java不同,Scala主构造器是在类定义时就声明了。

代码块

Scala

class Person(val name:String,val age:Int){
  //...
}

主构造器的参数是直接放在类名后,当我们使用主构造器创建对象时,传入的参数就会初始化主构造参数。

代码块

Scala

val p = new Person("yjz",26)

主构造器的内容为类中定义的所有语句,即所有方法体外的语句。

代码块

Scala

class Person(val name:String,val age:Int){
  println(s"$name,$age")
}

主构造器参数也可以使用默认参数,这样在构造主构造器时可以忽略参数,启动减少定义辅助构造器的作用。

代码块

Scala

class Person(val name:String = "",val age:Int = 0){
  println(s"$name,$age")
}

我们也可以定义私有主构造器,这样主构造器只能在类内部使用。使用private关键词定义私有主构造器。

代码块

Scala

class Person private (var name: String,age: Int) {}

辅助构造器

Java中可以创建多个构造函数,但是需要和类名一直,当我们修改类名时这些构造函数的名称也需要一起修改。而在Scala中我们除了定义主构造函数,也可以定义多个辅助构造函数,辅助构造函数定义的名称使用this关键字。

代码块

Scala

public Person() {
  var name: String = _
  var age: Int = _
  var sex: Int = _
  this(name:String) {
    this()
    this.name = name
  }
 
  this(name:String,age:Int) {
    this(name)
    this.age = age
  }
  this(name:String,age:Int,sex:Int) {
    this(name,age)
    this.sex = sex
  }
}
//创建对象
val p1 = new Person()
val p2 = new Person("yjz")
val p3 = new Person("yjz",26,1)

每个辅助构造函数都必须以它先前定义的其它辅助构造函数或主构造函数调用开始,所以上面我们定义的辅助构造函数都会先调用其它构造函数。

Scala对象

单例对象

Scala本身没有提供定义静态字和静态方法,我们可以使用Object来达到这个目的。通过Object字段来定义单例对象,我们可以定义某个类的单例:

代码块

Scala

object Accounts {
  private var lastNumber = 1
  def newUniqueNumber() = {
    lastNumber += 1
    lastNumber
  }
}

我们可以使用Scala提供的Object对象来完成一下特性:

  • 定义常量和工具函数(我们通常用的工具类和常量类)。

  • 高效共享单个不可变的实例。

  • 需要单个实例来协调某个任务(单例模式)

伴生对象

在Java中我们可以在类中定义实例方法和静态方法,但是在Scala中类中的静态方法和静态常量是通过伴生对象来完成的。

代码块

Scala

class Accounts {
  val id = Accounts.newUniqueNumber()
  private var balance = 0.0
  def deposit(amount: Double) balance += amount
}
object Accounts {
  private var lastNumber = 1
  def newUniqueNumber() = {
    lastNumber += 1
    lastNumber
  }
}

object单独定义是类的单例对象,当和对应的class(类名和对象名相同)一起定义在同一个文件中时,object定义称为伴生对象,class定义称为伴生类。类和伴生对象可以相互访问对方的私有特性。

apply方法

Scala在对象中提供了apply方法,当我们使用对象名加参数直接调用时,就是调用对象中的apply方法。

代码块

Scala

Object(arg1,..,argn)//默认调用的是Object.apply(arg1,..,argn)方法
//比如创建数组
val tc = Array['hadoop','spark']//并没有使用new关键词,而是调用的Array.apply()方法,在apply方法中Array会根据参数定义类对象。

一般apply方法返回的是伴生类对象。

代码块

Scala

class Accounts private (val id: Int,initBalance: Double){
  private var balance = initBalance
 
}
object Accounts {
  def apply(initBalance: Double) {
    new Accounts(newUniqueNumber(),initBalance)
  }
  private var lastNumber = 1
  def newUniqueNumber() = {
    lastNumber += 1
    lastNumber
  }
}
//我们在创建对象时就可以直接使用类名了
val ac = Accounts(1000)

启动类main方法

我们知道了Scala中的静态常量和静态方法都定义在对象中,而Java中启动类的main方法也是静态方法,所以可以知道Scala的启动类main方法也是定义在对象中的。

代码块

Scala

object BootStrap {
  def main(args: Array[String]): Unit = {
    //main方法
  }
}

访问权限

Java中提供了4中访问控制全新啊,分别是:private、package(默认)、protected和public。private为私有访问,private修饰的变量和方法只能在同一个类中被访问。package为默认访问,又称包访问,允许成员变量和方法被同一个包内的其它类访问。protected为受保护访问,protected修饰的成员变量和方法在同一个包中可以被访问,包外如果想要访问的需要为带有protected成员类的子类。public为共有访问,表示在任何地方都可以访问。

Scala使用自己一套访问控制,访问控制权限分为3种:默认访问控制、private访问控制和protected访问控制。当不加任何修饰时,使用的是默认访问控制,对应java中的public关键字修饰。private、protected和Java中的private、protected类似,只不过可以同包一起使用来限制包中的访问控制。我们可以使用private[x]、protected[x]来灵活定义访问控制,这种访问控制的意思是将private、protected限定到x内,x可以是包、类、单例对象。

访问控制

作用域

访问控制

作用域

private访问控制

只能在本类及伴生对象中访问

protected访问控制

在该类和子类都可以访问

默认访问控制

任何位置都能访问

private[this]访问控制(称为类的私有成员)

只能在该类中被访问,在伴生对象中也不能访问。

private[包名]

只能在指定的包及其子包中使用

protected[包名]

和private[包名],只不过子类也有相同的权限

需要知道的一点,当我们在构造主构造函数时,如果没有使用var或val修饰,则定义的变量访问权限为private[this]。

代码块

Scala

class Person(name: String,age: Int)
//等同于
class Person(private[this] name: String,private[this] age: Int)

猜你喜欢

转载自blog.csdn.net/colin_yjz/article/details/82901151
今日推荐