Scala:基础知识03

Posted Xiao Miao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scala:基础知识03相关的知识,希望对你有一定的参考价值。

Scala基础知识

一、模式匹配

模式匹配类似于java中switch case 根据的内容进行匹配 匹配到执行结果。

  • java switch case
import java.util.Scanner;

public class Six {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("你的成绩是: \\t");
        char grade = sc.next().charAt(0);
           switch (grade) {
               case 'A':
                   System.out.println("优秀");
                   break;
               case 'B':
               case 'C':
                   System.out.println("良好");
                   break;
               case 'D':
                   System.out.println("及格");
                   break;
               case 'F':
                   System.out.println("你需要再努力努力");
                   break;
               default:
                   System.out.println("输入错误,请重新输入");
           }



    }
}

1.匹配内容

import scala.io.StdIn

object MatchCase01 {
  def main(args: Array[String]): Unit = {
    print("请输入内容:\\t")
    //从控制台 stdin(标准输入)接收用户输入的内容 根据内容进行模式匹配
    val result: String = StdIn.readLine() // 这是个阻塞的方法

    result match {
      case "hadoop" => println("001")
      case "spark" => println("002")
      case "flink" => println("003")
      case _ => println("输入错误") //相当于default
    }
  }
}

2.匹配数据类型

import scala.util.Random

object MatchCase02 {
  def main(args: Array[String]): Unit = {
    //从集合中进行随机取值  根据取值的结果类型进行匹配
    val a = Array(11, 22, "hello", "bye", true, false, 3.14, 6.33)
    //    Random.nextInt() //从0 到(n-1)随机产生一个整数
    val result: Any = a(Random.nextInt(a.length))

    result match {
      case x: Int if x > 11 => println(x) //todo 可以在匹配的时候加上守卫条件 进行过滤
      case y: String => println(y) //todo 不满足守卫条件怎么办? 自动落入default中
      case z: Double => println(z)
      case w: Boolean => println(w)
      case _ => println("匹配异常")
    }
  }
}

3.匹配数组、集合

object MatchCase03 {
  def main(args: Array[String]): Unit = {
    val a1 = Array(1, 2, 3)
    //如果多个条件都满足  从上往下进行匹配 匹配成功 就执行后面的逻辑 不再继续匹配    
    a1 match { 
      case Array(2, x, y) => println(x + " " + y)
      case Array(1, 2, 3) => println("精准匹配")
      case Array(1, _*) => println("1 ...")
      case _ => println("something else")
    }

    val l1 = List(0, 1, 2)
    l1 match {
      case 0 :: Nil => println("只有0的列表") //List(0)
      case 0 :: tail => println("0开头的列表") //List(0,_*)
      case x :: y :: Nil => println(s"只有另两个元素${x}, ${y}的列表") //List(x,y)
      case _ => println("未匹配")
    }
  }
}

4.变量声明时进行模式匹配

object MatchCase04 {
  def main(args: Array[String]): Unit = {
    //todo result是程序处理返回的结果  需要提取当中第一个、第二个元素作为参数  进行后续的计算
    val result: Array[Int] = (1 to 5).toArray

        //val x = result(0)
        //val y = result(1)

    val Array(x,y,_*)  = result

    println(x+y)
  }
}

二、apply和unapply方法

1.apply方法

  • 背景:为什么在创建集合的时候 有初始值不需要new
  • 功能:某种程度上也是一种构造方法,当创建对象的时候 如果不加new底层就会尝试寻找apply方法,如果有,就调用apply方法实现对象的创建
