Scala 学习 1

1. 初识Scala

1. Scala概述

  • Scala是一门多范式的编程语言,设计初衷集成面向对象编程和函数式编程的各种特性
  • Scala运行于Java平台(Java虚拟机),并兼容现有的Java程序
  • Scala combines object-oriented and functional programming in one concise, high-level language. Scala’s static types help avoid bugs in complex applications, and its JVM and JavaScript runtimes let you build high-performance systems with easy access to huge ecosystems of libraries.

2. Scala安装

  • Java8
Thpffcj:software thpffcj$ echo $JAVA_HOME
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home
Thpffcj:software thpffcj$ java -version
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
  • 下载并解压Scala
  • 配置到系统环境变量
Thpffcj:software thpffcj$ vi ~/.bash_profile

export SCALA_HOME=/Users/thpffcj/Public/software/scala-2.12.8
export PATH=$PATH:$SCALA_HOME/bin

Thpffcj:software thpffcj$ source ~/.bash_profile

3. Scala使用入门及对比Java

Thpffcj:bin thpffcj$ ./scala
Welcome to Scala 2.12.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_191).
Type in expressions for evaluation. Or try :help.

scala> 1 + 3
res0: Int = 4

scala> res0 * 3
res1: Int = 12

scala> res0 * res1
res2: Int = 48

scala> println("Hello World")
Hello World

HelloWorld

public class HelloWorld{
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}
  • Scala每行代码并不强求使用;结束,但是Java是必须的
object HelloWorld {
    def main(args : Array[String]) {
        println("Hello World")
    }
}
  • 将上面代码保存为HelloWorld.scala
Thpffcj:data thpffcj$ scalac HelloWorld.scala 
Thpffcj:data thpffcj$ ls
HelloWorld$.class HelloWorld.class  HelloWorld.scala  hello.txt
Thpffcj:data thpffcj$ scala HelloWorld
Hello World



2. Scala入门

1. val vs var

  • val:值
    • val 值名称:类型 = xxx
scala> val money = 100
money: Int = 100

scala> money =200
<console>:12: error: reassignment to val
       money =200
             ^

scala> val age:Int = 20
age: Int = 20
  • var:变量
    • var 值名称:类型 = xxx
scala> var name:String = "zhangsan"
name: String = zhangsan

scala> name = "zhangsi"
name: String = zhangsi

2. 基本数据类型

  • Byte/Char
  • Short/Int/Long/Float/Double
  • Boolean
scala> val a:Int = 10
a: Int = 10

scala> val b:Boolean = true
b: Boolean = true

scala> val c = false
c: Boolean = false

scala> val d = 1.1
d: Double = 1.1

scala> val e:Float = 1.1
<console>:11: error: type mismatch;
 found   : Double(1.1)
 required: Float
       val e:Float = 1.1
                     ^

scala> val e:Float = 1.1f
e: Float = 1.1
  • 类型转换
scala> val f = 10
f: Int = 10

scala> val g = 10.asInstanceOf[Double]
g: Double = 10.0

scala> val h = 10.isInstanceOf[Int]
h: Boolean = true

3. lazy在Scala中的应用

scala> val i = 1
i: Int = 1

scala> lazy val a = 1
a: Int = <lazy>

scala> a
res0: Int = 1
  • 我们现在想读取文件
scala> import scala.io.Source._
import scala.io.Source._

scala> lazy val info = fromFile("/Users/thpffcj/Public/data/HelloWorld.scala").mkString
info: String = <lazy>

scala> info
res3: String =
"object HelloWorld {
    def main(args : Array[String]) {
        println("Hello World")
    }
}
"



3. Scala函数

1. 方法的定义和使用

  • 函数/方法的定义
def 方法名(参数名:参数类型): 返回值类型 = {

    // 括号内的叫做方法体

    // 方法体内的最后一行为返回值,不需要使用return
}
  • 我们去IDEA中试验一下
object FunctionApp {

  def main(args: Array[String]): Unit = {
    println(add(2, 3))
    println(three())
    println(three)  // 没有入参的函数,调用时括号是可以省略的
    sayHello("Thpffcj")
  }

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

  def three() = 1 + 2

  def sayHello(name:String): Unit = {
    println("Say hello " + name)
  }
}

2. 默认参数的使用

  • 在函数定义时,允许指定参数的默认值
def sayName(name:String = "Thpffcj"): Unit = {
  println(name)
}
  • 我们来看一下默认参数在Spark中的使用
