基本数据类型包括Byte、Short、Int、Long、Char、Float、Double、String、Boolean。
Scala变量 从变量声明角度看,只有两种类型,val和var,对于val声明的变量,在声明时就必须初始化,并且不可变。对于var声明的变量,是可变的。声明变量的语法如下:
val 变量名[:数据类型] = 初始值
var 变量名[:数据类型] = 初始值
Scala提供了一种类型推断机制(Type Inference),它会根据初始值自动推断变量的类型。因此,若给出了初始值,数据类型可以省略。
import io.StdIn._
var i = readInt()
var f = readFloat //可以不加括号
var str = readLine("Please input your name") //会在控制台输出"Please input your name",并读取用户输入的一整行
val i = 10
val f = 3.5452
val s = "hello"
println(s"$s:i=$i,f=$f") //s插值字符串,输出hello:i=10,f=3.5452
println(f"$s:i=$i%-4d,f=$f%.1f") //f插值字符串,输出hello:i=10 ,f=3.5
import java.io.PrintWriter
object test {
def main(args: Array[String]): Unit = {
val i = 9
val outputFile = new PrintWriter("test.txt")
outputFile.println("hello,world") //也可以使用print,printf
//若需要实现数值类型的格式化写入,可以使用String类的format方法,或者用f插值字符串,例如下
outputFile.print("%3d --> %d\n".format(i,i*i)) //向文件写入“ 9 --> 81”
outputFile.println(f"$i%3d --> ${i*i}%d")//与上句等效
outputFile.close()
}
}
import scala.io.Source
object test {
def main(args: Array[String]): Unit = {
val inputFile = Source.fromFile("test.txt")
for (line <- inputFile.getLines()) println(line)
inputFile.close()
}
}
/*输出结果如下
hello,world
9 --> 81
9 --> 81
*/
val a = if (6>0) 1 else -1
println(a) //输出1
for (变量 <- 表达式 if 条件表达式) 语句块
【例1】
val arr = Array(1,3,4,6,7,12,31)
//或者for(i <- 1 to arr.length-1)
for(i <- 1 until arr.length)
print(arr(i)+" ")
//输出3 4 6 7 12 31
【例2】
for (i <- 1 to 3 if i%2 == 0) print(i+" ")
//输出2
【例3】
for (i <- Array(3,5,6) print(i+" ")
//输出3 5 6
import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException
try{
val f = new FileReader("input.txt")
//文件操作
//......
}catch{
case ex: FileNotFoundException => ...//文件不存在时的操作
case ex: IOException => ... //发送I/O错误时的操作
}finally{
file.close()//确保关闭文件
}
import util.control.Breaks._//导入Breaks类的所有方法
val array = Array(1,3,10,5,4)
//break用法
breakable{
for(i <- array){
if(i>5) break //跳出breakable,终止for循环,相当于java中的break
println(i)
}
}//输出1,3
//continue用法
for(i <- array){
breakable{
if(i>5) break //跳出breakable,终止当此循环,相当于java的continue
println(i)
}
}//输出1,3,5,4
1.数组
数组(Array)是一种可变的、可索引的、元素具有相同类型的数据集合。数组的具体用法如下所示:
//1.数组的声明初始化
val intValueArr = new Array[Int](3) //显示给出类型参数Int定义一个长度为3的整型数组
val myStrArr = Array("BigData","Hadoop","Spark") //使用Scala中伴生对象的apply方法来生成一个对象,自动推导数据类型
//2.访问数组的方法和java不同,使用圆括号加从零开始的下标访问数组数据
val array = new Array[Int](3)
array(0) = 2
println(array(0))//输出2
//3.多维数组定义方法
val myMatrix = Array.ofDim[Int](3,4)
val myCube = Array.ofDim[String](3,2,4) //其数据类型其实是Array[Array[Array[String]]]
//多维数组访问方法同一维数组,比如
println(myMatrix(0)(2)) //输出0
2.元组
Scala的元组是对多个不同类型对象的一种简单封装。Scala提供了TupleN类(N的范围为1~22),用于创建一个包含N个元素的元组。构造一个元组的语法如下
val tuple = ("BigData",2015,45.0)//初始化方法
val (t1,t2,t3) = tuple //提取数据方法
//可以使用tuple._+从1开始的索引进行访问元组数据
println(t1 +" == "+ tuple._1)//输出BigData == BigData
println(t2 +" == "+ tuple._2)//输出2015 == 2015
println(t3 +" == "+ tuple._3)//输出45.0 == 45.0
3.容器
下图为scala.collection包中容器的宏观层级关系(省略了很多细粒度的特质)。所有容器的根为Traverable特质,表示可便利的,它为所有的容器类定义了抽象的foreach方法。
4.序列
序列(Sequence)是指元素可以按照特定的顺序访问的容器。特质Seq具有两个子特质LinearSeq和IndexedSeq。LinearSeq序列具有高效的head和tail操作,而IndexedSeq序列具有高效的随机存储操作。实现了LinearSeq的常用序列有列表(List) 和 队列(Queue)。实现了IndexedSeq的常用序列有可变数组(ArrayBuffer) 和 向量(Vector)。
//字符串列表
val site = List("Runoob","Google","Baidu")
val site = "Runoob" :: ("Google" :: ("Baidu" :: Nil))//与上行代码效果相同
//整型列表
val nums = List(1,2,3,4)
val nums = 1 :: (2 :: (3 :: (4 :: Nil)))//与上行代码效果相同
//空列表
val empty = List()
val empty = Nil//与上行代码效果相同
//二维列表
val dim =
List(
List(1,0,0),
List(0,1,0),
List(0,0,1)
)
//下面代码效果同上面
val dim =
(1 :: (0 :: (0 :: Nil))) ::
(0 :: (1 :: (0 :: Nil))) ::
(0 :: (0 :: (1 :: Nil))) :: Nil
列表的基本操作包括:
head:返回列表第一个元素
tail:返回一个列表,包含除了第一元素外的其他元素
isEmpty:在列表为空时返回true
val site = "Runoob"::"Google"::"Baidu"::Nil
val nums = Nil
println("第一个网站是:"+site.head)
println("site.tail为:"+site.tail)
println("查看列表site是否为空:"+site.isEmpty)
println("查看nums是否为空:"+nums.isEmpty)
/*
输出结果为
第一个网站是:Runoob
site.tail为:List(Google, Baidu)
查看列表site是否为空:false
查看nums是否为空:true
*/
更多列表的方法可以参考菜鸟教程。
for(i<-Range(1,5,2)) println(i) //输出1,3
for(i<-1 to 5) println(i) //输出1,2,3,4,5
for(i<-1 until 5) println(i) //输出1,2,3,4
5.集合
Scala的集合是不重复元素的容器。
var myset = Set("Hadoop","Spark")
println(myset) //输出Set(Hadoop, Spark)
myset += "flink"
println(myset) //输出Set(Hadoop, Spark, flink)
6.映射
映射(Map)是一系列键值对的容器。Scala提供了可变映射和不可变映射。默认情况下,Scala使用的是不可变映射。比如
val university = Map("XMU"->"Xiamen University","THU"->"Tsinghua University","PKU"->"Peking University")
println(university("XMU")) //输出Ximen University
如果需要使用可变映射,需要导入scala.collection.mutable.Map
import scala.collection.mutable.Map
val university = Map("XMU"->"Xiamen University","THU"->"Tsinghua University","PKU"->"Peking University","JMU"->"NULL")
university("JMU") = "Jimei University" //修改映射
university("TJU") = "Tianjin University" //添加映射
university += ("FZU" -> "Fuzhou University") //添加映射
println(university("JMU")) //输出Jimei University
7.迭代器
迭代器(Iterator)是一种提供了按顺序访问容器元素的数据结构。迭代器只能按从前往后的顺序一次访问其元素。迭代器提供了两个基本操作next 和 hasNext。next返回迭代器的下一个元素,并将该元素从迭代器中抛弃。hasNext用于检测是否还有下一个元素。用法如下
val iter = Iterator("Hadoop","Spark","Scala")
while (iter.hasNext){
println(iter.next())
}
class ClassName{
//这里定义类的字段和方法
}
//例如下面是一个完整的类定义
class Counter{
//字段
var value = 0
//方法
def increment(stp:Int):Unit = {value += step}
def current():Int = {value}//对于scala而言,方法的最后一个表达式值就是方法的返回值
}
定义完类以后,就可以通过new关键字进行实例化,并通过实例对成员进行访问,如下所示:val myCounter = new Counter
myCounter.value = 5 //访问字段
myCounter.increment(3) //调用方法
println(myCounter.current) //调用无参数方法时,可以省略方法名后的括号
//定义
class Counter{
private var privateValue = 0
def value = privateValue
def value_=(newValue:Int){
if(newValue>0) privateValue = newValue
}
def increment(step:Int):Unit = {value += step}
def current():Int = {value}
}
//调用
val myCounter = new Counter
myCounter.value_=(3) //为privateValue设置新的值
//scala看到value和value_= 成对出现的时候 允许用户去掉_和括号,如下所示
myCounter.value = 3 //等同于myCounter.value_=(3)
println(myCounter.value) //访问privateValue的当前值
val c = new Counter
c increment 5 //中缀调用法
println(c.current) //等同于c.current()
//比如重新定义Counter
class Counter{
var value = 0
def increment(step:Int) = value += step //省略大括号
def increment(step:Int) {value += step} //同上条一句相同,省略返回结果类型和等号
def current() = value //省略结果类型和大括号
}
class position(var x:Double=0,var y:Double=0){
def move(deltaX:Double = 0,deltaY:Double = 0): Unit ={
x+=deltaX
y+=deltaY
println("new pos("+x+","+y+")")
}
}
def main(args: Array[String]): Unit = {
val p = new position();
p.move(deltaY = 5)//输出new pos(0.0,5.0)
p.move(deltaX = 5)//输出new pos(5.0,5.0)
p.move(5,5)//输出new pos(10.0,10.0)
}
class ClassName(形参列表){ //主构造器
//类体
def this(形参列表){//辅助构造器}
def this(形参列表){//辅助构造器可以有多个...}
}
Scala采用单例对象(Singleton Object)来实现与Java静态成员同样的功能。单例对象的定义与类的定义类似,只是用object关键字替换了class关键字。例如
object Person{
private var lastId = 0 //一个人的身份编号
def newPersonId() = {
lastId += 1
lastId
}
}
上面单例对象调用效果如下:
单例对象在第一次被访问的时候初始化。单例对象包括两种,即伴生对象(Companion Object) 和 孤立对象(Standalone Object)。当一个单例对象和它的同名类一起出现时,这时的单例对象被称为这个同名类的 “伴生对象” ,相应的类被称为这个单例对象的 “伴生类”,二者可以互相访问私有的field。没有同名类的单例对象,被称为孤立对象。
Scala程序的入口main方法就是定义在一个孤立对象里。单例对象和类之间的另一个差别是,单例对象的定义不能带有参数列表。(因为不能使用new关键字实例化一个单例对象,因此没机会传递参数给单例对象)
伴生类和伴生对象关系如下代码所示:
//伴生类
class Person(val name:String){
private val id = Person.newPersonId() //调用伴生对象中的方法
def info(): Unit ={
printf("The id of %s is %d.\n",name,id)
}
}
//伴生对象
object Person {
private var lastId = 0 //一个人的身份编号
def newPersonId() = {
lastId+=1
lastId
}
def main(args: Array[String]): Unit = {
val person1 = new Person("Lilei")
val person2 = new Person("Hanmei")
person1.info() //输出The id of Lilei is 1.
person2.info() //输出The id of Hanmei is 2.
}
}
val myStrArr = Array("BigData","Hadoop","Spark")
对于以上代码,并没有使用new关键字来创建Array对象。采用这种语法格式时,Scala会自动调用Array类的伴生对象Array中的一个称为apply的方法,来创建一个Array对象myStrArr。
在Scala中,apply方法遵循如下的约定被调用:用括号传递给类实例或对象名一个或多个参数时,Scala会在相应的类或对象中查找方法名为apply且参数列表与传入参数一致的方法,并用传入的参数来调用该apply方法。效果如下所示:
class test{ //伴生类
def apply(param:String): Unit ={
println("class apply method called:"+param)
}
}
object test { //伴生对象
def apply(param:String):Unit ={
println("object apply method called:"+param)
}
def main(args: Array[String]): Unit = {
val myObject1 = new test
//自动调用类中定义的apply方法,等同于下句
myObject1("test1") //输出class apply method called:test1
//手动调用apply方法
myObject1.apply("test2") //输出class apply method called:test2
//单例对象的apply方法
val myObject2 = test("test3") //输出object apply method called:test3
}
}
对apply方法而言,更通常的用法时将其定义在类的伴生对象中,即将所有类的构造方法以apply方法的形式定义在伴生对象中。
unapply()方法用于对对象进行解构操作,与apply方法类似,该方法也会被自动调用。unapply方法包含一个类型为伴生类的参数,返回的结果是Option类型,对应的类型参数是N元组,N是伴生类中主构造器参数的个数。如下所示
class Car(var brand:String,val price:Int){
def info(): Unit ={
println("Car brand is "+brand+" and price is "+price)
}
}
object Car {
def apply(brand: String, price: Int)={
println("Debug:calling apply ……")
new Car(brand,price)
}
def unapply(arg: Car): Option[(String, Int)] = {
println("Debug:calling unapply ……")
Some((arg.brand,arg.price))
}
def main(args: Array[String]): Unit = {
var Car(carbrand,carprice) = Car("BMW",800000)//等号右侧调用apply方法,左侧调用unapply方法
println("brand:"+carbrand+" and carprice:"+carprice)
/*输出结果为:
Debug:calling apply ……
Debug:calling unapply ……
brand:BMW and carprice:800000
*/
}
}
与java不同的是,Scala里的抽象方法不需要加abstract修饰符。scala抽象类例子如下
abstract class Car(val name:String){
val carBrand:String //字段没有初始化值,就是一个抽象字段
def info() //抽象方法
def greeting() {
println("welcome to my car")
}
}
实例如下
//父类
abstract class Car(val name:String){
val carBrand:String //一个抽象字段
var age:Int = 0
def info() //抽象方法
def greeting(){
println("welcome to my car")
}
def this(name:String,age:Int){
this(name)
this.age = age
}
}
//派生类,其著构造函数调用了父类的主构造函数
//由于name是父类主构造器的参数,因此也必须使用override
class BMWCar(override val name:String) extends Car(name){
override val carBrand = "BMW" //重载父类抽象字段,override可选
def info() { //重载父类抽象方法,override可选
printf("This is a %s car,It has been used for %d year.\n",carBrand,age)
}
override def greeting() {//重载父类非抽象方法,override必选
println("Welcome to my BMW car!")
}
}
//派生类,其主构造函数调用了父类的辅助构造函数
class BYDCar(name:String,age:Int) extends Car(name,age){
val carBrand = "BYD" //重载父类抽象字段,override可选
override def info() {//重载父类抽象方法,override可选
printf("This is a %s car,It has been used for %d year.\n",carBrand,age)
}
}
object MyCar{
def main(args:Array[String]){
val car1 = new BMWCar("Bob's Car")
val car2 = new BYDCar("Tom's Car",3)
show(car1)
show(car2)
/*
输出如下
Welcome to my BMW car!
This is a BMW car,It has been used for 0 year.
welcome to my car
This is a BYD car,It has been used for 3 year.
*/
}
def show(thecar:Car) = {
thecar.greeting;
thecar.info()
}
}
在Scala类层级结构的最底层,有两个特殊类型:Null和Nothing。其中,Null是所有引用类型的子类,其唯一的实例为null,表示一个“空”对象,可以赋值给任何引用类型的变量,但不能赋值给值类型的变量。Nothing是所有其他类型的子类,包括Null。Nothing没有实例,主要用于异常处理函数的返回类型。
除非明确需要与Java库进行交互,否则,Scala建议尽量避免使用这种可能带来bug的null,而改用Option类来统一表示对象有值和无值的情形。类Option是一个抽象类,有一个具体的子类Some和一个对象None,其中,Some表示有值的情形,后者表示没有值。
所谓参数化类型,是指在类的定义中包含一个或几个未确定的类型参数,其具体的类型将在实例化类时确定。Scala使用方括号([…])来定义参数化类型。如下所示
import scala.collection.mutable.Stack
class Box[T]{//未确定类型参数
val elems:Stack[T] = Stack()
def remove:Option[T]={ //返回的对象采用了Option类型包装
if (elems.isEmpty) None else Some(elems.pop)
}
def append(a1:T) {elems.push(a1)}
}
object test { //伴生对象
def main(args: Array[String]): Unit = {
case class Book(name:String) //定义了一个Book类
val a = new Box[Book] //实例化一个元素为Book类型的Box实例并赋值给a
println(a) //输出Box@5ba23b66
a.append(Book("Hadoop"))
a.append(Book("Spark"))
println(a.remove)//输出Some(Book(Spark))
}
}
Java8前的接口不能为接口方法提供默认实现,Scala从设计之初就对Java接口的概念进行了改进,使用 “特质(Trait)” 来实现代码的多重复用,它不仅实现了接口的功能,还具备了很多其他的特性。Scala的特质是代码重用的基本单元,可以同时拥有抽象方法和具体方法。Scala中,一个类只能继承自一个超类,缺可以混入(Mixin) 多个特质,从而宠用特质中的方法和字段,