Scala语言基础

1.Scala基础知识

1.1 基本数据类型和变量

  基本数据类型包括Byte、Short、Int、Long、Char、Float、Double、String、Boolean
Scala语言基础_第1张图片
  Scala变量 从变量声明角度看,只有两种类型,valvar,对于val声明的变量,在声明时就必须初始化,并且不可变。对于var声明的变量,是可变的。声明变量的语法如下:

val 变量名[:数据类型] = 初始值
var 变量名[:数据类型] = 初始值

  Scala提供了一种类型推断机制(Type Inference),它会根据初始值自动推断变量的类型。因此,若给出了初始值,数据类型可以省略。


1.2 输入/输出

1.2.1 控制台输入输出语句

  • 从控制台读取数据,可以使用以read为前缀的方法,包括:readInt、readDouble、readByte、readShort、readFloat、readLong、readChar、readBoolean、readLine,readLine可以带参数,参数为想显示输出的提示。所有这些函数都属于对象scala.io.StdIn的方法,使用前必须导入。
    import io.StdIn._
    var i = readInt()
    var f = readFloat //可以不加括号
    var str = readLine("Please input your name") //会在控制台输出"Please input your name",并读取用户输入的一整行
    
  • 为了向控制台输出信息,常用的两个函数是print()和println(),同时也可以使用C语言风格的printf函数。另外Scala提供了字符串插值机制,以方便在字符串字面量中直接嵌入变量的值:只需要在字符串字面量前加一个"s"或"f"字符,然后,在字符串中既可以用"$"插入变量的值,s插值字符串不支持格式化,f插值字符串支持在$变量后再跟格式化参数,比如
    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
    

1.2.2 读写文件

  • Scala使用类java.io.PrintWriter实现文本文件的创建与写入。实例如下:
    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()
    	}
    }
    
  • Scala使用类Scala.io.Source实现对文件的读取,最常用的方法是getLines方法,它会返回一个含所有行的迭代器。
    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
    */
    

1.3 控制结构

  • 1.if条件表达式
    与java类似,但是不同的是scala中的if条件表达式会返回一个值,类似于三目表达式。例子如下
    val a = if (6>0) 1 else -1
    println(a) //输出1
    
  • 2.while循环
    Scala的while循环于java完全一样,也同样有do-while。
  • 3.for循环表达式
    scala中的for循环表达式与java中的foreach类似,具体语法结构如下:
    for (变量 <- 表达式 if 条件表达式) 语句块
    
    【例1val 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 
    【例2for (i <- 1 to 3 if i%2 == 0) print(i+" ")
    	//输出2 
    【例3for (i <- Array(3,5,6) print(i+" ")
    	//输出3 5 6
    
  • 4.异常处理结构
    和java一样,Scala也使用try-catch结构来捕获异常,例如:
    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()//确保关闭文件
    }
    
  • 5.循环控制(break和continue)
    Scala中没有breakcontinue关键词,但是提供了一个Breaks类来实现类似功能,该类位于包scala.util.control中。具体用法如下:
    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.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方法
    Scala语言基础_第2张图片

  • 4.序列
    序列(Sequence)是指元素可以按照特定的顺序访问的容器。特质Seq具有两个子特质LinearSeqIndexedSeq。LinearSeq序列具有高效的head和tail操作,而IndexedSeq序列具有高效的随机存储操作。实现了LinearSeq的常用序列有列表(List)队列(Queue)。实现了IndexedSeq的常用序列有可变数组(ArrayBuffer)向量(Vector)

    • 列表
      列表(List)是一种共享相同类型的不可变的对象序列。
      列表的声明方法如下
    //字符串列表
    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
    */
    

    更多列表的方法可以参考菜鸟教程。

    • Range
      Range类是一种特殊的、带索引的不可变数字等差序列,其包含的值为从给定按一定步长增长(减小)到指定终点的所有数值。
    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)是一种提供了按顺序访问容器元素的数据结构。迭代器只能按从前往后的顺序一次访问其元素。迭代器提供了两个基本操作nexthasNext。next返回迭代器的下一个元素,并将该元素从迭代器中抛弃。hasNext用于检测是否还有下一个元素。用法如下

    val iter = Iterator("Hadoop","Spark","Scala")
    while (iter.hasNext){
      println(iter.next())
    }
    

2.面向对象编程基础