object ApplyCase01 {
  def main(args: Array[String]): Unit = {
    val a1 = new Array[String](4) //没有初始值 需要new

    /**
     * def apply(x: Int, xs: Int*): Array[Int] = {
     *    val array = new Array[Int](xs.length + 1)
     *    array(0) = x
     *    var i = 1
     *    for (x <- xs.iterator) { array(i) = x; i += 1 }
     *    array
     * }
     */

    //todo 底层调用apply方法帮助我们去new
    val a2: Array[Int] = Array(11, 22, 33)  //有初始值 不需要new
  }
}
object ApplyCase02 {
  def main(args: Array[String]): Unit = {
    val miao= new Person("miao")  //因为Person是普通class 创建对象实例的时候必须new
    //todo 如何实现不new 也能创建呢?  需要自己实现apply方法
    val liu: Person =Person("liu")  //底层调用apply方法完成了new

    val s = Student  //为什么这里不需要new  因为它是object 是全局唯一实例 单列
  }
}

//todo apply方法必须定义在类的伴生对象中
object Person{
  //apply方法
  def apply(name: String): Person = new Person(name)
}

class Person(var name:String)

object Student

2.unapply方法

  • 功能:从对象中提取属性 相当于提取器 和apply方法功能正好相反
import scala.util.Random

class Student(var name:String)
case class Teacher(var name:String)
case object Maomao

object Student {
  //apply方法 实现了  属性--->对象实例
  def apply(name: String): Student = new Student(name)

  //unapply方法 实现了   对象实例----->属性
  def unapply(s: Student): Option[String] = {
    Some(s.name)
  }
}
object Case02 {
  def main(args: Array[String]): Unit = {
//    使用样例类、参与模式匹配
val a:Array[Object]=Array(Student("张三"),Teacher("miao"),Teacher("liu"),Maomao)
val result:Object=a(Random.nextInt(a.length))

    result match{
      case Teacher(name)=>println(name)
      case Student(name)=>println(name)
      case Maomao=>println("女神")
      case _=>println("error")
    }
  }

}

三、样例类

样例类是scala中一种特殊的类 使用关键字case修饰 用于封装数据参与模式匹配 传递数据

  • 具体来说 样例类分为两种

    • case class 多例的 需要 构造器参数列表
    • case object 单列的 不需要构造器
  • 探究:普通类和样例类之间有什么区别,加上case关键字之后,底层发生了什么?

    • 样例类自动实现了apply方法不需要new
    • 样例类自动实现了toString方法 之间打印输出属性 而不是地址
    • 样例类自动实现了hashcode和equals方法 对象实例可以直接比较 而不是比较地址

样例类自动实现了copy方法

class Cat(var name:String){

  override def toString = s"Cat($name)"

  def canEqual(other: Any): Boolean = other.isInstanceOf[Cat]

  override def equals(other: Any): Boolean = other match {
    case that: Cat =>
      (that canEqual this) &&
        name == that.name
    case _ => false
  }

  override def hashCode(): Int = {
    val state = Seq(name)
    state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
  }

}

//样例类
case class Dog(var name:String){

}

object Case1 {
  def main(args: Array[String]): Unit = {
    val jf1 = new Cat("jiafei")  //todo  1、普通的类需要new 才能创建实例

    println(jf1)  //todo 2、普通的类直接输出打印 针对是地址 如果需要输出属性 重写对象的toString方法

    val jf2 = new Cat("jiafei")
    println(jf1 == jf2 )  //todo 3、普通对象直接比较 比较的是地址  如果需要指定比较规则 需要重写对象的hashcode和equals方法

    /**
     * todo 为什么需要重写两个方法进行比较?
     *  首先比较hashcode值 如果不一样 肯定不一样 如果一样 有可能不一样  再比较equals方法
     */

    val eh1 = Dog("erha")  //todo A、样例类底层自动实现apply方法 在创建对象实例的时候可以不加new
    println(eh1) //todo B、样例类底层自动实现了toString方法

    val eh2 = Dog("erha")
    println(eh1 == eh2)  //todo C、样例类底层自动实现了hashcode 和equals方法
  }
}

样例类核心功能:封装数据

class Student(var name:String)
case class Teacher(var name:String)
case object MaoMao

