Scala基础

简介

  • 面向对象
  • 函数式编程
  • Scala需要使用Java的JVM
  • 静态语言类型

面向对象

Scala中每个值都是一个对象

Scala严格区分方法和函数

三大特征

  • 封装
  • 继承
  • 多态

函数式编程

  • 函数可以当做值进行传递
  • 无副作用
  • 函数是一等公民
  • 以表达式为中心
  • 以纯粹的函数构造程序

轻量级语法定义匿名函数,支持高阶函数,允许函数嵌套

静态语言

  • 事先声明变量类型
  • 类型不能改变
  • 编译时检查

动态语言:

不用事先声明;

随时可以为其赋值(任意类型);

编译时不知道是什么类型

强类型,弱类型

强类型:不同类型之间的操作,必须强制类型为同一种数据类型

弱类型:不同类型之间可以直接操作,存在隐式转换

下载安装

https://www.scala-lang.org/

idea下载Scala插件

基础

https://docs.scala-lang.org/zh-cn/tour/tour-of-scala.html

大体同Java

object关键字定义的类的main方法为入口方法

分号不是必须的,一般也不写

return也不是必须的,一般也不写

Scala中所有地方,默认最后一条语句的结果作为返回值

object Demo {
  def main(args: Array[String]): Unit = {
    println(1, 3, 4, 5)
    print("结束"+0)
  }
}

定义变量

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

数据类型可以自动推断(赋值的情况),根据值的类型推断

可以同时给多个变量赋值:

val (a,b,c) = (1,'p',"c")	//类型自动推断
// a = 1
// b = 'p'
// c = "c"
var d,e = 10
// d = 10
// e = 10

var

使用var定义变量代表改变量的值可变

val

使用var定义变量代表改变量的值不可变

Scala中所有数据类型皆为引用数据类型

使用val定义的变量的值(地址)不可变,但其引用的对象中的值(对象的属性)可以改变

相当于Java中使用了final关键字

lazy

关键字,惰性变量,只有val修饰的变量可以使用lazy修饰。

使用lazy定义的变量,只有在调用的时候才会实例化这个变量。

scala> lazy val a = 100
a: Int = 

scala> a
res0: Int = 100

数据类型

https://docs.scala-lang.org/resources/images/tour/unified-types-diagram.svg

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XkTI6HP7-1570532480980)(E:\大数据工具下载\scala\笔记\assets\1569665608894.png)]

Unit类似于Java中的void,但是Unit可以作为返回值进行接收

Nothing是所有类型的子类,但是它不存在,它没有具体的实例对象,一般常用引用、抛异常、程序exit退出、无限循环

Null 是代表没有引用多用于 ,集合,数组,Option,自定义类的赋值

基本数据类型

相当于Java中的八个基本数据类型的包装类 和 一个Unit

这八个基本数据类型不是Java中的包装类,比Java中的包装类强大一些

整数类型默认推断为Int

浮点类型默认推断为Double

字符类型可以写Unicode值,如'\u0093'

字符串类型

完全是Java中的String类

其他类型

Any相当于Java中的Object类,是所有类的根类

Anyval 接收基础数据类型

AnyRef 接收引用类型

操作符

Scala操作符和Java使用方式一致

没有++

没有–

可以使用其他的代替

如+=

如-=

Scala重载了一大堆运算符

分支

if(条件表达式){

执行代码

}

val x = 3
if(x > 2){
    println("x>2")
}

val x2 = if(x > 2){
    x - 2
}else if(x < 0){
    "x的值小于0"
}else {
    x
}

pirntln(x2)

循环

三种循环:while、do-while、for

while与do-while与Java一模一样,不写了

for循环与Java完全不同

for(变量 <- 范围 [守卫]){

执行语句

}

这个范围可以是表达式、数组、集合

这个守卫是一个if语句,如if(i>0)

i为变量

括号可以省略

for (i <- 1 to 10 if i % 2 == 0){
    println(i)
}

这里的to是一个方法,同1.to(10),代表从1到10,包含1和10

还有一个方法until,1.until(10),同样代表1到10,包含1,但是不包含10

1 to 10
1 until 10
1.to(10)
1.until(10)

两种写法是一样的,Scala支持这么写

终止循环

Scala里面推荐使用函数式的风格解决break和continue的功能,而不是一个关键字

  • return,注意使用场景
  • 使用Boolean变量,while(flag),在外部定义变量为true,在内部条件满足改为false,适用于while,do-while
  • 函数式风格,导包:import scala.util.control.Breaks._
import scala.util.control.Breaks._

// break
breakable{
    for(i <- 1 to 10){
        if(i == 8) break
    }
}

// continue
for(i <- 1 to 10){
    breakable{
        if(i == 8) break
    }
}

练习,万年历

import scala.io.StdIn

/**
 * @author ZJHZH
 */
object Exercise {
  def main(args: Array[String]): Unit = {
    //输入年月
    print("请输入年份:")
    val year: Int = StdIn.readInt()
    print("请输入月份:")
    val month: Int = StdIn.readInt()

    // 是否闰年
    val flag = leap_year(year)

    // 月天数
    val days = getDays(month, flag)

    // 距离1900年1月1日的总天数
    val day1 = getDaysfrom1900(year)
      
    // 距离当年(输入年)一月一日的天数
    val day2 = getDayYear(month)

    // 获取第一天周几
    val day_week = getDayOfWeek(day1 + day2)

    // 打印日历
    showCalendar(days, day_week)
  }

  /**
   * 判定当前年是不是闰年
   *
   * @author ZJHZH
   * @param year 年份
   * @return Boolean
   */
  def leap_year(year: Int): Boolean = {
    if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {
      return true
    } else {
      return false
    }
  }

  //  /**
  //   * 获取每月最大天数
  //   *
  //   * @author ZJHZH
  //   * @param year
  //   * @param month
  //   * @return Int 天数
  //   */
  //  def getMaxDaysMonth(year: Int, month: Int): Int = {
  //    val date1 = Calendar.getInstance()
  //    date1.set(Calendar.YEAR, year)
  //    date1.set(Calendar.MONTH, month)
  //    date1.getActualMaximum(Calendar.DATE)
  //  }
  /**
   * 获取当月最大天数
   *
   * @param month
   * @param flag 是否闰年
   * @return Int 天数
   */
  def getDays(month: Int, flag: Boolean = false): Int = {
    if (month == 2) {
      if (flag) {
        return 29
      } else {
        return 28
      }
    }
    if (month == 4 || month == 6 || month == 9 || month == 11) {
        return 30
    }
    return 31
  }

