Scala 高级编程之基础知识

1. Scala 基本数据类型

  1. 字符类型:Char/String
  2. 数值类型:Byte/Short/Int/Long/Float/Double
  3. 布尔类型:Boolean

2. 定义变量

2.1 val 与 var

val 定义不可变类型,相当于 Java 中的 final 关键字。定义形式:val 值名称:类型 = XXX
var 定义可变类型。定义形式:var 值名称:类型 = XXX

var name:String = "zhangsan"
name = "lisi"
val name = "wangwu"

val a:Int = 10

val b:Boolean = true
val c = false

// 默认 Double
val d = 1.1
val e:Float = 1.1f
// val e:Float = 1.1f 这是错的
val f = 1.2f

// 转换类型,相当于 Java 中的自动装箱和拆箱
val g = 10.asInstanceOf[Double]

// 相当于 Java 中的 instanceof
val h = 10.isInstanceOf[Int]

2.2 lazy 关键字

使用 lazy 定义的变量,Scala 不会立即算出来这个值,等到这个值使用的时候才计算。这种“惰性”计算的思想在很多语言中都有体现。比如 TensorFlow 中的计算图。
这个关键字适用于需要大量计算的变量定义。
缺点是:如果定义错误,只有当使用的时候才会报错。

lazy val a = 1

3. IDEA 整合 Maven 构建 Scala 应用程序

Maven 使用中央仓库一般加载库会非常慢。通常都需要设置成阿里云的代理。这也是为什么使用自己目录下面 settings.xml 的原因。

  1. 选择 Maven;
  2. Create from archetype --> scala-archetype-simple --> Next;
  3. 填写 GroupId, ArtifactId, 比如:GroupId: com.alex, ArtifactId: scala-tutorial; --> Next;
  4. 根据需要修改 User settings file,默认是放在自己个人目录下面。如果需要修改全局的 settings,勾选 Override,选择 Maven 主目录的 conf/settings.xml;一般情况下使用默认的即可,也是推荐的方式;–> Next;
  5. Project Name: scala-tutorial; --> Finish;
  6. 右下角 Enable Auto-Import;
  7. pom.xml 中,根据需要修改 Scala 的相关设置,如编译版本。<scala.version>2.12.6</scala.version>
  8. 创建一个 Scala,右键 --> New --> Scala Class --> 输入Name,并在 Kind 中选择 Object

4. 函数

4.1 函数定义

def 方法名字(参数名:参数类型):返回值类型 = {
	// 方法逻辑
	// 方法体内的最后一行为返回值,不需要使用 return。这一点需要特别注意
	if (x > y) {
		// x 为返回值
		x
	} else {
		// y 为返回值
		y
	}
}

4.1.1 最后一行就是返回值

def add(x:Int, y:Int):Int = {
	// 下面一行就是返回值,不需要 return
	x + y
}

4.1.2 当返回值类型确定时,可以不用写返回值

// 当返回值确定时,可以不用写返回值
def three() = 1 + 2
println(three())
// 没有输入参数的时候,调用时括号可以省略
println(three)

4.1.3 没有返回值时的简写

// 没有返回值
// 在 IDEA 中打完 sayHello() 后输入 {,IDEA 会自动补全 : Unit =
def sayHello(): Unit = {
	println("Hello ...")
}

4.2 默认参数值

  def loadConf(conf:String = "Spark-defaults.conf"): Unit = {
    println(conf)
  }

loadConf()
loadConf("Spark-production.conf")

4.3 不按序传参

def speed(distance:Float, time:Float):Float = {
    distance / time
  }

// 通过采用键值对的形式传参数,之前的方式是参数默认对应方法参数定义的顺序
println(speed(distance = 100, time = 10))
println(speed(time = 10, distance = 100))

4.4 可变参数

// 在参数类型后面加星号
def sum(numbers:Int*) = {
    var result = 0
    for (number <- numbers) {
      result += number
    }
    result
  }

println(sum(1, 2, 3))
println(sum(1, 2, 3, 4, 5))

4.5 条件表达式

 val a = if (x > 0) 1 else if (x == 0) 0 else -1

4.6 循环表达式

4.6.1 to

// 左:闭区间,右:闭区间

// 下面两行等价
1 to 10
1.to(10)

4.6.2 Range

// 左:闭区间,右:开区间

Range(1, 10)

// 步长
Range(1, 10, 2)
Range(1, 10, 4)
// 步长不能为 0

4.6.3 for

for (i <- i to 10) {
	println(i)
}

4.6.4 foreach