Thpffcj:bin thpffcj$ ./spark-shell --help

  --properties-file FILE      Path to a file from which to load extra properties. If not
                              specified, this will look for conf/spark-defaults.conf.
  • 如果你没有设置配置文件,它将会去寻找conf下的spark-defaults.conf

3. 命名函数的使用

def main(args: Array[String]): Unit = {
  println(speed(time = 10, distance = 100))
}

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

4. 可变参数的使用

  • JDK5+:可变参数
def main(args: Array[String]): Unit = {
  println(sum(1, 2))
  println(sum(1, 2, 3))
}

def sum(numbers:Int*) = {
  var result = 0
  for (number <- numbers) {
    result += number
  }
  result
}

5. 循环表达式

  • to:左闭右闭
  • Range:左闭右开
  • until:左闭右开
scala> 1 to 10
res0: scala.collection.immutable.Range.Inclusive = Range 1 to 10

scala> Range(1,10)
res1: scala.collection.immutable.Range = Range 1 until 10

scala> 1.to(10)
res2: scala.collection.immutable.Range.Inclusive = Range 1 to 10

scala> Range(1,10,2)
res3: scala.collection.immutable.Range = inexact Range 1 until 10 by 2

scala> Range(10,1,-1)
res4: scala.collection.immutable.Range = Range 10 until 1 by -1

scala> 1 until 10
res5: scala.collection.immutable.Range = Range 1 until 10
  • for循环
def main(args: Array[String]): Unit = {
  for (i <- 1 to 10 if i % 2 == 0) {
    println(i)
  }

  val courses = Array("Hadoop", "Spark", "Storm")
  for (course <- courses) {
    println(course)
  }

  // course 其实就是 courses 里面的每个元素
  // => 就是将左边的 course 作用上一个函数,变成另外一个结果
  // println 就是作用到 course 上的一个函数
  courses.foreach(course => println(course))
}
  • while循环
def main(args: Array[String]): Unit = {
  var (num, sum) = (100, 0)
  while (num > 0) {
    sum = sum + num
    num = num - 1
  }
}



4. Scala面向对象

1. 面向对象概述

  • 封装:属性,方法封装到类中
  • 继承:父类和子类之间的关系
  • 多态:父类引用指向子类对象
    • 开发框架的基石

2. 类的定义与使用

object SimpleObjectApp {

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

    println("invoke eat method: " + person.eat())

    person.watchFootball("Barcelona")

    person.printInfo()
  }
}

class People {

  // 定义属性
  var name:String = _
  val age:Int = 10
  private [this] val gender = "male"

  // 定义方法
  def eat():String = {
    name + "eat..."
  }

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

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

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, "M")
    println(person2.name + ":" + person2.age + ":" + person2.school + ":" + person2.gender)
  }
}

// 主构造器
class Person(val name:String, val age:Int) {

  println("Person Constructor enter..." )

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

  // 附属构造器
  def this(name: String, age:Int, gender:String) {
    this(name, age)  // 附属构造器的第一行的代码必须要调用主构造器或者其他附属构造器
    this.gender = gender
  }

  println("Person Constructor leave..." )
}

4. 继承与重写

  • 继承
object ConstructorApp {

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

    val student = new Student("zhangsan", 18, "Math")
    println(student.name + ":" + student.major)
  }
}

// major 属性需要使用var修饰
class Student(name:String, age:Int, var major:String) extends Person(name, age) {

  println("Student Constructor enter..." )

  println("Student Constructor leave..." )
}
  • 重写
class Student(name:String, age:Int, var major:String) extends Person(name, age) {

  println("Student Constructor enter..." )

  override val school = "peking"

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

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

5. 抽象类

object AbstractApp {

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

/**
  * 类的一个或者多个方法没有完整的实现(只有定义,没有实现)
  */
abstract class Person2 {

  def speak

  val name:String
  val age:Int
}

class Student2 extends Person2 {
  override def speak: Unit = {
    println("speak")
  }
  override val name: String = "thpffcj"
  override val age: Int = 18
}

6. 伴生类和伴生对象

/**
  * 伴生类和伴生对象
  * 如果有一个class,还有一个与class同名的object
  * 那么就称这个object是class的伴生对象,class是object的伴生类
  */
class ApplyTest {

}

object ApplyTest {

}

7. apply

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

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

    val b = ApplyTest() // ==> Object.apply