  /**
   * 循环
   * 使用循环计算1900年到输入年份之间的天数,不包含输入年份
   *
   * @param year
   * @return
   */
  def getDaysfrom1900(year: Int): Int = {
    var days = 0
    for (i <- 1900 until year) {
      if (leap_year(i)) {
        days += 366
      } else {
        days += 365
      }
    }
    return days
  }

  /**
   * 使用循环计算用户输入的月份距离当年1月1日共多少天
   *
   * @param month
   * @param flag 是否闰年
   * @return Int 天数
   */
  def getDayYear(month: Int, flag: Boolean = false): Int = {
    var days = 0
    for (i <- 1 until month) {
      days += getDays(i, flag)
    }
    return days
  }

  /**
   * 获取当月第一天是周几
   * 0 -> 周日
   * 6 -> 周六
   *
   * @param days
   * @return
   */
  def getDayOfWeek(days: Int): Int = {
    (days + 1) % 7
  }

  def showCalendar(days: Int, day_Week: Int): Unit = {
    println("日\t一\t二\t三\t四\t五\t六\t")
    var week = 0
    for (i <- 0 until day_Week) {
      week += 1
      print("\t")
    }
    for (i <- 1 to days) {
      if (week >= 7) {
        println()
        week = 0
      }
      print(i + "\t")
      week += 1
    }
  }
}

方法

定义方法使用def关键字

def 方法名(参数列表)[:返回值类型] = {

方法体

}

  • Scala中的方法可以定义在类中,也可以定义在方法体中
  • def不可以省略,它表示这是一个方法
  • Scala中没有静态方法,但有apply方法代替
  • 命名:同其他语言,如Java
  • 参数列表和Java完全不同
  • 返回值类型可以省略,让系统自动推断
  • return可以不要,同上所说,Scala任意位置,最后一行代码,结果既是返回值
// 标准版
def getDayOfWeek(days: Int): Int = {
   return (days + 1) % 7
}
// 简化版
def getDayOfWeek(days: Int) = {
   (days + 1) % 7
}
// 再简化,因为方法体只有一条语句,所以:
def getDayOfWeek(days: Int) = (days + 1) % 7
// 也可以这么写,等号大括号二选一保留
def getDayOfWeek(days: Int){(days + 1) % 7}

// 没参数,也没有返回值
def method1() = println("打印方法")
// 更简一步
def method2 = println("更简")

// 默认值
def sum(x:Int,y:Int = 2) = x + y
// 这时候在调用的时候就 可以 只传x的值,同样可以执行

// 可变参数
def sum(x:Int*) = {
    var num = 0
    for (i <- x){
        num += i
    }
    num
}
// 调用可变参数
sum(1,2,3,4)
// 传入数组时,需要转换类型才能传入,转变方式在数组或数组名后面添加:_*
// 注意这个下划线
sum(Array(1,2,3,4):_*)

// 递归,方法支持递归,但是一定要写明返回值类型
def fab(n:Int):Int={
    if((n==1)||(n==2)){
        return 1
    }else{
        return fab(n-1)+fab(n-2)
    }
}

函数

Scala中是区分函数和方法的,函数是函数,方法是方法。定义函数和定义方法的方式完全不同

但是,方法可以当函数使用,因为方法可以隐式转换为函数

函数一般写在方法体中,同样也可以写在类体中

写在方法体中的最多

函数同样可以使用return关键字,但是要注意return会结束方法,注意函数定义、执行的位置

val 函数名[:(参数类型列表)=>返回值类型]= (参数列表) => {函数体}

一般不写这个函数的类型

// 完全体
val getDays: (Int,Boolean) => Int = (month: Int, flag: Boolean) => {
  var days = 0
  if (month == 2) {
    if (flag) {
      days = 29
    } else {
      days = 28
    }
  } else if (month == 4 || month == 6 || month == 9 || month == 11) {
    days = 30
  } else {
    days = 31
  }
  days
}
// 稍简
val getDays: (Int,Boolean) = (month: Int, flag: Boolean) => {
  var days = 0
  if (month == 2) {
    if (flag) {
      days = 29
    } else {
      days = 28
    }
  } else if (month == 4 || month == 6 || month == 9 || month == 11) {
    days = 30
  } else {
    days = 31
  }
  days
}
// 最常用
val getDays = (month: Int, flag: Boolean) => {
  var days = 0
  if (month == 2) {
    if (flag) {
      days = 29
    } else {
      days = 28
    }
  } else if (month == 4 || month == 6 || month == 9 || month == 11) {
    days = 30
  } else {
    days = 31
  }
  days
}
// 整体简化规则同方法
// 但是这个不是函数,这个是变量
val function_1 = println("更简")

// 将函数转换为方法,注意这个下划线
// 格式:方法名+空格+下划线
val f2 = methodName _

// Scala是提供隐式转化的,会在传入的时候自动转换

集合

一种存储各种对象和数据的容器

Scala中集合分为两大类:

  • 可变集合 包:scala.collection.mutable

  • 不可变集合 包:scala.collection.immutable

不可变集合是可以并发访问的

可不可变指的是集合的长度(大小)

Scala中默认使用的是不可变集合

Scala三大集合:

  • Seq :序列
  • Set :集
  • Map :映射

集合默认继承于Iterable

使用最多的还是List,Set,Map

Map

  • Map
  • HashMap
  • TreeMap

Map默认是不可变的,底层实现是HashMap,可以导包,有可变的

HashMap是可变的

TreeMap是不可变的,会根据key值进行排序,key需要具备可比性,最低要求是数据类型一致

