Scala编程语言入门(3)

Scala编程语言入门(3),第1张

Scala编程语言入门(3)

文章目录

Scala编程语言入门

Scala进阶

trait特质

1. 作为接口使用2. 定义具体方法3. 定义具体方法和抽象方法4. 定义具体字段和抽象字段5. 实例对象混入 trait 模式匹配和样例类

1. 匹配字符串2. 匹配类型3. 匹配数组4. 匹配集合5. 匹配元组6. 样例类7. 样例对象8. Option类型9. 偏函数 异常处理

1. 异常场景2. 捕获异常3. 抛出异常 提取器泛型

1. 定义一个泛型方法2. 定义一个泛型类3. 上下界 协变、逆变、非变隐式转换和隐式参数

1. 隐式转换2. 隐式参数3. 案例演示

Scala编程语言入门 Scala进阶 trait特质

特质是scala中代码复用的基础单元

它可以将方法和字段定义封装起来,然后添加到类中

与类继承不一样的是,类继承要求每个类都只能继承一个超类,而一个类可以添加任意数量的特质。

特质的定义和抽象类的定义很像,但它是使用trait关键字

1. 作为接口使用

使用extends来继承trait(scala不论是类还是特质,都是使用extends关键字)如果要继承多个trait,则使用with关键字示例一:继承单个 trait

trait Logger1 {
  // 抽象方法
  def log(msg: String)
}

class ConsoleLogger1 extends Logger1 {
  override def log(msg: String): Unit = println(msg)
}

object LoggerTrait1 {
  def main(args: Array[String]): Unit = {
    val logger = new ConsoleLogger1
    logger.log("控制台日志: 这是一条Log")
  }
}

示例二:继承多个trait

trait Logger2 {
  def log(msg: String)
}

trait MessageSender {
  def send(msg: String)
}

class ConsoleLogger2 extends Logger2 with MessageSender {
  override def log(msg: String): Unit = println(msg)

  override def send(msg: String): Unit = println(s"发送消息:${msg}")
}

object LoggerTrait2 {
  def main(args: Array[String]): Unit = {
    val logger = new ConsoleLogger2
    logger.log("控制台日志: 这是一条Log")
    logger.send("你好!")
  }
}
2. 定义具体方法

和类一样,trait中还可以定义具体的方法。

trait LoggerDetail {
  // 在trait中定义具体方法
  def log(msg: String) = println(msg)
}

class PersonService extends LoggerDetail {
  def add() = log("添加用户")
}

object MethodInTrait {
  def main(args: Array[String]): Unit = {
    val personService = new PersonService
    personService.add()
  }
}
3. 定义具体方法和抽象方法

在trait中,可以混合使用具体方法和抽象方法使用具体方法依赖于抽象方法,而抽象方法可以放到继承trait的子类中实现,这种设计方式也称为模板模式示例:

trait Logger3 {
  // 抽象方法
  def log(msg: String)

  // 具体方法(该方法依赖于抽象方法log)
  def info(msg: String) = log("INFO: " + msg)

  def warn(msg: String) = log("WARN: " + msg)

  def error(msg: String) = log("ERROR: " + msg)
}

class ConsoleLogger3 extends Logger3 {
  override def log(msg: String): Unit = println(msg)
}

object LoggerTrait3 {
  def main(args: Array[String]): Unit = {
    val logger = new ConsoleLogger3

    logger.log("这是一条日志信息")
    logger.info("这是一条普通信息")
    logger.warn("这是一条警告信息")
    logger.error("这是一条错误信息")
  }
}
4. 定义具体字段和抽象字段

在trait中可以定义具体字段和抽象字段,继承trait的子类自动拥有trait中定义的字段,字段直接被添加到子类中示例:

trait LoggerEx {
  // 具体字段
  val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm")
  val INFO = "信息: " + sdf.format(new Date)
  // 抽象字段
  val TYPE: String

  // 抽象方法
  def log(msg: String)
}

class ConsoleLoggerEx extends LoggerEx {
  // 实现抽象字段
  override val TYPE: String = "控制台"

  // 实现抽象方法
  override def log(msg: String): Unit = print(s"$TYPE$INFO $msg")
}

object FieldInTrait {
  def main(args: Array[String]): Unit = {
    val logger = new ConsoleLoggerEx
    logger.log("这是一条消息")
  }
}
5. 实例对象混入 trait