    val c = new ApplyTest()
    c()

    // 类名() ==> Object.apply
    // 对象() ==> Class.apply
  }
}

class ApplyTest {
  def apply() = {
    println("Class ApplyTest apply...")
  }
}

object ApplyTest {

  println("Object ApplyTest enter...")

  var count = 0

  def incr = {
    count = count + 1
  }

  // 最佳实践:在Object的apply方法中去new Class
  def apply() = {
    println("Object ApplyTest apply...")

    // 在object中的apply中new class
    new ApplyTest
  }

  println("Object ApplyTest leave...")
}

8. case class

// 通常用在模式匹配
object CaseClassApp {
  def main(args: Array[String]): Unit = {
    println(Dog("wangcai").name)
  }
}

// case class 不用new
case class Dog(name:String)

9. trait

  • Scala中的Triat是一种特殊的概念
  • 首先我们可以将Trait作为接口来使用,此时的Triat就与Java中的接口非常类似
  • 在triat中可以定义抽象方法,就与抽象类中的抽象方法一样,只要不给出方法的具体实现即可
  • 类可以使用extends关键字继承trait,注意,这里不是implement,而是extends,在scala中没有implement的概念,无论继承类还是trait,统一都是extends
  • 类继承trait后,必须实现其中的抽象方法,实现时不需要使用override关键字
  • scala不支持对类进行多继承,但是支持多重继承trait,使用with关键字即可
  • xxx extends ATrait with BTrait
class SparkConf(loadDefaults: Boolean) extends Cloneable with Logging with Serializable {



5. Scala集合

1. 数组

定长数组

scala> val a = new Array[String](5)
a: Array[String] = Array(null, null, null, null, null)

scala> a.length
res0: Int = 5

scala> a(1) = "hello"

scala> a
res2: Array[String] = Array(null, hello, null, null, null)
  • 我们再来看一种其他创建数组的方法,其实它就是用了上面我们提到的apply
scala> val b = Array("hadoop", "spark", "storm")
b: Array[String] = Array(hadoop, spark, storm)

scala> b(1) = "flink"

scala> b
res4: Array[String] = Array(hadoop, flink, storm)
  • 我们看看数组的其他方法
scala> val c = Array(2, 3, 4, 5, 6, 7, 8, 9)
c: Array[Int] = Array(2, 3, 4, 5, 6, 7, 8, 9)

scala> c.sum
res5: Int = 44

scala> c.min
res7: Int = 2

scala> c.mkString(",")
res8: String = 2,3,4,5,6,7,8,9

变长数组

scala> val d = scala.collection.mutable.ArrayBuffer[Int]()
d: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer()
  • 对可变数组进行操作
d += 1
d += 2
d += (3, 4, 5)
d ++= Array(6, 7, 8)
d.insert(0, 0)
d.remove(1)
d.trimEnd(2)
println(d)
  • 我们看到最后的结果是ArrayBuffer(0, 2, 3, 4, 5, 6)
  • 我们如何对变长数组迭代呢
for (i <- (0 until d.length).reverse) {
  println(d(i))
}

for (ele <- d) {
  println(ele)
}
  • 可变数组可以通过toArray方法变为定长数组
scala> d.toArray
res10: Array[Int] = Array()

2. List

scala> val l = List(1, 2, 3, 4, 5)
l: List[Int] = List(1, 2, 3, 4, 5)

scala> l.head
res1: Int = 1

scala> l.tail
res2: List[Int] = List(2, 3, 4, 5)

scala> Nil
res0: scala.collection.immutable.Nil.type = List()

scala> val l2 = 1 :: Nil
l2: List[Int] = List(1)

scala> val l3 = 2 :: l2
l3: List[Int] = List(2, 1)
  • 我们也可以创建变长list
scala> val l5 = scala.collection.mutable.ListBuffer[Int]()
l5: scala.collection.mutable.ListBuffer[Int] = ListBuffer()

scala> l5 += (4, 5, 6, 7)
res3: l5.type = ListBuffer(4, 5, 6, 7)

scala> l5.toList
res4: List[Int] = List(4, 5, 6, 7)

scala> l5.toArray
res5: Array[Int] = Array(4, 5, 6, 7)
  • 接下来我们看个方法,这个方法可以完成求和操作
def sum(nums:Int*):Int = {
  if (nums.length == 0) {
    0
  } else {
    nums.head + sum(nums.tail:_*)
  }
}

3. Set

scala> val set = Set(1, 2, 2, 1, 4, 3)
set: scala.collection.immutable.Set[Int] = Set(1, 2, 4, 3)

4. Map