import scala.collection.mutable
object MapDemo{
    def main(args: Array[String]): Unit = {
        // 不可变Map
        // 第一种赋值方式 Map(key->value)
        val map1 = Map("zhangsan"->90,"lisi"->20,"wang"->1)
        // 第二种赋值方式 元组赋值 Map((key,value))
        val map2 = Map(("zhangsan",20),("lisi",19))
        
        // 可变Map
        val map3 = mutable.Map("zhangsan"->90,"lisi"->20,"wang"->1)
        
        
        // 访问值,可变不可变是一样的
        // 1.直接通过key访问,不推荐,不存在,抛异常
        val a1 = map3("wangwu")
        
        // 2.在1的基础上加个判断,和Java中一致,在Scala中一般般
        val a2 = if(map3.contains("wangwu")) map3("wangwu") else 0
        
        // 3.使用Scala中的get方法,返回一个Option类型的值
        // Option有两个子类,some()和None
        // some(value值)  key存在,值取出来了,就在some中
        // None    key不存在,没有value值
        val a3 = map3.get("lisi")
        val a4 = map3.get("wangwu")
        // 问题来了,some中的值怎么取出来:模式匹配
        
        // 4.使用getOrElse(key,默认值),是第二种的综合
        val a5 = map3.getOrElse("wangwu",90)
        
        // 修改map中的信息
        // 只能是可变Map
        // 只能是可变Map
        // 只能是可变Map
        
        // 1.通过key修改对应的value,key不存在则添加
        map3("wangwu") = 40
        // 2.添加,重载了运算符
        map3 += ("zhaoliu"->50)
        // 3.添加另一个Map集合,添加的这个(等号右面)可变不可变无所谓
        map3 ++= Map(("tianqi",20))
        
        // 删除一个键值对,同样重载了运算符
        map3 - "wangwu"
        
        
        // 遍历
        // keyset
        for(e <- map3.keyset){
            println(e)
        }
        // keys方法,获取迭代器
        for(e <- map3.keys){
            println(e)
        }
        // values方法,获取迭代器
        for(e <- map3.values){
            println(e)
        }
        // 直接打印Map,利用对偶元组
        for((k,v) <- map3){
            println(k + "\t" + v)
        }
    }
}

数组

定长数组

内容可变,长度不可变

// 定义方式,两种
// new
var arr1 = new Array[Int](9) // 长度为9
// 使用apply方法
var arr2 = Array(10,20,30,'b') // 常用

// 定长数组不可以直接打印,会打出数组的地址
// 可以转化数据类型 toBuffer toList ...
// 一般toBuffer,toBuffer表示转化为变长数组
println(arr2.toBuffer)
println(arr2(0)) // 打印下标0的元素
arr2(2) = 2000

// 添加元素,无法在原数组进行添加,只能拼接成一个新的数组
val ints: Array[Int] = arr2.+:(9999)

变长数组

内容可变,长度也可变

// 导包
import scala.collection.mutable
// 创建变长数组,默认还是创建不可变数组,需要加上包名
var arr1 = mutable.ArrayBuffer[Int]()
var arr2 = mutable.ArrayBuffer[Int](10,20,30)

// 添加数据,不生成新数组;如果是定长数组,这时候地址会变成新生成的定长数组的地址
arr2 += 1
// 定长数组没这个方法
arr2.append(1)

// 添加多个
arr2 += (2,3,9)
arr2.append(2,3,9)

// 添加数组
arr2 ++= Array(4,5,6)

// 在指定位置插入值
// insert(下标,可变参数)
arr2.insert(1,19,18)

// 变长数组变成定长数组
var array:Array[Int] = arr2.toArray

数组遍历

// 定长,变长,都一样,下标形式
for (i <- 0 until arr2.length) = println(arr2(i))
// 元素遍历
for (i <- arr2) = println(arr2(i))

常见操作

// 求和
arr2.sum
// 过滤,需要一个Boolean类型返回值的一个参数的函数,注意这个下划线
// (e)=>{e < 10}  简化为  e => e < 10   再简化   _ < 10
arr2.filter(_ < 10)
// 数据操作,简化方式同上
arr2.map(_ * 2)
// 排序,默认升序,你也自定义不了
arr2.sorted
// 但是可以翻转一下
arr2.sorted.reverse

数组操作生成新的数组,原数组不变,该接收的接收

元组

一个轻量级的集合,可以存储任意数据类型,可以存多个

(e1,e2,e3,…)

获取元素:

_1:第一个

_2:第二个

……

对偶元组:以k,v形式存储的元组(俩元素的元组),最常用的元组

// 创建元组
val t1 = ("sparkcore","sparkSQL","sparkStreaming")

// 获取值,通过对应的变量来存储对应元素的值,同变量的定义:多个变量赋值
val t2,(a,b,c) = ("sparkcore","sparkSQL","sparkStreaming")

// new一个,1代表可以存无限个,3代表存三个
val t3 = new Tuple1(1,2,3)
val t4 = new Tuple3(1,2,3)

// 遍历 productElement(下标值)
for(e <- t1.productElement){
    println(e)
}

// 遍历:打印:foreach
t1.productIterator.foreach(println(_))

拉链操作

将两个数组,集合,组成元组

  • zip:左边和右边组合,左边为key,以长度最短的为基准,直要没有匹配项就停止
  • zipAll:以长的为基准,第一个参数为匹配的value,第二个参数为key的默认值,第三个参数为value的默认值
  • zipWithIndex:不需要传参数,以下标为value
  • unzip:解耦,将key存一个集合(数组),value存一个集合(数组),这两个集合(数组)组成对偶元组
object ZipDemo {
  def main(args: Array[String]): Unit = {
    // zip将两个集合中元素拼接成kv形式对偶元组形式
    // 数组和集合用法一致
    val names = List("xiaobao","xiaohong","xiaoming")
    val moreList = List(List(1,2,3),List(2,3,4))
    println(moreList)
    val scores1 = List(16,5,9,10,20)
    val tuples: List[(String, Int)] = names.zip(scores1)
    val tuples1: List[(io.Serializable, Any)] = names.zipAll(scores1,"null",0)
    println(tuples)
    println(tuples1)
    val index: List[(String, Int)] = names.zipWithIndex
    println(index)
    val unzip: (List[io.Serializable], List[Any]) = tuples1.unzip
    println(unzip)
  }
}