trait还可以混入到实例对象中,给对象实例添加额外的行为。只有混入了trait的对象才具有trait中的方法,其他的类对象不具有trait中的行为。使用with将trait混入到实例对象中示例:

trait LoggerMix {
  def log(msg: String) = println(msg)
}

class UserService

object FixedInClass {
  def main(args: Array[String]): Unit = {
    val service = new UserService with LoggerMix

    service.log("你好")
  }
}
模式匹配和样例类

scala有一个十分强大的模式匹配机制,可以应用到很多场合

switch 语句类型查询快速获取数据 并且scala还提供了样例类,对模式匹配进行了优化,可以快速进行匹配。 1. 匹配字符串

object Case01MatchString extends App {
  val arr = Array("hadoop", "zookeeper", "spark", "storm")

  val name = arr(Random.nextInt(arr.length))
  println(name)

  name match {
    case "hadoop" => println("大数据分布式存储和计算框架")
    case "zookeeper" => println("大数据分布式协调服务框架")
    case "spark" => println("大数据分布式内存计算框架")
    case _ => println("啥也不是")
  }
}
2. 匹配类型
object Case02MatchType extends App {
  val arr = Array("hello", 1, -2.0, Case02MatchType)

  val value = arr(Random.nextInt(arr.length))
  println(value)

  value match {
    case x: Int => println("Int => " + x)
    case y: Double if (y >= 0) => println("Double => " + y)
    case z: String => println("String => " + z)
    case _ => throw new Exception("not match exception")
  }
}
3. 匹配数组
object Case03MatchArray extends App {
  val arr = Array(1, 3, 5)

  arr match {
    case Array(1, x, y) => println(x + "---" + y)
    case Array(1, _*) => println("1...")
    case Array(0) => println("only 0")
    case _ => println("something else")
  }
}
4. 匹配集合
object Case04MatchList extends App {
  val list = List(0, 3, 6)
  list match {
    case 0 :: Nil => println("only 0")
    case 0 :: tail => println("0....")
    case x :: y :: z :: Nil => println(s"x:$x y:$y z:$z")
    case _ => println("something else")
  }
}
5. 匹配元组
object Case05MatchTuple extends App {
  val tuple = (1, 3, 5)
  tuple match {
    case (1, x, y) => println(s"1,$x,$y")
    case (2, x, y) => println(s"$x,$y")
    case _ => println("others")
  }
}
6. 样例类

样例类是一种特殊类,它可以用来快速定义一个用于保存数据的类(类似于Java POJO类),而且它会自动生成apply方法,允许我们快速地创建样例类实例对象。后面在并发编程和spark、flink这些框架也都会经常使用它。语法结构

case class 样例类名(成员变量名1: 类型1, 成员变量名2: 类型2 ...)

示例:

// 定义一个样例类
case class CasePerson(name: String, age: Int)
// 使用var指定成员变量是可变的
case class CaseStudent(var name: String, var age: Int)

object Case06Class {
  def main(args: Array[String]): Unit = {
    // 1. 使用new创建实例
    val zhangsan = new CasePerson("张三", 23)
    println(zhangsan)

    // 2. 使用类名直接创建实例
    val lisi = CasePerson("李四", 34)
    println(lisi)

    // 3. 样例类默认的成员变量都是val的,除非手动指定变量为var类型
//    lisi.age = 22 // 编译错误!age默认为val类型

    val xiaoming = CaseStudent("小明", 13)
    xiaoming.age = 24
    println(xiaoming)
  }
}
7. 样例对象

使用case object可以创建样例对象。样例对象是单例的,而且它没有主构造器。样例对象是可序列化的。格式:

case object 样例对象名

示例:

case class SendMessage(text: String)
//  消息如果没有任何参数,就可以定义为样例对象
case object StartTask
case object PauseTask
case object StopTask

样例类和样例对象结合模式使用

case class SubmitTask(id: String, name: String)

case class HeartBeat(time: Long)

case object CheckTimeOutTask

object Case07Object extends App {
  val arr = Array(CheckTimeOutTask, HeartBeat(10000), SubmitTask("0001", "task-0001"))

  arr(Random.nextInt(arr.length)) match {
    case SubmitTask(id, name) => println(s"id = $id, name = $name")
    case HeartBeat(time) => println(s"time = $time")
    case CheckTimeOutTask => println("检查超时")
  }
}
8. Option类型