  • Map(映射)是一种可迭代的键值对(key/value)结构
  • 所有的值都可以通过键来获取
  • Map 中的键都是唯一的
  • Map 也叫哈希表(Hash tables)
  • Map 有两种类型,可变与不可变,区别在于可变对象可以修改它,而不可变对象不可以
  • 默认情况下 Scala 使用不可变 Map。如果你需要使用可变集合,你需要显式的引入 import scala.collection.mutable.Map 类
// 空哈希表,键为字符串,值为整型
var A:Map[Char,Int] = Map()

// Map 键值对演示
val colors = Map("red" -> "#FF0000", "azure" -> "#F0FFFF")
  • 定义 Map 时,需要为键值对定义类型。如果需要添加 key-value 对,可以使用 + 号
A += ('I' -> 1)
A += ('J' -> 5)

Map 基本操作

  • keys:返回 Map 所有的键(key)
  • values:返回 Map 所有的值(value)
  • isEmpty:在 Map 为空时返回true
object Test {
   def main(args: Array[String]) {
      val colors = Map("red" -> "#FF0000",
                       "azure" -> "#F0FFFF",
                       "peru" -> "#CD853F")

      val nums: Map[Int, Int] = Map()

      println( "colors 中的键为 : " + colors.keys )
      println( "colors 中的值为 : " + colors.values )
      println( "检测 colors 是否为空 : " + colors.isEmpty )
      println( "检测 nums 是否为空 : " + nums.isEmpty )
   }
}
  • 输出结果为:
colors 中的键为 : Set(red, azure, peru)
colors 中的值为 : MapLike(#FF0000, #F0FFFF, #CD853F)
检测 colors 是否为空 : false
检测 nums 是否为空 : true
  • 你可以使用 ++ 运算符或 Map.++() 方法来连接两个 Map,Map 合并时会移除重复的 key
object Test {
   def main(args: Array[String]) {
      val colors1 = Map("red" -> "#FF0000",
                        "azure" -> "#F0FFFF",
                        "peru" -> "#CD853F")
      val colors2 = Map("blue" -> "#0033FF",
                        "yellow" -> "#FFFF00",
                        "red" -> "#FF0000")

      //  ++ 作为运算符
      var colors = colors1 ++ colors2
      println( "colors1 ++ colors2 : " + colors )

      //  ++ 作为方法
      colors = colors1.++(colors2)
      println( "colors1.++(colors2)) : " + colors )

   }
}
  • 以下通过 foreach 循环输出 Map 中的 keys 和 values
object Test {
   def main(args: Array[String]) {
      val sites = Map("runoob" -> "http://www.runoob.com",
                       "baidu" -> "http://www.baidu.com",
                       "taobao" -> "http://www.taobao.com")

      sites.keys.foreach{ i =>  
                           print( "Key = " + i )
                           println(" Value = " + sites(i) )}
   }
}

5. Option & Some & None

object OptionApp extends App {

  val m = Map(1 -> 2)
  println(m.get(1))
  println(m.getOrElse(2, "None"))
}

/**
  * final case class Some[+A](@deprecatedName('x, "2.12.0") value: A) extends Option[A] {
  *   def isEmpty = false
  *   def get = value
  * }
  *
  * case object None extends Option[Nothing] {
  *   def isEmpty = true
  *   def get = throw new NoSuchElementException("None.get")
  * }
  */

6. Tuple

  • 与列表一样,元组也是不可变的,但与列表不同的是元组可以包含不同类型的元素
  • 元组的值是通过将单个的值包含在圆括号中构成的
scala> val t = (1, 3.14, "Fred")  
t: (Int, Double, String) = (1,3.14,Fred)
  • 访问元组的元素可以通过数字索引,我们可以使用 t._1 访问第一个元素, t._2 访问第二个元素
object Test {
   def main(args: Array[String]) {
      val t = (4,3,2,1)

      val sum = t._1 + t._2 + t._3 + t._4

      println( "元素之和为: "  + sum )
   }
}
  • 你可以使用 Tuple.productIterator() 方法来迭代输出元组的所有元素
object Test {
   def main(args: Array[String]) {
      val t = (4,3,2,1)

      t.productIterator.foreach{ i =>println("Value = " + i )}
   }
}