List

List可以存储不同的数据类型,并且有序,不可变

object ListDemo {
  def main(args: Array[String]): Unit = {
      // 创建不可变List
      val empty = List()
      
      // 创建一个有值的集合
      val names = List("xiaobai","xiaohong","xiaohuang")
      // 集合是可以存储多个值的(集合中存集合)
      val moreList = List(List(1,2,3),List(2,3,4))
      
      // 列表中有一个默认的空值 nil 它可以和 :: 运算符进行列表赋值操作
      // Nil 和 :: 的用法
      val strings:List[String] = "小明" :: "小红" :: "小黄" :: Nil
      
  }
}

练习,一个Scala版的WordCount

package WordCount

/**
 * @author ZJHZH
 */
object Scala_WordCount {
  def main(args: Array[String]): Unit = {
    val list: List[String] = List("hello tom hello jerry","hello suke hello"," hello tom")
    val strings: List[String] = list.flatMap((_: String).trim.split(" "))
    val tuples: List[(String, Int)] = strings.map(((_: String), 1))
    val tuples1: List[(String, Int)] = tuples.sortBy((_: (String, Int))._1)
    val stringToTuples: Map[String, List[(String, Int)]] = tuples1.groupBy((_: (String, Int))._1)
    val stringToInt: Map[String, Int] = stringToTuples.map((e: (String, List[(String, Int)]))=>(e._1,e._2.map((_: (String, Int))._2).sum))
    println(stringToInt)
  }
}

val tuples: List[(String, Int)] = List(("hello tom hello jerry", 3), ("hello suke hello", 9), (" hello tom", 2))
val stringToInt: Map[String, Int] = tuples.flatMap((e: (String, Int)) => {
  e._1.trim.split(" ").map(((_: String), e._2))
}).groupBy((_: (String, Int))._1).map((e: (String, List[(String, Int)])) => {
  (e._1, e._2.map((_: (String, Int))._2).sum)
})
println(stringToInt)
val line: List[Array[String]] = List(Array(" hello tom hello jerry"), Array(" hello xiaobai hello "), Array("hello tom "))

val tuples: List[(String, Int)] = line.flatMap((_: Array[String]).flatMap((_: String).trim.split(" ")).map(((_: String), 1)))

println(tuples)

高阶函数

高阶函数是指使用其他函数作为参数、或者返回一个函数作为结果的函数。

map

map遍历集合中的元素并可以对其进行操作。

遍历并对每一个元素进行操作

map[B, That](f: A => B)(implicit bf: CanBuildFrom[List[A], B, That]): That

map的使用很简单:

  • 第一个中括号是泛型,不需要写
  • 第二个是小括号,是一个函数,可以由传入类型A经过函数运算得出任意类型B
  • 第三个是隐式函数参数,一般没有写的可能性
// 一些极简单的使用
// 将list中的每个元素乘2,注意这会生成一个新的集合,需要接收
List(1,2,3,4).map(e:Int=>e * 2)
List(1,2,3,4).map(_ * 2)
// 这个函数可以写复杂,可以在外部定义好,传入即可

foreach同样是遍历集合,但是它一般不用来操作数据,一般只用来打印,和Java中使用基本一致

sum

def sum[B >: A](implicit num: Numeric[B]): B = foldLeft(num.zero)(num.plus)

求和,调用的时候一般不传参数,隐式函数参数一般情况下都不进行修改

List(1,2,3,4).sum

max,min同

reduce

def reduce[A1 >: A](op: (A1, A1) => A1): A1 = reduceLeft(op)

reduce 需要传入一个函数,这个函数定义了计算方式

reduce方法求和的时候,这个方法需要两个参数,这两个参数有处理逻辑
第一个参数 分为
第一次 会获取集合中第一个元素的值
以后每次计算 每次获取的是上一次 第一个参数+第二参数的和
第二参数 分为
第一次 获取集合中第二个元素的值
以后每次计算 开始获取集合中第二个元素后面每一个元素的值,然后参与运算

// 单线程
val sumed3 = arr.reduce((x,y)=>{x+y})
// 多线程
val sumed3_1 = arr.par.reduce((x,y)=>{x+y})
// 求差值,在并行化中,需要使用reduceleft

refuceLeft,reduceRight使用方式同reduce,但是reduceLeft在并行化状态也是准确的,原理为:

reduceLeft在并行化中,计算依旧是从左向右计算,只有左边的计算完毕再将值带入后续计算

val i: Int = List(1, 2, 7, 4, 9, 3, 2).reduce((e1, e2) => {
  println("e1\t" + e1)
  println("e2\t" + e2)
  e1 + e2
})
println(i)

fold

同reduce,但是fold有两个参数列表,第一个参数列表为每次计算的默认值,第二个参数列表同reduce。

第二个参数列表为一个函数,这个函数有两个参数,同reduce,但是参数逻辑有一点点不同

第一个参数为上一次第一个参数与第二个参数计算的值(或初次计算时获取第一个参数列表的值–默认值)

第二个参数为集合中下一个没有计算的元素

def fold[A1 >: A](z: A1)(op: (A1, A1) => A1): A1 = foldLeft(z)(op)

println(List(1, 2).fold(0)(_ + _ * 2))

foldLeft,foldRight使用方式同fold,但是foldLeft是做差值也准确的,计算原理同reduce,reduceLeft

foldRight是从右向左计算的,其他同foldLeft

forall

def forall(p: A => Boolean): Boolean = {
  var these = this
  while (!these.isEmpty) {
    if (!p(these.head)) return false
    these = these.tail
  }
  true
}

遍历整个集合,一旦碰到false则停止遍历,结果即为false;否则为true

println(List(1, 23, 5, 8).forall(_ < 10))

filter

过滤器,同Java中使用一模一样。满足条件的留下,生成一个新的集合。

def filter(p: A => Boolean): Repr = filterImpl(p, isFlipped = false)
private def filterImpl(p: A => Boolean, isFlipped: Boolean): Repr = {
  val b = newBuilder
  for (x <- this)
    if (p(x) != isFlipped) b += x
  b.result
}

println(List(1, 2, 34, 6, 7, 9, 1).filter(_ > 8))