val courses = Array("Hadoop", "Spark SQL", "Scala")
// course 是 courses 里面的每个元素
// => 就是将左边的 course 作用上一个函数,变成另一个结果
// println 就是作用到 course 上的一个函数
courses.foreach(course => println(course))

var (num, sum) = (100, 0)
while (num > 0) {
	sum += num
	num -= 1
}
println(sum)

5. Scala 面向对象

5.1 面向对象概述

object SimpleObjectApp {
  def main(args: Array[String]): Unit = {
    val person = new People()
    person.name = "Messi"

    println(person.name + " .. " + person.age)
    // 可以不用写成 person.eat()
    println("invoke eat method: " + person.eat)

    person.watchFootball("Barcelona")

    person.printInfo()
  }
}

class People {
  // 属性
  // _: 占位符,占位符不能用于 val
  var name:String = _
  // 可以不用写 Int,能够通过 10 推断出来类型
  val age = 10

  // 私有属性
  private [this] val gender = "male"

  def printInfo(): Unit = {
    println("gender: " + gender)
  }

  def eat():String = {
    name + "eat... "
  }

  def watchFootball(teamName: String): Unit = {
    println(name + " is watching match of " + teamName)
  }
}

5.2 构造器

object ConstructorApp {
  def main(args: Array[String]): Unit ={
    val person = new Person("zhangsan", 30)
    println(person.name + " : " + person.age + " : " + person.school)

    val person2 = new Person("lisi", 18)
    println(person2.name + " : " + person2.age + " : " + person2.school)
  }

}

// 跟在类名后面的叫做 主构造器
class Person(val name:String, val age:Int) {
  println("Person Constructor enter...")

  val school = "monash"
  var gender:String = _

  // 可以像 Java 中一样,根据参数不同编写不同的构造器
  def this(name:String, age:Int, gender:String) {
    // 附属构造器的第一行代码必须要调用主构造器或者其他附属构造器
    this(name, age)

    this.gender = gender
  }


  println("Person Constructor exit...")

}

5.3 继承

object ConstructorApp {
  def main(args: Array[String]): Unit ={
    val person = new Person("zhangsan", 30)
    println(person.name + " : " + person.age + " : " + person.school)

    val person2 = new Person("lisi", 18)
    println(person2.name + " : " + person2.age + " : " + person2.school)

    // 先调用父类的构造方法
    val student = new Student("wangwu", 20, "nlp")
    println(student.name + " : " + student.major)

    // 打印 toString 方法
    println(student)
  }

}

// 跟在类名后面的叫做 主构造器
// 如果将 val 去掉会报错
class Person(val name:String, val age:Int) {
  println("Person Constructor enter...")

  val school = "monash"
  var gender:String = _

  // 可以像 Java 中一样,根据参数不同编写不同的构造器
  def this(name:String, age:Int, gender:String) {
    // 附属构造器的第一行代码必须要调用主构造器或者其他附属构造器
    this(name, age)

    this.gender = gender
  }


  println("Person Constructor exit...")

}


// 如果父类有的属性,不必加 val 或 var。子类特有的属性,需要加。
class Student(name:String, age:Int, val major:String) extends Person(name, age) {
  println("Student Constructor enter...")

  // 重写父类的属性需要使用 override 关键字
  override val school = "seu"

  override def toString: String = "Person: override def toString" + school

  println("Student Constructor exit...")
}

5.4 抽象

5.4.1 抽象类的定义

object AbstractApp {
  def main(args: Array[String]): Unit = {
    val student = new Student2()
    student.speak
  }

}

abstract class Person2 {
  def speak

  val name:String
  val age:Int
}

class Student2 extends Person2{
  override def speak: Unit = {
    println("I am a student...")
  }

  override val name: String = "zhaoliu"
  override val age: Int = 22
}

5.4.2 伴生类与伴生对象

如果有个class 和 object 同名,那么,class 修饰的叫做伴生类,object 修饰的叫做伴生对象

/**
  * 伴生类和伴生对象
  * 如果有个class 和 object 同名,那么,class 修饰的叫做伴生类,object 修饰的叫做伴生对象
  */
// 伴生类
class ApplyTest {
}

// 伴生对象
object ApplyTest {
}

5.4.3 object 本身就是一个单例对象

object ApplyApp {
  def main(args: Array[String]): Unit = {
    for (i <- 1 to 10) {
      ApplyTest.increase
    }

    // 10, 说明 object 本身就是一个单例对象
    println(ApplyTest.count)
  }

}

