写在前边的话:
1:什么是Scala?
Scala是一门多范式的编程语言,类似于Java,并集成了面向对象编程和函数式编程的各种特性,具体可参考知乎上的一个帖子
2:本篇博客包含哪些内容?
Scala中变量的声明与函数定义
Scala中的控制结构
Scala中的数据类型
Scala中的类详解
1:变量声明与函数定义
变量声明:val 和 var ,两者的区别是val声明的变量是不可变的,而var声明的变量可变
eg:
scala> val a = 12; a: Int = 12 scala> a = 13 <console>:12: error: reassignment to val a = 13 ^ scala> var b =11 b: Int = 11 scala> b = 12 b: Int = 12
函数定义:
带返回值:
scala> def max(x:Int,y:Int):Int = { | if(x>y) x | else y | } max: (x: Int, y: Int)Int scala> max(1,2) res5: Int = 2不带返回值:
scala> def helloworld()=println("HelloWorld") helloworld: ()Unit
2:控制结构
1) 判断(if)
类似于上边函数定义中的max函数
2) 循环(while/do)
while 语句包括状态判断和循环体,只要当前状态判断为真,就执行循环体一遍,然后进行下一状态判断,判断为假时终止判断,形如 while (A)B
do语句和while相反,先执行循环体一遍,然后进行状态判断,状态判断为真,则继续执行循环体,否则终止循环,形如 do B while(A)
scala> var m =3 m: Int = 3 scala> while(m!=0){ | println(m) | m -= 1 | } 3 2 1 scala> var n =3 n: Int = 3 scala> do{ | println(n) | n -= 1 | }while(n!=0) 3 2 1
3) 枚举(for)
for的两种实现
scala> for(i<- 1 to 3) | println(i) 1 2 3 scala> for(i<- 1 until 3) | println(i) 1 2
4) 匹配(match表达式)
scala中的match类似于其他语言中的switch,从上往下进行匹配
scala> val a = "gyt" a: String = gyt scala> a match{ | case "gyt" => println("OK") | case "cyan" => println("no") | } OK
5) 异常处理(throw/try)
Scala通过throw抛出一个异常,其异常捕获和处理与java十分类似
scala> if(true){ | println("throw new exception") | throw new IllegalArgumentException | } throw new exception java.lang.IllegalArgumentException ... 35 elided
scala> try{ | val file = new FileReader("input.txt") | }catch{ | case ex: FileNotFoundException => //handle missing file | case ex: IOException => //handle other I/O error | }finally{ | println("end") | } end
6) 输出(print/println)
一个不换行输出,一个换行输出
7) 输入(readline)
输入通常使用read函数,readline是从控制台输如一行,指定类型为readT,T为类型,例如readInt
scala> val name = readLine("Your Name: \n") warning: there was one deprecation warning; re-run with -deprecation for details Your Name: name: String = thinkgamer
8) 其他语句
return语言表示返回某个值,但是Scala事实上无需使用return语句,对于函数来说,其默认返回值是最后出现的一个值,不用特别注明,如需要返回的值,并非最后出现时,可在函数体后加上该值的标识符使之出现,声明函数时使用return语句,必须声明返回值类型,例如 def max:T = {return}
break/continue在C++中非常常见的控制结构语句,但在Scala中是不必要的,可以使用布尔值类型的两通过if语句进行控制
3:数据结构
1) 数组
使用new来实例化一个类,当你创建一个对象的实例时,你可以使用数值或者类型参数
scala> val abc = new Array[String](3) abc: Array[String] = Array(null, null, null) scala> abc(0)="thinkgamer" scala> abc(1)="cyan" scala> abc(2)="GQ" scala> for(i<- 0 to 2) | println(abc(i)) thinkgamer cyan GQ这里也说明下为什么Scala使用()来访问数组元素,在Scala中,数组和其他普遍的类的定义一样,没有什么特别之处,当你某个值后面使用()时,Scala将其翻译成对应对象的apply方法,因此本例中abc(0)其实调用abc.apply(0)方法,这种表达方法不仅仅只限于数据,对于任何对象,如果在其后面使用(),都将调用该对象的apply方法,同一,日过对某个使用()的对象复制,比如
abc(0)="thinkgamer"
scala将这种复制转换为该对象的update方法,也就是abc.update(0,"thinkgamer"),因此上边的例子也可以使用传统的方法调用,可以写成:
scala> val new_abc = new Array[String](3) new_abc: Array[String] = Array(null, null, null) scala> new_abc.update(0,"aaa") scala> new_abc.update(1,"bbb") scala> new_abc.update(2,"ccc") scala> for(i<- 0 to 2) | println(new_abc(i)) aaa bbb ccc从这点来收,数组在scala中并不是某种特殊的数据类型,和普通的类并没有什么区别
不过scala还是提供了初始化数组的简单的方法,上述的例子可以这样写:
scala> val abc = Array("thinkgamer","cyan","GQ") abc: Array[String] = Array(thinkgamer, cyan, GQ)数组的输出可以采用while或者foreach或者for进行输出
scala> val abc = Array("thinkgamer","cyan","GQ") abc: Array[String] = Array(thinkgamer, cyan, GQ) scala> var i = 0 i: Int = 0 scala> while(i<abc.length){ | println(abc(i)) | i+=1 | } thinkgamer cyan GQ scala> abc.foreach(str=>println(str)) thinkgamer cyan GQ
2):Lists
Scala的List和Java不同,不能被改变,这样做的一个好处是方法与方法之间关联性较小,从而方法变得更可靠和重用性高,使用这个规则也就意味着变量的设置是不可修改的,这也就避免了多线程访问的互锁问题
scala> val one = List(1,2,3) one: List[Int] = List(1, 2, 3) scala> val two = List(4,5) two: List[Int] = List(4, 5) scala> val three = one:::two three: List[Int] = List(1, 2, 3, 4, 5):::方法表示连接两个列表,当然列表定义了::方法(右操作符),用于向列表添加元素
scala> val four = three::6::7::Nil four: List[Any] = List(List(1, 2, 3, 4, 5), 6, 7) scala> val five = 1::2::3::4::Nil five: List[Int] = List(1, 2, 3, 4)Nil表示空列表
Scala的List类还定义了其他很多很有用的方法,比如head,last,length,reverse,tail等这里就不一一说明了,具体可以参考List的文档
3) Tuples
Scala中另外一个很有用的容器类是Tupels,和Lists不同的Tuples可以包含不同类型的数据,而List只能包含同类型的数据,Tuples在方法需要返回多个结果时非常有用(Tuple对应数学的矢量的概念)一旦定义了一个元组,可以使用._和索引来访问元组的元素(矢量的分量,注意和数组不同的是,元组的索引从1开始)
scala> val pair = (22,"one") pair: (Int, String) = (22,one) scala> println(pair._1) 22 scala> println(pair._2) one元组的实际类型取决于它的分量的类型,比如上边的pair的类型实际为Tuple2[Int,String],目前Scala支持的元组的最大长度为22,如果有需要,你可以扩展更长的元组
4) Set
scala> var set = Set("a","b") set: scala.collection.immutable.Set[String] = Set(a, b) scala> set+="c" scala> set res8: scala.collection.immutable.Set[String] = Set(a, b, c) scala> println(set.contains("c")) true缺省情况Set为Immutable Set,如果你需要使用可修改的集合类(Set类型),你可以使用全路径来指明Set,比如scala.collection.mutalbe.Set
5) Map
Map的基本用法如下(Map类似于其他语言中的关联数据如PHP)
scala> val roman =Map(1->"I",2->"II") roman: scala.collection.immutable.Map[Int,String] = Map(1 -> I, 2 -> II) scala> println(roman(1)) I scala> println(roman(2)) II
4:类与对象
1) 单例与伴生对象
scala比java更面向对象的一个方面是Scala没有静态成员,替代品是scala有单例对象(singleton object),当单例对象与某个类共享同一个名称时,他被称作是这个类的伴生对象(companion object),你必须在同一个源文件里定义类和他的伴生对象,类被陈我给是这个单例对象的伴生类(companion class),类和他的伴生对象可以互相访问其私有成员,例如,一个源文件大致如下:
object A{ //A 是一个单例对象 //Object A 与Class A同名,故 是class A 的伴生对象 } class A{ // 类A与单例对象A同名 // 故class 是 object A 的伴生类 //两者可以互相访问私有成员 }定义单例对象不是定义类型(在Scala的抽象层次上说)
类和单例对象的一个差别是,单例对象不带参数,而类可以,因为你不能用new关键字实例化一个单例对象,你没机会传递给他参数,每个单例对象都被作为由一个静态变量指向的虚构类,synthetic class的一个实例来实现,因此他们与java静态类有着相同的初始化语法。Scala程序特别要指出的是:单例对象会在第一次被访问的时候初始化
不与伴生类共享名称的单例对象成为孤立对象:standlone object。最常见的就是程序入口:
scala> object ObjecOps { | def main(args: Array[String]): Unit = { | println(args.length) | } | } defined object ObjecOps scala> var arr = Array("1","2") arr: Array[String] = Array(1, 2) scala> ObjecOps.main(arr) 2
2) 主构造器和辅助构造器
主构造器:每个类都有主构造器,且与类的定义交织在一起,主构造器的参数紧跟在类名之后,如 class helloworld(val hello:String,val world:String){...},主构造器的参数在传入时,就已经被编译成字段,并在构造对象时初始化传入,一个类若没有显式定义主构造器,自动拥有一个无参主构造器,若类中有直接执行的语句(非定义的方法、函数等),每次构造对象时皆会执行一次,不论是什么样的构造器类型,如:
scala> class HelloWorld(val v1:String,val v2:String){ | println("Hello World") | val v3 = v1+v2 | } defined class HelloWorld scala> val one = new HelloWorld("welcome","thinkgamer") Hello World one: HelloWorld = HelloWorld@6afe925b scala> val two = new HelloWorld("My Name is","thinkgamer") Hello World two: HelloWorld = HelloWorld@1b35c4f8辅助构造器:Scala类可以有任意多个构造器,辅助构造器的名称为this,在类中定义,辅助盗走阿七必须以一个主构造器活其他一定义的辅助构造器调用开始,例如:
scala> class GYT{ | private var v1 = "" | private var v2 = "" | def this(m:String) { | this() | this.v1=m | } | def this(m:String,n:String) { | this(m) | this.v2 = n | } | def get(){ | println(v1+"\t" + v2) | } | } defined class GYT scala> var one = new GYT("A") one: GYT = GYT@3b2e9a90 scala> one.get() A scala> var one = new GYT("A","B") one: GYT = GYT@753e481 scala> one.get() A B
3) 嵌套类
scala允许任何语法结构中嵌套任何语法结构,因此能在类中定义类,例如:
scala> class A{ | class B{ | println("this B") | } | println("this A") | } defined class A scala> val one = new A() this A one: A = A@7ee14288对于同一个外部类,不同实例下的内部类是不同的,形如val one = new A() 与val two = new A(),one.B 和 two.B是两个不同的类
内部类中可以调用外部类的策划那个元,利用外部类.this或指针实现,例如:
scala> class Hello{ | pointto=> | val value1 = "" | class Hi{ | val value2 = Hello.this.value1 | val value3=pointto.value1 | } | } defined class Hello scala> val one = new Hello one: Hello = Hello@25703a98 scala> val two = new one.Hi two: one.Hi = Hello$Hi@617599ab scala> two.value2 res4: String = "" scala> two.value3 res5: String = ""java的内部类(嵌套类是从属于外部类的,而scala的内部类是从属于对象的),再看一个实例加深对scala内部类的理解:
scala> //定义外部类 scala> class Outer(val name:String){ | outer=> | class Inner(val name:String){ | def foo(b:Inner)=println("Outer" + outer.name+" Inner" + b.name) | } | } defined class Outer scala> val out1 = new Outer("Spark") out1: Outer = Outer@1fed28ce scala> val out2 = new Outer("Scala") out2: Outer = Outer@5f5fe5b9 scala> val in1 = new out1.Inner("hadoop") in1: out1.Inner = Outer$Inner@537d147e scala> val in2 = new out2.Inner("Hbase") in2: out2.Inner = Outer$Inner@7376d6c3 scala> in1.foo(in1) OuterSpark Innerhadoop scala> in2.foo(in2) OuterScala InnerHbase
4) apply方法
需要构造有参数需求的伴生对象时,可定义并使用apply方法,例如:
scala> class Hello(var m:String,n:Char){ | println(m + "\t" + n) | } defined class Hello scala> object Hello{ | def apply(n:Char) = new Hello("|" ,n ) | } defined object Hello warning: previously defined class Hello is not a companion to object Hello. Companions must be defined together; you may wish to use :paste mode for this. scala> val hi = Hello('j') | j hi: Hello = Hello@111648d0在创建实例时若使用new来创建则使用的是class,若直接使用雷鸣创建对象则使用的是object创建对象
5) 扩展
extends是Scala中实现继承的保留字,形如 class week extends month{...},week类继承了month类的所有非私有成员,在这里week是month的子类,month是week的超类,子类能够重写超类的成员(具有相同的名称和参数),另外单例对象同样能从类中继承,与类的继承语法相同
6) 重写
Scala中使用override保留字进行方法、字段重写,形如:class week extends month{ override def firstday = {...} }
override保留字实际使用类似于private,声明这个保留字后的定义、声明是对超类的重写,因此,其也可以写在类定义的参数中,形如:
class week(override val lastday:String) extends month{...}
重写包括字段和方法,单参数不同的方法可以不重写,例如
class month {def secondday (m:String) = {...} }
class week extends month { def secondday = {...} }
进行重写时有以下一些规则:
重写def:用val,利用val能重写超类用没有参数的方法(getter)
用def,子类的方法与超类方法
用var,同时重写getter、setter方法,只重写getter方法报错
重写val:用val,子类的一个私有字段与超类的字段重名,getter方法重写超类的getter方法
重写var:用var,且当超类的var是抽象的才能被重写,否则超类的var都会被继承
看一个包含重写,继承和主构造器的例子:
scala> class Month{ | var day = 0 | private var month = 0 | def this(now_day:Int,now_month:Int){ | this() | this.day = now_day | this.month = now_month | } | def add(){ | day+=1 % 30 | month += (day / 30) | println("day is:" + day + "\nmonth is:" + month) | } | } defined class Month scala> class Week extends Month{ | private var week = 0 | def this(now_day:Int,now_week:Int){ | this() | this.day = now_day | this.week = now_week | } | override def add():Unit={ | day+=1% 7 | week += (day/7) | println("day is:" + day + "\nweek is:" + week) | } | } defined class Week scala> var m1 = new Month(1,1) m1: Month = Month@2bda037e scala> m1.add day is:2 month is:1 scala> m1.add day is:3 month is:1 scala> var w1 = new Week(1,1) w1: Week = Week@442634e9 scala> w1.add day is:2 week is:1 scala> w1.add day is:3 week is:1 scala> m1.add day is:4 month is:1
7) 抽象
不能被实例化的类叫做抽象类,抽象类的某个或者某几个成员没有被完整定义,这些没有被完整定义的成员称为抽象方法或抽象字段,用abstract保留字标记抽象类,例如:
scala> abstract class year{ | val name:Array[Srting] //抽象的val,带有一个抽象的getter方法 | var num:Int //抽象的var,带有抽象的getter/setter方法 | def sign //没有方法体/函数体,是一个抽象方法 | }只要类中有任意一个抽象成员,必须使用abstract标记
重写抽象方法,抽象字段不需要使用override保留字
scala> abstract class animal{ | var name :String | var age : Int | def show | } defined class animal scala> class dog extends animal{ | override var name ="dog" | override var age = 23 | override def show(){ | println("name:" + this.name + "\tage: " + this.age) | } | } defined class dog scala> var d = new dog d: dog = dog@4a1c2a40 scala> d.show name:dog age: 23 //重写抽象方法不需要使用override保留字 scala> class pig extends animal{ | var name ="pig" | var age = 23 | def show(){ | println("name:" + this.name + "\tage: " + this.age) | } | } defined class pig scala> var p = new pig p: pig = pig@6bf5210 scala> p.show name:pig age: 23
8) 保护
当一个类不希望被继承、拓展时,可在类声明前加上final保留字,形如 final class year{...}
当一个类的某些成员不希望被重写时,可以在成员声明前加上final保留字,形如 class year {final def sign{...} }
当超类中的某些成员需要被子类继承,又不想对子类以外成员可见时,在成员声明前加上protected保留字,protected[this],将访问权限定于当前对象,类似于private[this]
类中protected的成员对其子类可见,对其超类不可见,形如:class year{ protected def sign {...} }
“保护”的应用:
子类构造器的运行在超类构造器运行之后,在超类的构造器中调用的成员被子类重写后,返回值可能不正确,如下边的例子
scala> class month{ | val num = 31 | val days = new Array[Int](num) | } defined class month scala> class week extends month{ | override val num = 7 | } defined class week scala> val a = new week a: week = week@5c4e3bb0 scala> a.days res4: Array[Int] = Array()我们可以看到 Array的长度变成了0,但是我们month类中明明定义为num长度,原因为何?
构造week对象前先执行超类month的构造器,num被初始化为31,month为初始化days数组,调用num,但是num被子类重写了,但因为week构造器还没被调用,此时num的值未被初始化,因而为0,days被设置为长度为0的数组,month构造器运行完毕,执行week构造器,num被初始化为7
解决办法:将超类的val声明为final,将超类的val声明为lazy
scala> class month{ | final val num = 31 | val day = new Array[Int](num) | } defined class month scala> class week extends month{ | } defined class week scala> val w = new week w: week = week@3794d72e scala> w.day res10: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) scala> class month{ | lazy val num = 31 | val day = new Array[Int](num) | } defined class month scala> class week extends month{ | } defined class week scala> val w = new week w: week = week@183fde2b scala> w.day res11: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)另外一中办法是 在子类中使用提前定义语法,那么什么事提前定义?
提前定义是在超类的构造器运行之前初始化子类的字段,把需要提前定义的语句块放在extends与超类之间,并后接with保留字,形如
class week extends {override val num= 7} with month{...}
提前定义中=号右侧若需调用类B中成员时,除非B成员已在调用前提前定义
scala> class week extends{ | override val num = 7 | override val num2 = num +1 //允许,num已被提前定义 | override val num4= num2+num3 //不允许,num3没有在之前提前定义 | } with month{...}
9) 特质
Scala不支持多重继承,取而代之的是特质,一个子类只能拥有一个超类,一个超类能拥有多个子类,即class week extends month,year是不合法的,若一个子类继承自不同的超类,不同的超类中同名子类不知如何处理,多重继承会产生菱形继承问题,解决多重继承可能导致的问题消耗的资源远比多重继承产生的价值高
特质定义
Scala里代码复用的基础单元,封装了方法和字段的定义,一个类可以扩展自一个或多个特质,一个特质可以被多个类扩展,而且特质能限制被什么样的类所扩展,特质的定义使用的保留字是trait,具体语法和类定义相似,除了不能拥有构造参数,比如说我们来定义一个特质:
scala> trait reset{ | def reset(m:Int,n:Int)=if(m>n) 1 | } defined trait reset一旦特质被定义了,就可以混入类中
scala> class week extends reset{ | println("add class") | } defined class week当要混入多个特质时,利用with保留字
scala> trait A{ | } defined trait A scala> trait B{} defined trait B scala> trait C{} defined trait C scala> class week extends A with B with C {} defined class week
特质的性质
成员时可以抽象的,而且不需要使用abstract声明,同样的,重写特质的抽象方法无需给出override,但是多个特质重写同一个特质的抽象方法需给出override,除了在类定义中可以混入特质,在特质定义中也可以混入特质
scala> abstract trait A{ | var a:Int | def show | } defined trait A scala> trait B extends A{ | override var a:Int | def show(){ | println() | } | } defined trait B在对象构造时也可以混入特质,形如 val one = new month with resering
特质的构造也是有顺序的,从左到右被构造,构造器按超类->父特质->第一个特质->第二个特质(父特质不重复构造)->类 的顺序构造
如果 class A extens B1 with B2 with B3 ...那么串联B1,B2,B3...等特质,去掉重复项且右侧胜出
特质的应用
(1):接口,根绝已有的方法为类添加方法
实例,利用特质实现富接口,即构造一个具有少量方法和大量抽象方法的具体方法的特质,那么只要把特质混入类中,通过类重写抽象方法后,类便可以自动获取大量的具体方法
scala> trait Logger{ | def log(msg:String) | def warn(msg:String){} | def server(msg:String) {} | } defined trait Logger scala> class week extends Logger{ | def log(msg:String){ | println(msg) | } | server("test") | } defined class week
(2) 为类提供可堆叠的改变(super保留字)
当为类添加多个互相调用的特质时,从最后一个开始进行处理,在类中super.foo()这样的方法调用时静态绑定的,明确是调用它的父类的foo()方法,在特质中写下了super.foo()时,它的调用是动态绑定的,调用的实现在每一次特质被混入到具体类的时候才被绑定,因此特质混入的次序的不同其执行效果也就不同
scala> abstract class IntQueue{ | def get():Int | def put(x:Int) | } defined class IntQueue scala> class BasicIntQueue extends IntQueue{ | private val buf = new scala.collection.mutable.ArrayBuffer[Int] | def get()=buf.remove(0) | def put(x:Int){ | buf += x | } | } defined class BasicIntQueue scala> trait Incrementing extends IntQueue{ | abstract override def put(x:Int){ | super.put(x+1) | } | } defined trait Incrementing scala> trait Doubling extends IntQueue{ | abstract override def put(x:Int){ | super.put(2*x) | } | } defined trait Doubling scala> object TestClient extends App{ | val queue = (new BasicIntQueue with Incrementing with Doubling) | queue.put(2) | println(queue.get()) | | val queue2 = (new BasicIntQueue with Doubling with Incrementing) | queue2.put(2) | println(queue2.get()) | } defined object TestClient
PS:终于写完了,好累.....