在Scala中Option类型用样例类来表示可能存在或也可能不存在的值,Option 类型有 2 个子类:

一个是 Some:包装了某个值一个是 None:表示没有值 源码

final case class Some[+A](x: A) extends Option[A] {
  def isEmpty = false
  def get = x
}
case object None extends Option[Nothing] {
  def isEmpty = true
  def get = throw new NoSuchElementException("None.get")
}

示例:

object Case08Option {
  def main(args: Array[String]): Unit = {
    val map = Map("a" -> 1, "b" -> 2)
    val value: Option[Int] = map.get("b")
    val v1 = value match {
      case Some(i) => i
      case None => 0
    }
    println(v1)
    // 更好的方式
    val v2 = map.getOrElse("c", 0)
    println(v2)
  }
}
9. 偏函数

被包在花括号内没有match的一组case语句是一个偏函数,它是PartialFunction[A, B] 的一个实例

A 代表输入参数类型B 代表返回结果类型可以理解为:偏函数是一个参数和一个返回值的函数。 示例:

object Case09PartialFunction {
  // func1是一个输入参数为Int类型,返回值为String类型的偏函数
  val func1: PartialFunction[Int, String] = {
    case 1 => "一"
    case 2 => "二"
    case 3 => "三"
    case _ => "其它"
  }

  def main(args: Array[String]): Unit = {
    println(func1(1))
    val list = List(1, 2, 3, 4, 5, 6)
    // 使用偏函数 *** 作
    val result = list.filter {
      case x if x > 3 => true
      case _ => false
    }
    println(result)
  }
}
异常处理 1. 异常场景

看下面一段代码

def main(args: Array[String]): Unit = {
  val i = 10 / 0

  println("你好!")
}

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at ForDemo$.main(ForDemo.scala:3)
	at ForDemo.main(ForDemo.scala)

执行程序,可以看到scala抛出了异常,而且没有打印出来"你好"。说明程序出现错误后就终止了。那怎么解决该问题呢? 2. 捕获异常

在scala中,可以使用异常处理来解决这个问题

在Scala里,借用了模式匹配的思想来做异常的匹配,以下为scala中try…catch异常处理的语法格式

try {
    // 代码
}
catch {
    case ex: 异常类型1 => // 代码
    case ex: 异常类型2 => // 代码
}
finally {
    // 代码
}

try中的代码是我们编写的业务处理代码;在catch中表示当出现某个异常时,需要执行的代码;在finally中,是不管是否出现异常都会执行的代码。示例:

object TryCatchException {
  def main(args: Array[String]): Unit = {
    try {
      var i = 10 / 0
    } catch {
      case ex: Exception => println(ex.getMessage)
    } finally {
      println("始终会执行")
    }
  }
}
3. 抛出异常

我们也可以在一个方法中,抛出异常。语法格式和Java类似,使用throw new Exception

object ThrowsException {
  def main(args: Array[String]): Unit = {
    throw new Exception("这是一个异常")
  }
}
提取器

提取器是从传递给它的对象中提取出构造该对象的参数。(回想样例类进行模式匹配提取参数)scala 提取器是一个带有unapply方法的对象。

unapply方法算是apply方法的反向 *** 作unapply接受一个对象,然后从对象中提取值,提取的值通常是用来构造该对象的值。

示例:

object ExtractorTest {
  def main(args: Array[String]): Unit = {
    val zhangsan = Student("张三", 23)
    zhangsan match {
      case Student(name, age) => println(s"姓名: $name 年龄: $age")
      case _ => println("未匹配")
    }
  }
}

class Student {
  var name: String = _
  var age: Int = _

  // 实现一个辅助构造器
  def this(name: String, age: Int) = {
    this()
    this.name = name
    this.age = age
  }
}

object Student {
  def apply(name: String, age: Int): Student = new Student(name, age)

  // 实现一个解构器
  def unapply(arg: Student): Option[(String, Int)] = Some(arg.name, arg.age)
}
泛型

scala和Java一样,类和特质、方法都可以支持泛型。我们在学习集合的时候,一般都会涉及到泛型。在scala中,使用方括号来定义类型参数

scala> val lsit: List[String] = List("1", "2", "3")
lsit: List[String] = List(1, 2, 3)
1. 定义一个泛型方法

不考虑泛型的支持

object GenericMethod {
  // 不考虑泛型的支持
  def getMiddle(arr: Array[Int]) = arr(arr.length / 2)

