• 快速介绍Scala特性


    class Upper1:
      def convert(strings: Seq[String]): Seq[String] =
        strings.map((s: String) => s.toUpperCase)
    
    val up = new Upper1()
    val uppers = up.convert(List("Hello", "World!"))
    println(uppers)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 使用 class 关键字声明一个类 Upper1, 后跟冒号, 整个类体在后面的行上缩进, Upper1 包含了一个名为 convert 的方法
    • 方法定义以 def 关键字开始, 然后是方法名称可选的参数列表, 方法签名以可选的返回类型结束, 等号将方法签名和方法体分隔开
      • 如果省略冒号和返回类型, Scala 会推断返回类型
      • 如果方法不接受参数, 也可以省略圆括号
    • 等号提醒我们函数式编程原则: 变量和函数是统一处理的。函数可以像值一样作为参数传递给其他函数, 也可以从函数中返回, 并赋值给变量
    • convert 方法接收一个由零个或多个输入字符串组成的序列(Seq)并返回一个新的序列, 其中每个输入字符串都被转换为大写
    • Seq 是可以迭代的集合的抽象, convert 方法返回的实际类型将与作为参数传递给它的具体类型相同, 比如 Vector 或者 List (两者都是不可变集合)
      • 像 Seq[T] 这样的集合类型是参数化类型, T 是序列中元素的类型
      • List[T] 是一个 immutable linked list, 访问List的头部是O(1),而访问位置N的任意元素是O(N)
      • Vector[T] 是 Seq[T] 的子类型,几乎所有访问模式都是O(1)。
    • 在方法体中, 使用了大多数集合可用的一种强大方法 map, 它遍历集合, 对每个元素调用所提供的方法, 并返回带有转换后元素的新集合
    • 传递给 map 方法的是一个未命名的函数字面量 (parameters) => body
    (s: String) => s.toUpperCase
    
    • 1
    • 它接受一个名为 s 的字符串作为参数, 函数体位于 => 之后, 对 s 调用 toUpperCase 方法
    • 在Scala中,函数、方法或其他代码块中的最后一个表达式是返回值
    • 最后两行使用 new Upper1() 创建 Upper1 的实例,命名为 up, 并使用它将两个字符串转换为大写,最后打印结果
    • 通常,println 需要一个字符串参数,但如果你传递给它一个对象,比如 Seq 时,将调用 toString 方法。
    package progscala3.introscala                 // <1>声明包的位置
    
    object UpperMain1:
      def main(params: Array[String]): Unit =     // <2>在对象内部声明一个main方法,一个程序入口点。
        print("UpperMain1.main: ")
        params.map(s => s.toUpperCase).foreach(s => printf("%s ",s))
        println("")
    
    def main(params: Array[String]): Unit =       // <3>将另一个主入口点声明为顶级方法,位于任何对象之外,但作用域限于当前包
      print("main: ")
      params.map(s => s.toUpperCase).foreach(s => printf("%s ",s))
      println("")
    
    @main def Hello(params: String*): Unit =      // <4>声明一个入口点方法,我们可以在其中使用不同的名称,并且对参数列表有更灵活的选项
      print("Hello: ")
      params.map(s => s.toUpperCase).foreach(s => printf("%s ",s))
      println("")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • Scala中没有static关键字, 相反, 使用 object 关键字声明一个对象 UpperMain1 然后使用与在类中相同的语法声明 main 和其他成员, 将 UpperMain1 声明为一个 object 将使它成为一个单例, 它永远只有一个实例
    • 这个文件有三个入口点
      • 第一个: UpperMain1. main, 是 Scala2 中声明入口点的方式
      • 第二个: Scala3 新特性, 可以在 object 和 class 外部声明方法和变量
      • 第三个: Scala3 新特性, 使用 @main 注释将方法标记为一个入口点, 可以在object 内外部声明
        • 减少了定义时的样板文件, 可以定义参数列表
        • String* 表示0到多个字符串(重复参数)
    s => s.toUpperCase
    
    • 1
    • 前面的例子使用了 (s: String) => s.toUpperCase, 大多数时候, Scala 可以推断函数字面量的参数类型, 因为 map 提供的上下文告诉编译器需要什么类型, 所以类型声明 String 可以省略
    • 当希望处理每个元素并只执行副作用而不返回新值时就会使用 foreach 方法, 这里将一个字符串打印输出, 相反, map 方法为每个元素返回一个新值(应该避免产生副作用)
      • 副作用意味着传递给 foreach 的函数会影响局部上下文之外的状态, 比如写入数据库或文件, 打印到控制台
    package progscala3.introscala
    
    @main def Hello2(params: String*): Unit =
      val output = params.map(_.toUpperCase).mkString(" ")
      println(output)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 将字符串序列映射到一个新的字符串序列后, 调用 mkString 方法, 将字符串连接成一个最终的字符串
    (s: String) => s.toUpperCase
    s => s.toUpperCase
    _.toUpperCase
    
    • 1
    • 2
    • 3
    • 这三个函数字面量的本质是相同的
    • 单次引用一个或多个参数, 可以使用 _ 作为占位符代替一个参数, 而不为参数提供名称
    package progscala3.introscala.shapes
    //1 为二维点声明一个类。因为没有定义成员,所以省略了类签名末尾的冒号(:)
    case class Point(x: Double = 0.0, y: Double = 0.0)
    
    //2 为几何形状声明一个抽象类。它需要一个冒号,因为它定义了一个方法 draw
    abstract class Shape():
      /**
       * Draw the shape.
       * @param f is a function to which the shape will pass a string version of itself to be rendered.
       */
      //3 实现了draw方法
      def draw(f: String => Unit): Unit = f(s"draw: $this")
    
    //4 一个具有中心和半径的圆, 它是 Shape 的子类
    case class Circle(center: Point, radius: Double) extends Shape
    
    //5 一个具有左下角点、高度和宽度的矩形, 它是 Shape 的子类
    case class Rectangle(lowerLeft: Point, height: Double, width: Double) extends Shape
    
    //6 一个由三点确定的三角形
    case class Triangle(point1: Point, point2: Point, point3: Point) extends Shape
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • Point 类名后面的参数列表是构造函数参数列表
    • case 关键字修饰的类有特殊处理
      • 构造函数参数自动转换为只读字段(val)
      • 会自动生成 toString, equals, hashCode 方法
      • 编译器会为每个 case 类生成一个伴生对象(同名的单例对象), 我们声明了 case class Point, 编译器就创建了 object Point
        • 当一个 object 和一个 class 有相同的名称并且同一个文件中定义时, 它们是伴生的
        • 编译器还会自动向伴生对象中添加几个方法, 其中一个方法名为apply, 它接受与构造函数相同的参数列表
      • 可以不使用 new 关键字构造实例 (实际上是调用了伴生对象的 apply 方法)
    object Point:
      def apply(x: Double = 0.0, y: Double = 0.0) = new Point(x, y)
    
    val p1 = Point.apply(1.0, 2.0)
    val p2 = Point(1.0, 2.0) // Same!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 显式声明 object Point 就可以向伴生对象添加方法, 包括重载的 apply 方法, 默认的apply方法仍然会生成
    • 可以在任何 class 中定义 apply 方法

    当参数列表放在 objectinstance 之后, Scala 会寻找一个 apply 方法调用

    • 继续代码例子, Shape 是一个抽象类, Shape. draw 方法被定义
    • draw 方法的参数 f 是 String => Unit 类型的函数
      • 当一个函数返回 Unit 时它是完全的副作用
      • 通常在 FP 中更喜欢没有副作用的纯函数, 只在必要的地方使用副作用, 保持其余代码的纯粹性
    • f 方法由 draw 方法调用, 它使用插值字符串构造最终的字符串
    package progscala3.introscala.shapes
    
    //1 声明了名为 Message 的 trait,trait 类似于 abstract base class 抽象基类
    //所有交换的信息都是 Message 的子类
    sealed trait Message
    //2 绘制形状的信息
    case class Draw(shape: Shape) extends Message
    //3 对前一条信息做出响应的信息
    case class Response(message: String) extends Message
    //4 一个终止的信息
    case object Exit extends Message
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • sealed 关键字意味着只能在同一个文件中定义 Message 的子类型
    • sealed hierarchies 密封的层次结构
    package progscala3.introscala.shapes
    
    object ProcessMessages:                                              
      def apply(message: Message): Message =                             
        message match                                                    
          case Exit =>
            println(s"ProcessMessage: exiting...")
            Exit
          case Draw(shape) =>
            shape.draw(str => println(s"ProcessMessage: $str"))
            Response(s"ProcessMessage: $shape drawn")
          case Response(unexpected) =>
            val response = Response(s"ERROR: Unexpected Response: $unexpected")
            println(s"ProcessMessage: $response")
            response
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • apply 方法引入了一个很强大的特性 pattern matching 模式匹配
      • match 表达式的工作原理很像 if/else 表达式, 但更加强大和简洁,
      • 当其中一个模式匹配时, 计算箭头(=>)后的表达式块, 直到下一个 case 或结尾
      • 如果 case 子句没有涵盖可以传递给 match 表达式的所有可能值, 则会在运行时抛出 MatchError
      • pattern matching 的一个强大特性是从匹配的对象中提取数据的能力, 称为 deconstruction 解构

    最后运行这个例子

    @main def ProcessShapesDriver =                                      
      val messages = Seq(                                                
        Draw(Circle(Point(0.0,0.0), 1.0)),
        Draw(Rectangle(Point(0.0,0.0), 2, 5)),
        Response(s"Say hello to pi: 3.14159"),
        Draw(Triangle(Point(0.0,0.0), Point(2.0,0.0), Point(1.0,2.0))),
        Exit)
    
      messages.foreach { message =>                                      
        val response = ProcessMessages(message)
        println(response)
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    确保你知道每一行是如何处理的

  • 相关阅读:
    手绘板的制作——重置与橡皮擦(2)
    AspNetCore&云效Flow持续集成
    LM358运放电路参数设计-运算放大器-单位增益带宽及反馈并联电容
    会议动态 | 浙江省水泥行业高质量发展暨碳达峰推进会成功召开
    SRE视角下的DevOps构建之道
    讲解用Python处理Excel表格
    Elasticsearch:使用你的 RAG 来进行聊天
    【linux基础(1)】
    239.滑动窗口的最大值
    Yapi 1.10.3迁移踩坑记
  • 原文地址:https://blog.csdn.net/qq271003351/article/details/125470651