栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

快速介绍Scala特性

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

快速介绍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)
  • 使用 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
  • 它接受一个名为 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("")
  • Scala中没有static关键字, 相反, 使用 object 关键字声明一个对象 UpperMain1 然后使用与在类中相同的语法声明 main 和其他成员, 将 UpperMain1 声明为一个 object 将使它成为一个单例, 它永远只有一个实例
  • 这个文件有三个入口点
    • 第一个: UpperMain1. main, 是 Scala2 中声明入口点的方式
    • 第二个: Scala3 新特性, 可以在 object 和 class 外部声明方法和变量
    • 第三个: Scala3 新特性, 使用 @main 注释将方法标记为一个入口点, 可以在object 内外部声明
      • 减少了定义时的样板文件, 可以定义参数列表
      • String* 表示0到多个字符串(重复参数)
s => s.toUpperCase
  • 前面的例子使用了 (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)
  • 将字符串序列映射到一个新的字符串序列后, 调用 mkString 方法, 将字符串连接成一个最终的字符串
(s: String) => s.toUpperCase
s => s.toUpperCase
_.toUpperCase
  • 这三个函数字面量的本质是相同的
  • 单次引用一个或多个参数, 可以使用 _ 作为占位符代替一个参数, 而不为参数提供名称
package progscala3.introscala.shapes
//1 为二维点声明一个类。因为没有定义成员,所以省略了类签名末尾的冒号(:)
case class Point(x: Double = 0.0, y: Double = 0.0)

//2 为几何形状声明一个抽象类。它需要一个冒号,因为它定义了一个方法 draw
abstract class Shape():
  
  //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
  • 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!
  • 显式声明 object Point 就可以向伴生对象添加方法, 包括重载的 apply 方法, 默认的apply方法仍然会生成
  • 可以在任何 class 中定义 apply 方法

当参数列表放在 object 或 instance 之后, 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
  • 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
  • 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)
  }

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/995322.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号