  def main(args: Array[String]): Unit = {
    val arr1 = Array(1, 2, 3, 4, 5)
    println(getMiddle(arr1))
  }
}

考虑泛型的支持

object GenericMethod {
  // 考虑泛型的支持
  def getMiddle[A](arr: Array[A]) = arr(arr.length / 2)

  def main(args: Array[String]): Unit = {
    val arr1 = Array(1, 2, 3, 4, 5)
    val arr2 = Array("a", "b", "c", "d", "e", "f")

    println(getMiddle[Int](arr1))
    println(getMiddle[String](arr2))

    // 简写
    println(getMiddle(arr1))
    println(getMiddle(arr2))
  }
}
2. 定义一个泛型类

示例:定义一个Pair类包含2个类型不固定的泛型

// 类名后面的方括号,就表示这个类可以使用两个类型、分别是T和S
class Pair[T, S](val first: T, val second: S)

case class People(var name: String, val age: Int)

class GenericClass {
  def main(args: Array[String]): Unit = {
    val p1 = new Pair[String, Int]("张三", 23)
    val p2 = new Pair[String, String]("李四", "1984-01-12")
    val p3 = new Pair[People, People](People("张三", 20), People("李四", 14))
  }
}
3. 上下界

在指定泛型类型时,有时需要界定泛型类型的范围,而不是接收任意类型。比如,要求某个泛型类型,必须是某个类的子类,这样在程序中就可以放心的调用父类的方法,程序才能正常的使用与运行scala的上下边界特性允许泛型类型是某个类的子类,或者是某个类的父类

U >: T —— 这是类型下界的定义,也就是 U 必须是类型 T 的父类或者是 T 类型本身。U <: T —— 这是类型上界的定义,也就是 U 必须是类型 T 的子类或者是 T 类型本身。 示例一:

class Person(var name: String, val age: Int)

// 类名后面的指定泛型的范围 ---- 上界
class Pair1[T <: Person, S <: Person](val first: T, val second: S) {
  def chat(msg: String) = println(s"${first.name}对${second.name}说: $msg")
}

object GenericLimitUp {
  def main(args: Array[String]): Unit = {
    val p = new Pair1[Person, Person](new Person("张三", 33), new Person("李四", 24))
    p.chat("你好")
  }
}

示例二:

class Person(var name: String, val age: Int)

class PoliceMan(name: String, age: Int) extends Person(name, age)

class SuperMan(name: String) extends PoliceMan(name, -1)

class Pair2[T <: Person, S >: PoliceMan <: Person](val first: T, val second: S) {
  def chat(msg: String) = println(s"${first.name}对${second.name}说: $msg")
}

object GenericLimitUp {
  def main(args: Array[String]): Unit = {
    // 编译错误:第二个类型参数必须是Person的子类(包括本身)、Policeman的父类(包括本身)
    val p = new Pair2[Person, SuperMan](new Person("张三", 33), new SuperMan("李四"))
    p.chat("你好")
  }
}
协变、逆变、非变

来一个类型转换的问题

class Pait[T](a: T)
object PAir {
  def main(args: Array[String]): Unit = {
    val p1 = new Pair("hello")
    // 编译报错,无法将 p1 转为 p2
    val p2: Pair[AnyRef] = p1
      
    println(p2)
  }
}

协变:class Pair[+T],这种情况是协变。类型 B 是 A 的子类型,Pair[B] 可以认为是 Pair[A] 的子类型。这种情况,参数化类型的方向和类型的方向是一致的。

逆变:class Pair[-T],这种情况是逆变。类型 B 是 A 的子类型,Pair[A] 反过来可以认为是 Pair[B] 的子类型。这种情况,参数化类型的方向和类型的方向是相反的。

非变:class Pair[T]{},这种情况就是非变(默认),类型 B 是 A 的子类型,Pair[A] 和 Pair[B] 没有任何从属关系,这种情况和 Java 是一样的。


示例:

class Super
class Sub extends Super
// 非变
class Temp1[A](title: String)
// 协变
class Temp2[+A](title: String)
// 逆变
class Temp3[-A](title: String)