// 伴生类
class ApplyTest {

}

// 伴生对象
object ApplyTest {

  println("Object ApplyTest enter...")

  var count = 0

  def increase = {
    count += 1
  }
  
  println("Object ApplyTest exit...")

}

5.4.4 apply 方法(考点)

类名() 调用的是 Object.apply(); 对象() 调用的是 Class.apply()
apply 的最佳的用法是:在 Object 的 apply 方法中 new class。这种方式在很多框架中大量使用,如 spark 中

object ApplyApp {
  def main(args: Array[String]): Unit = {
    for (i <- 1 to 10) {
      ApplyTest.increase
    }

    // 10, 说明 object 本身就是一个单例对象
    println(ApplyTest.count)

    // 调用 Object 里面的 apply 方法
    val b = ApplyTest()

    val c = new ApplyTest()
    // toString
    println(c)
    // class 中的 apply 方法
    c()
    
    // 类名() 调用的是 Object.apply()
    // 对象() 调用的是 Class.apply()
  }

}

/**
  * 伴生类和伴生对象
  * 如果有个class 和 object 同名,那么,class 修饰的叫做伴生类,object 修饰的叫做伴生对象
  */
// 伴生类
class ApplyTest {
  def apply() = {
    println("class ApplyTest apply...")

  }

}

// 伴生对象
object ApplyTest {

  println("Object ApplyTest enter...")

  var count = 0

  def increase = {
    count += 1
  }

  def apply() = {
    println("Object ApplyTest apply...")

    // 在 object 中 apply 中 new 一个 class
    new ApplyTest
  }

  println("Object ApplyTest exit...")

}

5.4.5 case class

case class 不用 new。通常用在模式匹配中

object CaseClassApp {
  def main(args: Array[String]): Unit = {
    println(Dog("wangcai").name)
  }
}

// case class 不用 new
// 通常用在模式匹配中
case class Dog(name: String)

5.4.6 trait

Traits are used to share interfaces and fields between classes.
They are similar to Java 8’s interfaces. Classes and objects can extend traits but traits cannot be instantiated and therefore have no parameters.
将这个关键字理解成 Java 中的 interface 就可以了。

// 第一个用 extends,之后的都用 with
class SparkConf(loadDefaults: Boolean) extends Cloneable with Logging with Serializable 

6. Scala 集合

6.1 数组

6.1.1 定长数组

object ArrayApp {
  def main(args: Array[String]): Unit = {
    val a = new Array[String](5)
    println(a.length)
    a(1) = "hello"

    // 调用的是 apply 方法
    val b = Array("hadoop", "spark", "scala")
    b(0) = "Python"

    val c = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    c.sum
    c.min
    c.max

    println(c.mkString(","))
    println(c.mkString(" and "))
    println(c.mkString("<", ",", ">"))

  }

}

6.1.2 可变数组

object ArrayApp {
  def main(args: Array[String]): Unit = {
       
    val d = ArrayBuffer[Int]()
    println(d.length)
    
    d += 1
    d += 2
    d += (3, 4, 5)
    d ++= Array(6, 7, 8)
    
    // 在第 0 位置添加元素 0
    d.insert(0, 0)
    // 在位置7 删除 1 个元素
    d.remove(7, 1)
    // 在末尾删除 2 个元素
    d.trimEnd(2)
    
    for (i <- 0 until d.length) {
      println(d(i))
    }
    
    // 这种更常用
    for (i <- c) {
      println(i)
    }
    
    // 反向
    for (i <- (0 until d.length).reverse) {
      println(d(i))
    }
    
    // 变成不可变数组
    println(d.toArray)
  }

}

6.2 List

6.2.1 Nil

不可变 List,把它当成一个空的集合就可以了。
scala.collection.immutable.Nil.type = List()

6.2.2 定长 List

考点:剩下的所有元素是一个 tail,不是最后一个叫 tail

object ListApp {
  def main(args: Array[String]): Unit = {

    val l = List(1, 2, 3, 4, 5)
    println(l.head)
    // 考点:剩下的所有元素是一个 tail,不是最后一个叫 tail
    println(l.tail)

    // 1 表示 head,Nil 表示 tail,使用 :: 将两者拼起来当成一个新的 List
    val l2 = 1 :: Nil

    // 2 是 head,l2 是 tail,别弄反了
    val l3 = 2 :: l2

  }

}

6.2.3 变长 List