2.1 类

  • 1.类的定义
    scala类用关键字class声明。定义形式如下:
    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) //调用无参数方法时,可以省略方法名后的括号
    
  • 2.类成员的可见性
    在Scala中,所有成员的默认可见性为公有,且不需要用public关键字限定,任何作用域内都能直接访问公有成员。Scala同时还提供了类似Java的可见性选项,如private和protected。其中private成员只对本类型和嵌套类型可见;protected成员对本类型和其继承类型都可见。
    对于私有字段的访问,Scala采用类似Java的getter和setter方法,定义了两个函数value和value_=。具体用法如下
    //定义
    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的当前值
    
  • 3.方法的定义方式
    • 在scala中,方法参数前不能加val或var关键字限定,所有方法参数都是不可变类型,相等于隐式使用了val关键字限定。
    • 对于无参数的方法,定义时可以省略括号,若省略了括号,在调用时也不能带括号。若无参数的方法在定义时带有括号,在调用时可以带括号,也可以不带括号。
    • 在调用方法时,方法名后面的()可以用{}来代替。
    • 如果方法只有一个参数,可以省略点号,而采用中缀操作符调用方法,形式为"调用者 方法名 参数"。
    val c = new Counter
    c increment 5 //中缀调用法
    println(c.current) //等同于c.current()
    
    • 当方法的返回结果类型可以从最后的表达式推断而出的时候,方法定义可以省略结果类型。
    • 如果方法体只有一条语句,可以省略方法体两边的大括号。
    • 如果方法的返回类型为Unit,可以同时省略返回结果类型和等号,但是不能省略大括号。
    //比如重新定义Counter
    class Counter{
    	var value = 0
    	def increment(step:Int) = value += step //省略大括号
    	def increment(step:Int) {value += step} //同上条一句相同,省略返回结果类型和等号
    	def current() = value //省略结果类型和大括号
    }
    
    • scala中设置方法参数默认值,和调用方法如下
    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)
    }
    
  • 4.构造器
    scala的构造器分为主构造器和辅助构造器。形式如下
    class ClassName(形参列表){ //主构造器
    //类体
    	def this(形参列表){//辅助构造器}
    	def this(形参列表){//辅助构造器可以有多个...}
    }
    

2.2 对象

2.2.1 单例对象

  Scala采用单例对象(Singleton Object)来实现与Java静态成员同样的功能。单例对象的定义与类的定义类似,只是用object关键字替换了class关键字。例如

object Person{
	private var lastId = 0 //一个人的身份编号
	def newPersonId() = {
		lastId += 1
		lastId
	}
}

上面单例对象调用效果如下:
Scala语言基础_第3张图片
  单例对象在第一次被访问的时候初始化。单例对象包括两种,即伴生对象(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.
  }
}

2.2.2 apply方法

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方法的形式定义在伴生对象中。


2.2.3 unapply方法

  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
     */
  }
}

2.3 继承

2.3.1 抽象类

  与java不同的是,Scala里的抽象方法不需要加abstract修饰符。scala抽象类例子如下

abstract class Car(val name:String){
	val carBrand:String //字段没有初始化值,就是一个抽象字段
	def info() //抽象方法
	def greeting() {
		println("welcome to my car")
	}
}

2.3.2 类的继承

  • 只支持单一继承,一个子类只能有一个父类
  • 重载父类抽象成员,override关键字是可选的(建议省略)。重载父类非抽象成员时,override关键字是必选的。
  • 只能重载val字段,因为var本身就可变。
  • 对于父类主构造器中用var或val修饰的参数,相当于类的一个字段。如果子类主构造器要使用相同的参数,必须加override关键字。
  • 子类的主构造器必须调用父类的主构造器或者辅助构造器,所采用的方法是在extend关键字后的父类名称后跟上相应的参数列表。

实例如下

//父类
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()
	}
}

2.3.3 Scala的类层级结构

Scala语言基础_第4张图片
  在Scala类层级结构的最底层,有两个特殊类型:Null和Nothing。其中,Null是所有引用类型的子类,其唯一的实例为null,表示一个“空”对象,可以赋值给任何引用类型的变量,但不能赋值给值类型的变量。Nothing是所有其他类型的子类,包括Null。Nothing没有实例,主要用于异常处理函数的返回类型。


2.3.4 Option类

  除非明确需要与Java库进行交互,否则,Scala建议尽量避免使用这种可能带来bug的null,而改用Option类来统一表示对象有值和无值的情形。类Option是一个抽象类,有一个具体的子类Some和一个对象None,其中,Some表示有值的情形,后者表示没有值。

2.4 参数化类型

  所谓参数化类型,是指在类的定义中包含一个或几个未确定的类型参数,其具体的类型将在实例化类时确定。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))
  }
}

2.5 特质

  Java8前的接口不能为接口方法提供默认实现,Scala从设计之初就对Java接口的概念进行了改进,使用 “特质(Trait)” 来实现代码的多重复用,它不仅实现了接口的功能,还具备了很多其他的特性。Scala的特质是代码重用的基本单元,可以同时拥有抽象方法和具体方法。Scala中,一个类只能继承自一个超类,缺可以混入(Mixin) 多个特质,从而宠用特质中的方法和字段,

2.6 模式匹配

2.7 包


3.函数式编程

3.1 函数的定义与使用

3.2 高阶函数

3.3 闭包

3.4 偏应用函数和Curry化

3.5 针对容器的操作

你可能感兴趣的:(Flink,#,Flink学习笔记,scala,开发语言,后端)