object GenericCovariance {
  def main(args: Array[String]): Unit = {
    val a = new Sub()
    // 没有问题,Sub是Supper的子类
    val b: Super = a

    // 非变
    val t1: Temp1[Sub] = new Temp1[Sub]("非变")
//    // 报错:默认不允许转换
//    val t2: Temp1[Super] = t1

    // 协变
    val t3: Temp2[Sub] = new Temp2[Sub]("协变")
    val t4: Temp2[Super] = t3

    // 逆变
    val t5: Temp3[Super] = new Temp3[Super]("逆变")
    val t6: Temp3[Sub] = t5
  }
}

总结:

C[+T]:如果A是B的子类,那么C[A]是C[B]的子类。C[-T]:如果A是B的子类,那么C[B]是C[A]的子类。C[T]: 无论A和B是什么关系,C[A]和C[B]没有从属关系。 隐式转换和隐式参数 1. 隐式转换

Scala提供的隐式转换和隐式参数功能,是非常有特色的功能,是Java等编程语言所没有的功能。它可以允许你手动指定,将某种类型的对象转换成其他类型的对象或者是给一个类增加方法。通过这些功能,可以实现非常强大、特殊的功能。隐式转换其核心就是定义一个使用 implicit 关键字修饰的方法,实现把一个原始类转换成目标类,进而可以调用目标类中的方法。 2. 隐式参数

所谓的隐式参数,指的是在函数或者方法中,定义一个用implicit修饰的参数,此时Scala会尝试找到一个指定类型的用 implicit 修饰的值,即隐式值,并注入参数。

所有的隐式转换和隐式参数必须定义在一个object中 3. 案例演示

案例一:让 File 类具备 RichFile 类中的 read 方法

object MyPredef {
  // 定义一个隐式转换的方法,实现把File转换成RichFile
  implicit def file2RichFile(file: File) = new RichFile(file)
}

class RichFile(val file: File) {
  def read(): String = {
    Source.fromFile(file).mkString
  }
}

object RichFile {
  def main(args: Array[String]): Unit = {
    // 1. 构建一个 File 对象
    val file = new File("1.txt")
    // 2. 手动导入隐式转换
    import MyPredef.file2RichFile

    val data: String = file.read

    println(data)
  }
}

案例二:超人变身

class Man(val name: String)

class SuperMan(val name: String) {
  def heat = print(s"${name}超人打怪兽")
}

object SuperMan {
  // 隐式转换方法
  implicit def man2SuperMan(man: Man) = new SuperMan(man.name)

  def main(args: Array[String]): Unit = {
    val hero = new Man("Tom")
    // Man具备了SuperMan的方法
    hero.heat
  }
}

案例三:一个类隐式转换成具有相同方法的多个类

class C

class A(c: C) {
  def readBook(): Unit = {
    println("A: 好诗好诗..")
  }
}

class B(c: C) {
  def readBook(): Unit = {
    println("B: 看不懂")
  }

  def writeBook(): Unit = {
    println("B: 不会写..")
  }
}

object AB {
  // 创建一个类转换为2个类的隐式转换
  implicit def C2A(c: C) = new A(c)

  implicit def C2B(c: C) = new B(c)
}

object Multi {
  def main(args: Array[String]): Unit = {
    // 导包
    // 1. import AB._ 会将AB类下的所有隐式转换导进来
    // 2. import AB.C2A 只导入C类到A类的的隐式转换方法
    // 3. import AB.C2B 只导入C类到B类的的隐式转换方法
    import AB._

    val c = new C
    // 由于A类与B类中都有readBook(),只能导入其中一个,否则调用共同方法时代码报错
    // c.readBook();

    // C类可以执行B类中的writeBook()
    c.writeBook()
  }
}

案例四:员工领取薪水

object Company {
  // 在object中定义隐式值 注意:同一类型的隐式值只允许出现一次,否则会报错
  implicit val x = "zhangsan"
  implicit val y = 10000.00
}

class Boss {
  // 定义一个用implicit修饰的参数 类型为String
  // 注意参数匹配的类型,它需要的是String类型的隐式值
  def callName(implicit name: String): String = {
    name + " is coming!"
  }

  // 定义一个用implicit修饰的参数,类型为Double
  // 注意参数匹配的类型,它需要的是Double类型的隐式值
  def getMoney(implicit money: Double): String = {
    "当前薪水: " + money
  }
}

object Salary extends App {
  // 使用import导入定义好的隐式值,注意:必须先加载否则会报错
  import Company.{x, y}

  val boss = new Boss
  println(boss.callName + boss.getMoney)
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5713037.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存