object ListApp {
  def main(args: Array[String]): Unit = {

    val l5 = ListBuffer[Int]()

    l5 += 2
    l5 += (3, 4, 5)

    l5.tail
    l5.tail.head

    l5 ++= List(6, 7, 8, 9)
    l5 -= 2
    // 1 没有就不减了
    l5 -= (1, 4)
    l5 --= List(5, 6, 7, 8)


    // 转成不可变 List
    l5.toList
    // 转成不可变数组
    l5.toArray

  }
}

6.2.4 自己实现求和

:_*,固定写法。能够将 seq 转换成可变参数

object ListApp {
  def main(args: Array[String]): Unit = {
    println(sum())
    println(sum(1, 2, 3, 4))
  }
  
  // 自己实现 sum
  def sum(nums: Int*): Int = {
    if (nums.length == 0) {
      0
    } else {
      // :_*,固定写法。能够将 seq 转换成可变参数
      nums.head + sum(nums.tail:_*)
    }
  }

}

6.3 Set

Set 无序,不可重复。用法参考 List 即可

7. 模式匹配

7.1 语法

在 Java 中有 switch 关键字,用于对一个值进行判断,返回针对不同的条件进行不同的处理。在 Scala 也有同样的功能实现,并且功能更加强大。

变量 match {
	case value1 => 代码1
	case value2 => 代码2
	...
	case _ => 代码 N
}

7.2 双重过滤

import scala.util.Random

object MatchApp {
  def main(args: Array[String]): Unit = {
    val names = Array("P1", "P2", "P3")
    val name = names(Random.nextInt(names.length))

    name match {
      case "P1" => println("1")
      case "P2" => println("2")
      case _ => println("3")
    }

    judgeGrade("zhangsan", "A")
    judgeGrade("lisi", "D")

    greeting(Array("zhangsan"))
    greeting(Array("lisi", "wangwu"))
    greeting(Array("zhangsan", "lisi", "wangwu"))

    greeting(List("zhangsan"))
    greeting(List("zhangsan", "lisi"))
    greeting(List("lisi", "zhangsan"))
    greeting(List("wangwu", "lisi", "zhangsan"))
  }

  def judgeGrade(name:String, grade:String): Unit = {
    grade match {
      case "A" => println("Excellent... ")
      case "B" => println("Good... ")
      case "C" => println("Just so so...")
      case _ if (name == "lisi") => println(name + ", you are a significant improvement.")
      case _ => println("You need work harder")
    }
  }

  // 匹配数组
  def greeting(array:Array[String]): Unit = {
    array match {
      case Array("zhangsan") => println("Hi: zhangsan")
      case Array(x, y) => println("Hi:" + x + ", " + y)
      case Array("zhangsan", _*) => println("Hi:zhangsan and others...")
      case _ => println("Hi: everybody...")
    }
  }

  // 匹配 List
  def greeting(list:List[String]): Unit = {
    list match {
      case "zhangsan"::Nil => println("Hi: zhangsan")
      case x :: y :: Nil => println("Hi:" + x + ", " + y)
      case "zhangsan"::tail => println("Hi:zhangsan and others...")
      case _ => println("Hi: everybody...")
    }
  }

  // 匹配类型
  def matchType(obj:Any): Unit = {
    obj match {
      case x:Int => println("Int")
      case s:String => println("String")
      case m:Map[_, _] => m.foreach(println)
      case _ => println("Other type")
    }
  }

}

7.3 case class 匹配

object MatchApp {
  def main(args: Array[String]): Unit = {

    caseclassMatch(CTO("zhaoliu", "22"))
    caseclassMatch(Employee("sunqi", "22"))
    caseclassMatch(Others("zhouba"))
  }
  
  def caseclassMatch(person:Person): Unit = {
    person match {
      case CTO(name, floor) => println("CTO name is: " + name + ", floor is: " + floor)
      case Employee(name, floor) => println("Employee name is: " + name + ", floor is: " + floor)
      case _ => println("Others")
    }
  }
  
  class Person
  case class CTO(name:String, floor:String) extends Person
  case class Employee(name:String, floor:String) extends Person
  case class Others(name:String) extends Person
}

7.4 Spark 源码中的模式匹配应用