//定义Student的伴生对象 里面定义apply方法和unapply方法
object Student{
  //apply方法 实现了  属性--->对象实例
  def apply(name: String): Student = new Student(name)

  //unapply方法 实现了   对象实例----->属性
  def unapply(s: Student): Option[String] = {
    Some(s.name)
  }
}


object Case2 {
  def main(args: Array[String]): Unit = {
    //使用样例类 参与模式匹配
    val a: Array[Object] = Array(Student("张三"), Teacher("miao"), Teacher("liu"), Maomao)
 

    val result: Object = a(Random.nextInt(a.length))

    result match {
      case Teacher(name) => println(name) //可以直接提取对象里面的属性  todo 底层样例类还实现了unapply方法 提取器
      case Student(name) => println(name)
//    case s:Student => println(s.name)  //不行 先匹配对象  然后调用对象.属性
      case Maomao=>println("女神")
      case _ => println("error")
    }

  }
}

option类型

  • "量子叠加类型"表示可有可无两种状态
  • 分为两个子类
    • 有 some
    • 无 none
object OptionsCase01 {
  def main(args: Array[String]): Unit = {
    val m = Map("name" ->"liu","age" ->"18")

    //根据key去map中取值: 有值  none
    val maybeString: Option[String] = m.get("name")

    println(maybeString)
  }
}
object OptionsCase02 {
  def main(args: Array[String]): Unit = {
   def m1(x:Int,y:Int) =x / y
//    println(m1(4,0))

    //如何使用option类型封装程序 让程序更加健壮 鲁棒性
    def m2(x:Int,y:Int): Option[Int] ={
      if( y != 0)  Some(x/y)
      else None
    }

    val maybeInt: Option[Int] = m2(4, 0)

    maybeInt match {
      case Some(s) =>println(s)
      case None => println("除数不能为0")
    }
  }
}

偏函数
partila Funcation 部分的,不完全的

  • 偏函数不是函数,是一种特殊的数学符号现象,可以优化程序代码

  • 偏函数从现象上看:没有match却有case的一组语句

  • 偏函数是PartialFunction[A, B]的一个实例

  • A代表输入参数类型

  • B代表返回结果类型

object PF01 {
  def main(args: Array[String]): Unit = {
    val a1: Array[Int] = Array(11,22,33)
    println(a1.map(_*10).toBuffer)  //在IDEA中 Array对象无法直接打印内容 可以转换成为ArrayBuffer查看

    val a2: Array[Any] = Array(11,22,"USA",33)
//    println(a2.map(_*10).toBuffer)
    println(a2.filter(x => x.isInstanceOf[Int]).map(x => x.asInstanceOf[Int]).map(_ * 10).toBuffer)

    //todo 使用偏函数实现上述逻辑   所谓的偏指的是对整体中部分应用
    val pf1:PartialFunction[Any,Int] ={
      case i:Int => i * 10
    }

    println(a2.collect(pf1).toBuffer) //针对偏函数的调用 使用collect方法 相当于map方法功能  只不过只接收偏函数作为参数
  }
}
object PF02 {
  def main(args: Array[String]): Unit = {
    //用户输入1,2,3 返回一 二 三

    //todo 实现1:使用普通的方法+模式匹配
    def m1(i:Int) = i match {
      case 1 => println("一")
      case 2 => println("二")
      case 3 => println("三")
    }
    m1(2)

    //todo 实现2:使用偏函数实现
    val pf2:PartialFunction[Int,String] ={
      case 1 => "一"
      case 2 => "二"
      case 3 => "三"
    }

    println(pf2(1))
  }
}

正则类

  • 又叫做规则表达式 用于字符串内容的匹配
  • RegEx类
    • 直接new
    • String.r —>RegEx类
import scala.util.matching.Regex