filter有一个参数列表,参数是一个函数。

filterNot逻辑与之正好相反。

sorted

排序,不需要传参,因为它有自动传入的隐式函数,一般不做处理,同样,它也会生成一个新的集合。

def sorted[B >: A](implicit ord: Ordering[B]): Repr = {
  val len = this.length
  val b = newBuilder
  if (len == 1) b ++= this
  else if (len > 1) {
    b.sizeHint(len)
    val arr = new Array[AnyRef](len)  // Previously used ArraySeq for more compact but slower code
    var i = 0
    for (x <- this) {
      arr(i) = x.asInstanceOf[AnyRef]
      i += 1
    }
    java.util.Arrays.sort(arr, ord.asInstanceOf[Ordering[Object]])
    i = 0
    while (i < arr.length) {
      b += arr(i).asInstanceOf[A]
      i += 1
    }
  }
  b.result()
}
println(List(1, 2, 34, 6, 7, 9, 1).sorted)

sortwith

需要传入一个参数列表,是一个函数,函数有两个参数,需要一个布尔类型的返回值。

自定义排序规则

def sortWith(lt: (A, A) => Boolean): Repr = sorted(Ordering fromLessThan lt)

println(List(1, 2, 34, 6, 7, 9, 1).sortWith(_ > _))	// 降序

sortby

指定按什么排序。两个参数列表,第二个是隐式函数不用管。第一个参数列表是一个函数,需要一个参数,会有一个任意返回值类型,可以自定义排序规则。

def sortBy[B](f: A => B)(implicit ord: Ordering[B]): Repr = sorted(ord on f)

println(List(1, 2, 34, 6, 7, 9, 1).sortBy(_))

groupby

分区,同SQL。返回值是一个Map集合,键值对,一一对应。参数是一个函数,函数一个参数,任意返回值类型,这函数返回值就是key,与这个key对应的就是获得这个key的参数的List集合。

def groupBy[K](f: A => K): immutable.Map[K, Repr] = {
  val m = mutable.Map.empty[K, Builder[A, Repr]]
  for (elem <- this) {
    val key = f(elem)
    val bldr = m.getOrElseUpdate(key, newBuilder)
    bldr += elem
  }
  val b = immutable.Map.newBuilder[K, Repr]
  for ((k, v) <- m)
    b += ((k, v.result))
    
  b.result
}


println(List(1, 2, 34, 6, 7, 9, 1).groupBy(e=>e))
// Map(1 -> List(1, 1), 6 -> List(6), 9 -> List(9), 2 -> List(2), 34 -> List(34), 7 -> List(7))

partition

分区,同filter的使用,一个参数,是函数,这个函数一个参数和一个布尔类型的返回值。

def partition(p: A => Boolean): (Repr, Repr) = {
  val l, r = newBuilder
  for (x <- this) (if (p(x)) l else r) += x
  (l.result, r.result)
}


println(List(1, 2, 34, 6, 7, 9, 1).partition(e=>e > 8))
// (List(34, 9),List(1, 2, 6, 7, 1))

find

查找,在集合中查找第一个满足条件的元素,条件同filter,返回值是一个Option类型。

def find(p: A => Boolean): Option[A] = {
  var these = this
  while (!these.isEmpty) {
    if (p(these.head)) return Some(these.head)
    these = these.tail
  }
  None
}

println(List(1, 2, 34, 6, 7, 9, 1).find(e=>e > 8))

takeWhile

匹配元素,直到匹配不上为止,将匹配上的返回,返回一个新的集合。

@inline final override def takeWhile(p: A => Boolean): List[A] = {
  val b = new ListBuffer[A]
  var these = this
  while (!these.isEmpty && p(these.head)) {
    b += these.head
    these = these.tail
  }
  b.toList
}

println(List(1, 2, 34, 6, 7, 9, 1).takeWhile(e=>e < 8))
// List(1, 2)

take

获取前N个元素,从左向右。如需从右向左,可以使用takeRight。

override def take(n: Int): List[A] = if (isEmpty || n <= 0) Nil else {
  val h = new ::(head, Nil)
  var t = h
  var rest = tail
  var i = 1
  while ({if (rest.isEmpty) return this; i < n}) {
    i += 1
    val nx = new ::(rest.head, Nil)
    t.tl = nx
    t = nx
    rest = rest.tail
  }
  h
}

println(List(1, 2, 34, 6, 7, 9, 1).take(4))
// List(1, 2, 34, 6)

flatten

将复杂集合中数据进行扁平化处理,参数是一个隐式函数,不需要传参,直接用即可。