override def receiveAndReply(context: RpcCallContext): PartialFunction[Any, Unit] = {

    // Messages sent and received locally
    case ExecutorRegistered(executorId) =>
      executorLastSeen(executorId) = clock.getTimeMillis()
      context.reply(true)
    case ExecutorRemoved(executorId) =>
      executorLastSeen.remove(executorId)
      context.reply(true)
    case TaskSchedulerIsSet =>
      scheduler = sc.taskScheduler
      context.reply(true)
    case ExpireDeadHosts =>
      expireDeadHosts()
      context.reply(true)

    // Messages received from executors
    case heartbeat @ Heartbeat(executorId, accumUpdates, blockManagerId) =>
      if (scheduler != null) {
        if (executorLastSeen.contains(executorId)) {
          executorLastSeen(executorId) = clock.getTimeMillis()
          eventLoopThread.submit(new Runnable {
            override def run(): Unit = Utils.tryLogNonFatalError {
              val unknownExecutor = !scheduler.executorHeartbeatReceived(
                executorId, accumUpdates, blockManagerId)
              val response = HeartbeatResponse(reregisterBlockManager = unknownExecutor)
              context.reply(response)
            }
          })
        } else {
          // This may happen if we get an executor's in-flight heartbeat immediately
          // after we just removed it. It's not really an error condition so we should
          // not log warning here. Otherwise there may be a lot of noise especially if
          // we explicitly remove executors (SPARK-4134).
          logDebug(s"Received heartbeat from unknown executor $executorId")
          context.reply(HeartbeatResponse(reregisterBlockManager = true))
        }
      } else {
        // Because Executor will sleep several seconds before sending the first "Heartbeat", this
        // case rarely happens. However, if it really happens, log it and ask the executor to
        // register itself again.
        logWarning(s"Dropping $heartbeat because TaskScheduler is not ready yet")
        context.reply(HeartbeatResponse(reregisterBlockManager = true))
      }
  }

8. 异常处理

object ExceptionApp {
  def main(args: Array[String]): Unit = {
    try {
      val i = 10 / 0
      println(i)
    } catch {
      case e: ArithmeticException => println("除数不能为 0")
      case e: Exception => println(e.getMessage)
    } finally {
      // release sources
    }
   
  }

}

9. Scala 函数高级操作(重点)

9.1 字符串高级操作

插值操作和多行字符串

object StringApp {
  def main(args: Array[String]): Unit = {
    val hello = "Hello, "
    val name = "zhangsan"

    // 插值
    println(s"Hello:$name")
    
    // 多行字符串
    // 和 Python 一样,使用三个字符串符号
    val s =
      """
        |这是一个多行字符串
        |第二行
        |第三行
      """.stripMargin
  }

}

9.2 匿名函数

object FunctionApp {
  def main(args: Array[String]): Unit = {
    // 语法
    // 括号必须加
    // (参数名: 参数类型... ) => 函数体

    val f = (x:Int) => x + 1
    def add = (x:Int, y:Int) => x + y

    println(f(1))
    println(add(1, 2))
  }

}

9.3 currying 函数

在 Spark SQL 的 DataFrame 中用的比较多。

object FunctionApp {
  def main(args: Array[String]): Unit = {

    println(sum(2, 3))
    println(sum2(2)(3))
  }

  def sum(a:Int, b:Int) = a + b
  
  // currying 函数
  // 将原来接收两个参数的一个函数,转换成 2 个
  def sum2(a:Int)(b:Int) = a + b

}

9.4 Scala 中的高阶函数(重点)

  1. map:逐个去操作集合中的每个元素。和 Python 中一样
  2. filter: 过滤符合要求的元素
  3. reduce: 两两操作,结果再与第三个元素操作
  4. flatten: “压扁”
  5. flatMap: 相当于 flatten + map
// map:逐个去操作集合中的每个元素。和 Python 中一样
    l.map((x: Int) => x + 1)
    // 能够推断出来类型,可以省略类型
    l.map((x) => x + 1)
    // 只有一个元素时可以省略左边的括号
    l.map(x => x + 1)
    // 使用占位符
    l.map(_ * 2).foreach(println)

    // 使用 filter 过滤符合要求的元素
    l.map(_ * 2).filter(_ > 8).foreach(println)

    // 取前 4 个
    println(l.take(4))

    // reduce 逐个操作。
    // 下面一行的意思是:1 + 2 = 3; 3 + 3 = 6; 6 + 4 = 10; 10 + 5
    println(l.reduce(_ + _))
    println(l.reduceLeft(_ - _))
    println(l.reduceRight(_ - _))

    val l2 = List(List(1, 2), List(3, 4), List(5, 6))
    // flatten: “压扁”
    println(l2.flatten)

    // flatMap: 相当于 flatten + map
    println(l2.flatMap(_.map(_ * 2)))

9.5 偏函数

https://blog.csdn.net/bluishglc/article/details/50995939
https://www.jianshu.com/p/0a8a15dbb348

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值