object  RegexTest {
  def main(args: Array[String]): Unit = {
    val email1 = "miao@qq.com"
    val email2 = "liu@com"
    var regex:Regex=""".+@.+\\..+""".r
    println(regex.findAllMatchIn(email1).toList)
  }

}

scala异常处理

  • 捕获异常 try catch
  • 抛异常throws
object ExceptionCase01 {
  def main(args: Array[String]): Unit = {

    //todo IDEA快捷键 ctrl +alt +t

    try {
      //捕获的业务代码 有可能出错的地方
      val z = 4 / 0
    } catch {
      //处理异常地方
//    case e:Exception => println(e.getMessage)
      case e1:ArithmeticException => println("数值异常"+e1.getMessage)
      case e2:NullPointerException => println("空指针异常")
    } finally {
      //不管是否出错 一定会执行的
      println("一定执行")
    }

  }
}

四、Actor并发编程

Java并发编程

  • 模型:基于多线程+共享数据
    • 线程越多,并发能力越强
    • 不容易维护,容易死锁

scala中并发编程

  • 基于时间+消息发送和接受模型
    • actor越多并发能力越强
    • 不共享数据
import scala.actors.Actor

class Actor03 extends Actor {
  override def act(): Unit = {
    //    todo 在actor内部 使用receive方法进行消息接收 配合模式匹配处理消息
    //    如何实现不间断处理消息 使用while true 包装receive方法
    while (true) {
      receive {
        case "hello" => println("欢迎光临")
        case "bye" => println("下次再见")
        case "stop" => System.exit(0)
      }
    }
  }
}

object Actor03 {
  def main(args: Array[String]): Unit = {
    val actor03 = new Actor03()
    actor03.start()
    //    演示 自己给自己发消息
    actor03 ! "hello"
    actor03 ! "bye"
    actor03 ! "stop"
  }
}

使用react接收处理消息

  • 使用while+receive效率不高 不能复用线程
  • 针对react方法 使用loop控制外层循环
import scala.actors.Actor

class Actor04 extends Actor {
  override def act(): Unit = {
    loop {
      react {
        case "hello" => println("欢迎光临")
        case "bye" => println("下次再见")
        case "stop" => System.exit(0)
      }
    }
  }
}

object Actor04 {
  def main(args: Array[String]): Unit = {
    val actor04 = new Actor04()
    actor04.start()
    //    演示 自己给自己发消息
    actor04 ! "hello"
    actor04 ! "bye"
    actor04 ! "stop"
  }
}

Actor的三种消息发送方式

import scala.actors.{Actor, Future}

case class AsyncMessage(id: Int, msg: String)

case class SyncMessage(id: Int, msg: String)

case class Reply(msg: String)

class Actor05 extends Actor {
  override def act(): Unit = {
    loop {
      react {
        case AsyncMessage(id, msg) => {
          println(s"id:${id}-->msg:${msg}")
          //          todo 返回消息
          sender ! Reply("已收到,并回复消息01!")
        }
        case SyncMessage(id, msg) => {
          println(s"id:${id}-->msg:${msg}")
        }
      }
    }
  }
}

//! 异步消息,没有返回值
//!! 异步信息,有返回值
//!? 同步消息 等待返回值
object Actor05 {
  def main(args: Array[String]): Unit = {
    val actor05 = new Actor05()
    actor05.start()
    //todo 1、异步消息 没有返回值  !
    actor05 ! AsyncMessage(1, "这是一条异步消息,不需要返回值!")
	//todo 3、同步消息 等待返回值  !? 阻塞动作
    actor05 !? SyncMessage(3, "这是同步阻塞消息!")
	//todo 2、异步消息 需要返回值  !!  Future[Any]
    //针对异步消息的返回值 其立即受到的一个未来的状态 叫做future 至于最终的返回结果 在对方处理完之后 会把最终的结果放在future中
    //可以从future中提取最终的结果
    val future: Future[Any] = actor05 !! AsyncMessage(2, "这是一条异步消息需要返回值")
    val result = future.apply().asInstanceOf[Reply]
    println(result)
  }
}