def flatten[B](implicit asTraversable: A => /*<:

flatMap

是flatten和map的综合版。

将数据进行扁平处理并遍历。

final override def flatMap[B, That](f: A => GenTraversableOnce[B])(implicit bf: CanBuildFrom[List[A], B, That]): That = {
  if (bf eq List.ReusableCBF) {
    if (this eq Nil) Nil.asInstanceOf[That] else {
      var rest = this
      var found = false
      var h: ::[B] = null
      var t: ::[B] = null
      while (rest ne Nil) {
        f(rest.head).seq.foreach{ b =>
          if (!found) {
            h = new ::(b, Nil)
            t = h
            found = true
          }
          else {
            val nx = new ::(b, Nil)
            t.tl = nx
            t = nx
          }
        }
        rest = rest.tail
      }
      (if (!found) Nil else h).asInstanceOf[That]
    }
  }
  else super.flatMap(f)
}
println(List(List(1, 2, 3), List(4, 5, 6), List(7, 8), List(9)).flatMap(_.toList))
// List(1, 2, 3, 4, 5, 6, 7, 8, 9)

aggregat

def aggregate[S](z: =>S)(seqop: (S, T) => S, combop: (S, S) => S): S = {
  tasksupport.executeAndWaitResult(new Aggregate(() => z, seqop, combop, splitter))
}

两个参数列表,第一个参数列表一个参数,为默认值,又或者叫初值;

第二个参数列表两个参数,为两个函数,第一个参数为分片内的计算规则,第二个参数为分片之间的计算规则。

单说分片内的计算逻辑与分片之间的计算逻辑都与fold,reduce没什么区别。这个默认值会在没个分片之内计算的时候调用一次。

并行化par

println(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).par.reduce(_ - _))
// 5,这个差值明显不正确
println(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).par.reduceLeft(_ - _))
// -53

List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).par.reduce((e1,e2)=>{
    println("e1\t" + e1)
    println("e2\t" + e2)
    e1 - e2
})

面向对象

面向对象思想和Java中是一样的,只不过语法不同。

Java Scala
class class和object
访问权限修饰符 public、protected、default、private public、private
构造方法 类内部 默认构造和辅助构造
抽象类 抽象方法必须使用abstract关键字 允许属性重写
接口 从JDK1.8开始允许写实现方法(default或static修饰) 名为特质,允许写实现方法
class类本身 有伴生类,伴生对象

Scala中定义类

在scala中描述一个事物需要使用class修饰的类来完成,在Scala中需要执行代码即描述对象的执行过程时需要使用object修饰的类来完成,Scala中只有object修饰类才能写main方法即执行代码。

class Monster(val name: String, var HP: Int, val ATK: Int) extends Fighter {
  def combat(list: List[Fighter]): Unit = {
    val fighter: Fighter = ATKwith(list)
    fighter.beAttacked(ATK)
    println(this.name + "\t普攻\t" + fighter.name)
  }
}

object Monster {
  def apply(name: String, HP: Int, ATK: Int): Monster = new Monster(name, HP, ATK)
}



// 若想使属性只能在当前类访问可以这么写
private[this] val cardID = "123456"

getter和setter

class Ponit {
  private var _x = 0 // 只能在当前了中或伴生对象中进行访问
  // 获取属性 --> 提供 getter方法
  def x = _x
  // 赋值属性 --> 提供setter方法 setter方法名字和getter方法名字类似 所以为了区分
  // 建议setter方法的方法名字 后面添加_
  def x_(x:Int):Unit = {
    _x = x
  }
}

@BeanProperty

@BeanProperty使用了和Java完全相似getter和setter,但是,使用这种方式不能私有化。

import scala.beans.BeanProperty
class Person {
  // 默认和Java中提供的getter和setter是一致的了,不能使用private
  // 这个下划线是占位符
  @BeanProperty var name:String = _
}

构造方法

Scala中没有构造方法重载的概念,为了达到平时对不同属性调用不同构造方法进行初始的方式,scala提供了辅助构造方法

主构造方法

class 类名(参数列表){ --> 主构造方法(只能有一个)
    def this(参数列表){ --> 辅助构造方法
    
    }
}

主构造方法只能写一个,所以一般主构造方法只写无参,其他的使用辅助构造方法写,这样就和Java基本一样。

// 主构造方法中的修饰
private var 属性:数据类型 // 当前属性只能在当前类访问
var 属性:数据类型 // 相当于是共有的getter和setter方法,但是属性是私有化
val 属性:数据类型 // 相当于提供了getter方法,但是不能赋值

主构造方法中的参数可以直接当做属性来使用。

辅助构造方法

辅助构造方法的方法名必须是this,辅助构造方法可以有多个,根据不同参数进行区分。

class Constuctor2(var name:String) {
    var age:Int = _
    //在创建对象的时候,将age属性也进行初始化
    def this(age:Int,name:String){
        //辅助构造方法代码中第一句必须调用 主构造方法 不能将辅助构造方法和主构造声明成一个样式
        this(name)
        this.age = age
    }
}

特质(Trait)

class 类名 extends 特质1 with 特质2 with 特质3 …

class 类名 extends 父类 with 特质1 with 特质2 …

若需要继承类,还要继承特质,一定先继承类,然后再实现特质。

特质可以有属性、抽象方法、实现方法。

trait Flyable {
  // 1.特质中属性的定义
  val speed:Int = 1000
  // 2.可以声明一个没有值的属性 ,当类继承与特质的时候,必须给当前属性进行赋值
  // 这样属性可当做抽象属性看待
  val height:Int
  // 3.定义实现的方法
  def fly :String = {
    "I Can fly"
  }
  // 4.定义抽象方法 --> 不写方法体即可
  def fight:String
}
class Brid extends Flyable{
  //需要实现特质中没有实现的属性和方法,必须添加override关键字
  override val height: Int = 100
  override def fight: String = "战斗"
  //已经在特质中实现方法再次重新实现 (scala中将特质其实就是看做是一个 类 父类)
  override def fly: String = "不想飞了"
}

特殊的继承关系

Scala中特质允许继承类。

特质会获取类中属性和行为。

Java中接口与Scala中特质

  • JDK1.8之前,接口与特质完全不同,接口只能写全局静态常量和抽象类,特质可以定义属性、抽象方法和实现方法
  • JDK1.8开始,接口可以定义静态常量和抽象方法,也可以使用static或default关键字修饰的实现方法,这时候接口和特质就没什么不一样的了
  • 实现的关键字不同:Java中接口实现使用关键字implement,Scala中特质使用关键字extends
  • 继承关系:Java中接口只能继承接口,Scala中特质可以继承特质和类

Scala中的抽象类

类前必须有abstract关键字,必须使用class修饰。

  • 抽象类不能创建对象
  • 抽象方法只能存在在抽象类或特质中,但是特质和抽象类不一定需要抽象方法
  • 抽象方法不需要使用关键字abstract ,但是抽象类必须使用这个关键字
  • 抽象类中属性不能使用private修饰
  • 抽象类可以继承特质,但是可以不用实现特质中抽象方法和抽象属性,并且可以重写已经实现过的方法
  • 若普通类继承抽象类或特质必须实现其提供抽象属性和抽象方法,并且可以重写已经实现过的方法
  • 重写关键字 override

伴生类和伴生对象

两个类在同一个文件中,两个类的名字必须相同,其中一个类使用class修饰,另外一个类使用object修饰,这样就会被称为伴生类,,这个伴生类指的是object修饰的类,而通过object类创建出来的对象,叫做伴生对象。

伴生类的出现就是为了解决封装问题,scala中伴生类可以访问原有类的私有属性。伴生类和原生类之间是可以互相访问私有属性,普通属性和方法也是完全可以的,但是若原生类使用private[this],伴生类无法访问。

因为伴生类也可以创建对象,所有伴生类中提供一个方法apply,多用于伴生类创建对象。每个伴生类都是默认提供一个apply方法,这个方法就是伴生类创建对象的方法。我们可以定义apply对原生类中提供属性赋值操作(重载)。apply方法构建对象时候就会自动调用。

继承

单继承,主构造方法可以调用父类的主构造方法,默认无参,子类的辅助构造方法不能调用父类的构造方法。

类型检查和转换

isInstanceOf:判断对象是否属于指定类

asInstanceOf:进行类型转换

匹配模式

Scala中没有Java中的switch-case语句,但是Scala中有更强大的匹配模式。

匹配对象(任何数据类型) match{
case 表达式/值/方法/类 => 代码
case 表达式/值/方法/类 => 代码
case _ => 代码
}

下换线当做通配符,通配任意可能。

 //标准模式
 val ch: Char = 'p'
 val chp = 'p'
 ch match {
   case '+' => println("加法")
   case '*' | '%' => println("|表示的是或者关系")
   case chp => println("匹配成功")
   case _ => println("最后一种情况[相当于剩余所有情况]")
   // 允许添加条件守卫
   // case _ if(条件) => 执行代码
 }
 // 匹配其他数据
 // 1.匹配数组(取出了元素值)
 val arr = Array("scala", 1, 2.0, 'a')
 // match-case不仅可以匹配值,还可以匹配数据类型
 val value: Any = arr(Random.nextInt(4))
 value match {
   case x: Int => println("当前是Int类型" + x) // x数据是一个局部变量,只能在match中case内使用
   case s: String => println("当前是String类型" + s)
   case d: Double => println("当前是Double类型" + d)
   case _ => println("字符")
 }
 // 直接匹配类型 --> 相当于匹配是否是当前类型的对象
 value match {
   case Char => println("Char类型的对象") // 匹配value是否是Char类型的对象
   case _ => println("匹配的是数据类型")
 }

 // 匹配Map集合
 val map = Map(("1", 1), ("2", 2))
 map match {
   case m: Map[String, Int] => println("存储着String和Int类型数据")
   case m: Map[_, _] => println("所有Map都可以匹配,相当于是所有类型剩余的情况")
 }
 // 匹配数组
 val arr1 = Array(1, 1)
 val res = arr1 match { // 可以接收匹配成功后的值
   // 匹配数组中存储的元素
   case Array(0) => "0" // 数组中是否存在0这个元素
   case Array(x, y) => (x + y) + "" // 只要数组中存在两个元素,就可以赋值到x和y的身上
   case Array(0, _*) => "0..." // 匹配数组是否是从0开始 -->数组是否是以0开头 _*代表剩余的元素
   case _ => "something"
 }
 println(res)
 // 匹配字符串
 val arr2 = Array("zhangsan","lisi")
 val name = arr2(Random.nextInt(arr2.length))
 name match{
   case "zhangsan" => println("张三") // 值
   case s:String => println(s) // 匹配类型,千万不要把类型放到值的上面,因为只要是String类型都会被匹配走
   case _ => println("所有")
 }
 // 列表List
 val list = List(1,2)
 list match{
   case 0 :: Nil => "0" // 集合中是否是以0开头
   case x :: y :: Nil => x+" "+y// 当前集合时候包含连续元素
   case 0 :: tail => "0.." // 以0开头的元素
   case _ => "剩余情况"
 }
 // 同理可以使用下标取值匹配 --> 参考Array
 // 使用比较高 元组
 val pair = (1,2)
 pair match{
   //case pair._1 => println("") // 相当于是匹配的整个元组即 (x,y) 这样的形式才可以
   case (0,_) => "0..."// 元组中是否存在0 ,并且在第一个元素的位置
   case (x,y) => (x+y) +"x+y" // 匹配元组中元素 x和y都可以进行操作
   case (_,_) => "匹配任何元素 这个下划线的个数决定这元组存储数据的个数"
   case _ => "任意类型"
 }
 // ps:单独匹配元组中的值,那么就现将值取出后在进行匹配
 pair._1 match{
   case x:Int => x
 }

样例类

case class 类名(构造方法) { 写类的实现}

case object 类名 --> 作用只有一个就是做模式匹配

  • apply方法通常被称为注入方法,在类的伴生对象中做一些初始化的操作
  • apply方法的参数列表不需要和原生类中构造方法参数一致
  • unapply方法被称为提取方法,使用uappluy可以提取固定对象或值
  • unapply返回值类型必须是Option类型,通过判断Some或None来决定这个对象是否存在
  • 只要是object类,默认都提供apply 和unapply方法,并且是隐式调用
import scala.util.Random
// 当前样例类对数据进行分装传递
object CaseCalssDemo {
  def main(args: Array[String]): Unit = {
    val arr =
      Array(CheckTimeOutTask,SubmitTask("0001","task_001"),HeatBeat(15000))
    val obj = arr(Random.nextInt(arr.length)) // 获取存储在Array数组中是三个对象(随机获取)
    obj match{
      case CheckTimeOutTask => println("任务超时")
      // 匹配成功存储在SubmitTask对象中属性就会被赋值到id 和task 变量上
      case SubmitTask(id,task) => println("任务ID"+id+"任务名称:"+task)
      case HeatBeat(time) => println("时间:"+time)
      case _ => println("剩余")
    }
  }
}
// 每一个样例类都会有自己的apply,所以直接获取类名的方式就可以创建对象
// 开发会将样例类单独存到一个文件,通过对这个文件的维护,就可以添加和删除或修改对应样例类
case object CheckTimeOutTask
case class SubmitTask(id:String,taskName:String)
case class HeatBeat(time:Long)

偏函数

是一种特殊的匹配模式,不使用match关键字,但是有多组case语句组成,这样的形式就是偏函数。

偏函数有一个名字 PartialFunction[A,B], A代表的是参数类型,B代表的是返回值类型。

def f1:PartialFunction[String,Int]={
  case "one" => 1
  case "two" => 2
  case _ => -1
}
val i = f1("two")

柯里化

方法可以定义多个参数列表,当使用较少的参数列表调用多参数列表的方法时,会产生一个新的函数,该函数接收剩余的参数列表作为其参数。这被称为柯里化。

柯里是个人名,柯里化是指将一个以函数为返回值的函数调用的简化。

val f1 = (s:String)=>{
  println(s)
  (i:Int)=>{
    println(s + i)
  }
}
// 这个时候想调用f1返回值的这个函数就需要这么写
f1("s1")(1)
// 这样就可以获取f1的返回值,是个函数
val f2 = f1("s2")

// 柯里化就是简化这个函数
val f3 = (s:String)=>(i:Int)=>{
  println(s)
  println(s + i)
}
f3("f3")(0)

闭包

函数内部调用一个或多个外部变量,这个变量并不存在函数的内部,这个就是闭包。

同Java中的闭包,都是提升外部变量的声明周期实现闭包。

// 引用外部变量,这就是闭包
val a = 2
val f1 = (i:Int)=>{
    i * a
}

隐式转换

关键字:implicit

在不显示调用某个方法的前提下,就可以完成对数据的其他形式操作,这样方式其实就是隐式转换。

隐式转换一直在用,Scala中隐式转换经常被用到。

当写Scala程序的时候,会自动为类添加三个隐式转换包:

  • import java.lang._

  • import scala._

  • import Predef._

// 定义一个隐式转化类
implicit class RichFile(from:File){
  def read:String = Source.fromFile(from.getPath).mkString
}
  • 定义的隐式类必须带有构造方法且参数只能有一个
  • 隐式类只能定义在class类或object类中
  • 隐式类不能定义在case class中(样例类中)
  • 在定义隐式类的时候需要class前面添加 implicit关键字
  • 若隐式类和使用隐式类的位置再通一个文件中,此时就不需要导入隐式类
  • 若隐式类在一个文件中定义,需要在另外一个文件中使用此隐式类,必须导入当前隐式类的包才可以是也能够

隐式参数

隐式参数在柯里化的时候大量使用,多是系统提供的方法,高阶函数。

trait Adder[T]{
  def add(x:T,y:T):T
}
implicit val a = new Adder[Int] {
  override def add(x: Int, y: Int): Int = x+y
}
def addTest(x:Int,y:Int)(implicit adder: Adder[Int])={
  adder.add(x,y)
}

隐式转换的时机和解析机制

时机

  • 当方法中的参数类型与目标类型不一致的时候
  • 当对象调用所在类中不存在的方法或成员,编译器会自动将对象进行隐式转换

解析

  • 首先会在当前代码作用域下进行查找(隐式方法,隐式类,隐式参数,隐式对象)
  • 如果当前文件下没有隐式转换,会去对当前隐式参数的类型的作用域中进行查找,类型作用域指的是当前类和当前类型伴生类

泛型

[B <: A] 上界或上限

[B >: A] 下界或下限

[B <% A] 视界

[B: A] 上下文界

[-A] 逆变

[+B] 协变

上下文界定

必须存在一个隐私转换

class Pair_Context[T:Ordering](val first:T,val second:T){
    def smaller(implicit ord:Ordering[T])={
        if(ord.compare(first,second) < 0) first else second
    }
}
object Demo{
    def main(args: Array[String]): Unit = {
        val i = new Pair_Context(1,2)
        println(i.smaller)
    }
}

/ 柯里化就是简化这个函数
val f3 = (s:String)=>(i:Int)=>{
println(s)
println(s + i)
}
f3(“f3”)(0)


# 闭包

函数内部调用一个或多个外部变量,这个变量并不存在函数的内部,这个就是闭包。

同Java中的闭包,都是提升外部变量的声明周期实现闭包。

```scala
// 引用外部变量,这就是闭包
val a = 2
val f1 = (i:Int)=>{
    i * a
}

隐式转换

关键字:implicit

在不显示调用某个方法的前提下,就可以完成对数据的其他形式操作,这样方式其实就是隐式转换。

隐式转换一直在用,Scala中隐式转换经常被用到。

当写Scala程序的时候,会自动为类添加三个隐式转换包:

  • import java.lang._

  • import scala._

  • import Predef._

// 定义一个隐式转化类
implicit class RichFile(from:File){
  def read:String = Source.fromFile(from.getPath).mkString
}
  • 定义的隐式类必须带有构造方法且参数只能有一个
  • 隐式类只能定义在class类或object类中
  • 隐式类不能定义在case class中(样例类中)
  • 在定义隐式类的时候需要class前面添加 implicit关键字
  • 若隐式类和使用隐式类的位置再通一个文件中,此时就不需要导入隐式类
  • 若隐式类在一个文件中定义,需要在另外一个文件中使用此隐式类,必须导入当前隐式类的包才可以是也能够

隐式参数

隐式参数在柯里化的时候大量使用,多是系统提供的方法,高阶函数。

trait Adder[T]{
  def add(x:T,y:T):T
}
implicit val a = new Adder[Int] {
  override def add(x: Int, y: Int): Int = x+y
}
def addTest(x:Int,y:Int)(implicit adder: Adder[Int])={
  adder.add(x,y)
}

隐式转换的时机和解析机制

时机

  • 当方法中的参数类型与目标类型不一致的时候
  • 当对象调用所在类中不存在的方法或成员,编译器会自动将对象进行隐式转换

解析

  • 首先会在当前代码作用域下进行查找(隐式方法,隐式类,隐式参数,隐式对象)
  • 如果当前文件下没有隐式转换,会去对当前隐式参数的类型的作用域中进行查找,类型作用域指的是当前类和当前类型伴生类

泛型

[B <: A] 上界或上限

[B >: A] 下界或下限

[B <% A] 视界

[B: A] 上下文界

[-A] 逆变

[+B] 协变

上下文界定

必须存在一个隐私转换

class Pair_Context[T:Ordering](val first:T,val second:T){
    def smaller(implicit ord:Ordering[T])={
        if(ord.compare(first,second) < 0) first else second
    }
}
object Demo{
    def main(args: Array[String]): Unit = {
        val i = new Pair_Context(1,2)
        println(i.smaller)
    }
}

你可能感兴趣的:(Scala基础)