Actor并发版本–Wordcount

  • 初始版本
import scala.actors.Actor
import scala.io.Source

//定义个样例类 用于封装提交的任务
case class SubmitTask(filePath: String)

class Task extends Actor {
  override def act(): Unit = {
    loop {
      react {
        //接收的task任务 处理数据 进行wc
        case SubmitTask(filePath) => {
          //1、从文件中读取数据 并且读取的数据转换为String  底层是全文读
          val content: String = Source.fromFile(filePath).mkString
          //2、将读取的内容根据换行符切割成为一行行内容  \\r\\n
          val lines: Array[String] = content.split("\\r\\n")
          //3、根据分隔符进行单词切割 并扁平化操作
          val words: Array[String] = lines.flatMap(line => line.split(" "))
          //4、将单词标记1   单词----> (单词,1) 对偶元组,二元组
          val wordAndOne: Array[(String, Int)] = words.map(word => (word, 1))
          //5、将单词相同的分为一组
          val wordGrouped: Map[String, Array[(String, Int)]] = wordAndOne.groupBy(tuple2 => tuple2._1)
          //6、todo scala集合中有个方法mapValues 只处理map当做value 处理的结果和之前的key组成一个新的map
          val result: Map[String, Int] = wordGrouped.mapValues(v => v.length)
          for ((k, v) <- result) println(s"${k}---->${v}")
        }
      }
    }
  }
}

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

    //1、待处理数据路径
    val filePath = "D:\\\\Study\\\\idea\\\\Scala\\\\files\\\\1.txt"

    //2、创建启动TaskActor
    val task = new Task
    task.start()

    //3、把待处理的数据路径(任务) 发送给task处理
    task ! SubmitTask(filePath)
  }
}

  • 最终版本
import scala.actors.{Actor, Future}
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.io.Source

//定义个样例类 用于封装提交的任务
case class SubmitTask(filePath:String)
//定义样例类 用于封装task处理的结果
case class ReplyResult(result: Map[String, Int])


class Task extends Actor {
  override def act(): Unit ={
    loop{
      react{
        //接收的task任务 处理数据 进行wc
        case SubmitTask(filePath) =>{
          //1、从文件中读取数据 并且读取的数据转换为String  底层是全文读
          val content: String = Source.fromFile(filePath).mkString
          //2、将读取的内容根据换行符切割成为一行行内容  \\r\\n
          val lines: Array[String] = content.split("\\r\\n")
          //3、根据分隔符进行单词切割 并扁平化操作
          val words: Array[String] = lines.flatMap(line => line.split(" "))
          //4、将单词标记1   单词----> (单词,1) 对偶元组,二元组
          val wordAndOne: Array[(String, Int)] = words.map(word => (word, 1))
          //5、将单词相同的分为一组
          val wordGrouped: Map[String, Array[(String, Int)]] = wordAndOne.groupBy(tuple2 => tuple2._1)
          //6、todo scala集合中有个方法mapValues 只处理map当做value 处理的结果和之前的key组成一个新的map
          val result: Map[String, Int] = wordGrouped.mapValues(v => v.length)
          // for( (k,v)<- result) println(s"${k}---->${v}")

          //todo task处理完的结果进行返回
          sender !  ReplyResult(result)
        }
      }
    }
  }
}

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

    //创建一个集合 用于保存每个任务Future
    val futureSet = new mutable.HashSet[Future[Any]]()
    //创建一个集合 用于保存最终的每个task处理结果
    val resultList = new ListBuffer[ReplyResult]

    //1、待处理数据路径
    val files = Array("D:\\\\Study\\\\idea\\\\Scala\\\\files\\\\1.txt","D:\\\\Study\\\\idea\\\\Scala\\\\files\\\\2.txt","D:\\\\Study\\\\idea\\\\Scala\\\\files\\\\3.txt")
    //val filePath =D:\\\\Study\\\\idea\\\\Scala\\\\files\\\\1.txt"
    //遍历待处理文件文件 分别启动task处理各个文件
    for(f <- files){
      //2、创建启动TaskActor
      val task = new Task
      task.start()
      //3、把待处理的数据路径(任务) 发送给task处理
      //因为需要对最终的结果进行合并 异步有返回值的消息
      val future: Future[Any] = task !! SubmitTask(f)
      //把future保存至集合中
      futureSet += future
    }

    //判断future是否已经完成有结果 如果有结果 提取结果      future.isSet可以判断结果是否有了
    while(futureSet.nonEmpty){
      //过滤出已经有结果的future
      val completed: mutable.HashSet[Future[Any]] = futureSet.filter(f => f.isSet)
      //遍历已经完成的 提取结果
      for(c <- completed){
        //提取结果
        val result: ReplyResult = c.apply().asInstanceOf[ReplyResult]
        //将结果添加至resultList
        resultList += result
        //todo 把提取完结果的future从futureSet给剔除
        futureSet.remove(c)
      }
    }

    println(resultList.flatMap(r => r.result)
      .groupBy(_._1)
      .mapValues(values => values.map(x =>x._2).sum))
  }
}

五、泛型及其延伸问题

泛型

  • 参数化类型 泛型程序设计(generic programming)

  • scala中泛型使用 泛型通常大小字母表示 理论上26个字母都可以 常见的T A B

泛型方法 格式:

def 方法名[泛型名称](..) = {
//...
}
object TestGen {
  def main(args: Array[String]): Unit = {
    val a1 = new Array[String](4) //T -->String
    val a2 = new Array[Int](2) //T  --->Int

    def m1(a: Array[Int]) = a(a.length / 2)

    //    println(m1(Array(11,22,33)))
    //    println(m1(Array("allen","tom","jerry")))

    //定一个同样功能方法 使用泛型定义

    def m2[A](a: Array[A]) = a(a.length / 2)

    println(m2(Array(11, 22, 33)))
    println(m2(Array("allen", "tom", "jerry")))
    println(m2(Array(3.14, 6.88, 7.52)))
  }
}

  • 泛型类
object TestGen1 {
  def main(args: Array[String]): Unit = {
    val jiafei: Cat1 = Cat1("jiafei")

    val tom: Cat2[String] = Cat2("tom")
    val jerryMao: Cat2[Int] = Cat2(12)
  }
}

case class Cat1(var name:String)

case class Cat2[T](var name:T)
  • 上下界 限制泛型类型的边界
[T <: 类型]    T <=类型   上界 T类型必须是后面指定的类型及其子类  

[T >: 类型]    T >=类型    下界 T类型必须是后面值的类型及其父类

如果类既有上界、又有下界。下界写在前面,上界写在后面

泛型延伸问题

  • 非变 斜变 逆变
  • java支持非变
  • scala三个都支持
class Super
class Sub extends Super

class Temp1[T]  //非变 
class Temp2[+T] //保持一致 继续延伸
class Temp3[-T] //逆转过来 延伸

def main(args: Array[String]): Unit = {
	val a:Temp1[Sub] = new Temp1[Sub]
	// 编译报错
	// 非变
	//val b:Temp1[Super] = a
    
	// 协变
	val c: Temp2[Sub] = new Temp2[Sub]
	val d: Temp2[Super] = c
	
    // 逆变
	val e: Temp3[Super] = new Temp3[Super]
	val f: Temp3[Sub] = e
}

以上是关于Scala:基础知识03的主要内容,如果未能解决你的问题,请参考以下文章

Scala基础学习03

scala基础篇-03 if与for

Scala - 03 - 基础语法

Scala语言专题

为什么Scala是可扩展的?

初学scala4——trait混入