乐趣区

关于后端:Scala初学者指南

本文已收录至 Github,举荐浏览 👉 Java 随想录

微信公众号:Java 随想录

因为之后工作会接触到 Spark 离线工作相干的内容,所以必定会接触到 Scala,谈到大数据技术栈,Scala 语言根本绕不开。像 Spark。Flink 和 Kafka 等,源码中都能看到 Scala 的影子。本文旨在对 Scala 语法进行扫盲,让初学者可能看懂 Scala 代码,目标就达到了,至于底层原理不会开展叙述。

先分享 Scala 官网的网站:https://docs.scala-lang.org/

大部分的学习材料都能够在这找到,语言能够切换为中文,十分敌对。

另外咱们能够应用 Scastie 在浏览器上间接运行 Scala 代码进行调试:https://scastie.scala-lang.org/

Scala 跟 Java 的区别和分割

Scala 语言和 Java 语言有许多相似之处,但也有一些显著的区别。Scala 语言来源于 Java,它以 Java 的虚拟机(JVM)为运行环境,Scala 源码 (.scala)会编译成.class 文件。这意味着 Scala 程序能够与 Java 程序互操作,并且能够利用 JVM 的优化和性能

在语法上,Scala 和 Java 也有一些区别。例如,在 Scala 中,所有皆为对象,而在 Java 中,根本类型、null、静态方法等不是对象。在 Scala 中,成员变量 / 属性必须显示初始化,而在 Java 中能够不初始化。此外,在 Scala 中,异样解决采纳 Try-catch {case-case}-finally 的形式,而在 Java 中采纳 Try-catch-catch-finally 的形式。

Scala 还有一些特有的概念,例如:惰性函数、伴生对象、特质、偏函数 等。这些概念都为 Scala 语言提供了更多的灵活性和表达能力。使得 Scala 语言非常适合用来开发大数据处理框架。此外,Scala 语言的语法糖也十分甜,能够用更少的代码量来实现雷同的性能。

Scala 装置

Scala 装置很简略。

  1. 首先 Idea 装置 Scala 插件。

  2. 我的项目构造里点击全局库,增加 Scala SDK 进行下载。

  3. 右键点击增加到你要应用 Scala 的我的项目的我的项目库,我的项目的库里就会多出 Scala 的 SDK。

到这就完结了,而后咱们就能够在我的项目里应用 Scala 了。

新建一个 Scala 我的项目,运行 Hello Wrold 试一下。

Scala 中的数据类型

Scala 中的数据类型能够分为两大类:值类型(AnyVal)和援用类型(AnyRef)。这两种类型都是 Any 类型的子类。

值类型包含 9 种根本数据类型,别离是 ByteShortIntLongFloatDoubleCharBooleanUnit。其中,前 8 种类型与 Java 中的根本数据类型绝对应,而 Unit 类型示意无值,相似于 Java 中的 void

援用类型包含所有非值类型的数据类型,例如字符串、数组、列表等。它们都是 AnyRef 类型的子类。

在 Scala 的数据类型层级构造的底部,还有两个非凡的数据类型:NothingNull。其中,Nothing 类型是所有类型的子类型,它没有任何实例。而 Null 类型是所有援用类型的子类型,它只有一个实例:null

Scala 语法

主办法是一个程序的入口点。JVM 要求一个名为 main 的主办法,承受一个字符串数组的参数。你能够如下所示来定义一个主办法。

object Main {def main(args: Array[String]): Unit =
    println("Hello, Scala developer!")
}

在 Scala 2 中,也能够通过创立一个扩大 App 类的对象来定义主程序。例如:

object Main extends App {println("Hello, Scala developer!")
}

须要留神的是,这种办法在 Scala 3 中不再举荐应用。它们被新的 @main 办法取代了,这是在 Scala 3 中生成能够从命令行调用的程序的举荐办法。App目前仍以无限的模式存在,但它不反对命令行参数,未来会被弃用。

val 和 var

在 Scala 中,valvar 都能够用来定义变量,但它们之间有一些重要的区别。

val 用于定义不可变变量,也就是说,一旦定义了一个 val 变量,它的值就不能再被扭转。例如:

val x = 1
// x = 2 // 这会报错,因为不能给 val 变量从新赋值

var 用于定义可变变量,它的值能够在定义后被扭转。例如:

var y = 1
y = 2 // 这是非法的,因为 y 是一个 var 变量

val 和 var 的类型能够被推断,或者你也能够显式地申明类型,例如:

val x: Int = 1 + 1
var x: Int = 1 + 1

在理论编程中,咱们应该尽量应用 val 来定义不可变变量,这样能够进步代码的可读性和可维护性。只有在的确须要扭转变量值的状况下,才应该应用 var 来定义可变变量。

泛型

在 Scala 中,应用方括号 [] 来定义泛型类型。而在 Java 中是应用<>

object Main extends App {
  trait Animal {def speak: String}

  class Dog extends Animal {def speak = "Woof!"}

  class Cat extends Animal {def speak = "Meow!"}

  class Parrot extends Animal {def speak = "Squawk!"}

  class AnimalShelter[A <: Animal] {private var animals: List[A] = Nil

    def addAnimal(animal: A): Unit = {animals = animal :: animals}

    def getAnimal: A = {
      val animal = animals.head
      animals = animals.tail
      animal
    }
  }

  val dogShelter = new AnimalShelter[Dog]
  dogShelter.addAnimal(new Dog)
  val dog: Dog = dogShelter.getAnimal
  println(dog.speak)

  val catShelter = new AnimalShelter[Cat]
  catShelter.addAnimal(new Cat)
  val cat: Cat = catShelter.getAnimal
  println(cat.speak)

  val parrotShelter = new AnimalShelter[Parrot]
  parrotShelter.addAnimal(new Parrot)
  val parrot: Parrot = parrotShelter.getAnimal
  println(parrot.speak)
}

输入:Woof!
Meow!
Squawk!

这个示例中,咱们定义了一个 Animal 特质和三个实现了该特质的类:DogCatParrot。而后咱们定义了一个 AnimalShelter 类,它应用了泛型类型参数 A,并且限度了 A 必须是 Animal 的子类型。这样咱们就能够创立不同类型的动物收容所,比方 dogSheltercatShelterparrotShelter,并且在增加和获取动物时保障类型平安。

包导入

import 语句用于导入其余包中的成员(类,特质,函数等)。应用雷同包的成员不须要 import 语句。导入语句能够有选择性:

import users._  // 导入包 users 中的所有成员
import users.User  // 导入类 User
import users.{User, UserPreferences}  // 仅导入抉择的成员
import users.{UserPreferences => UPrefs}  // 导入类并且设置别名

Scala 不同于 Java 的一点是 Scala 能够在任何中央应用导入:

def sqrtplus1(x: Int) = {
  import scala.math.sqrt
  sqrt(x) + 1.0
}

如果存在命名抵触并且你须要从我的项目的根目录导入,请在包名称前加上 _root_

package accounts

import _root_.users._

留神:包 scalajava.lang 以及 object Predef 是默认导入的。

包对象

在 Scala 中,包对象(Package Object)是一种非凡的对象,它与包同名,并且能够在包中定义一些公共的成员和办法,供包中的其余类和对象间接应用。包对象能够解决在包级别共享常量、类型别名、隐式转换等问题。在 Scala 中,能够应用 package 关键字定义一个包对象。包对象的文件名必须为 package.scala,并与包名统一。

上面是对于包对象的解释和示例代码:

// File: com/example/myapp/package.scala

package com.example

package object myapp {
  val appName: String = "MyApp"

  def printAppName(): Unit = {println(appName)
  }
}

在上述示例中,定义了一个包对象 myapp,位于包 com.example 下。在包对象中,咱们定义了一个名为 appName 的常量和一个名为 printAppName 的办法。

这样,咱们就能够在包中的其余类和对象中间接应用 appNameprintAppName,而无需导入或限定符。

上面是一个应用包对象的示例代码:

package com.example.myapp

object Main {def main(args: Array[String]): Unit = {println(myapp.appName)  // 间接拜访包对象中的常量
    myapp.printAppName()    // 间接调用包对象中的办法}
}

在上述示例中,咱们在 Main 对象中间接拜访了包对象 myapp 中的常量 appName 和办法 printAppName。因为包对象与包同名且位于同一包中,因而能够间接应用它们。

特质

在 Scala 中,类是单继承的,然而特质(trait)能够多继承。这意味着,一个类只能继承一个父类,但能够继承多个特质。这样,从后果上看,就实现了多重继承。

上面是一个例子:

trait A {def printA() = println("A")
}

trait B {def printB() = println("B")
}

class C extends A with B

object Main extends App {
  val c = new C
  c.printA()
  c.printB()}

输入:A
B

例子中,定义了两个特质 AB,它们别离有一个办法 printAprintB。而后咱们定义了一个类 C,它继承了特质 AB。这样,类 C 就能够应用特质 AB 中定义的办法了。

特质也能够有默认的实现。

trait Greeter {def greet(name: String): Unit =
    println("Hello," + name + "!")
}

你能够应用 extends 关键字来继承特质,应用 override 关键字来笼罩默认的实现。

class DefaultGreeter extends Greeter

class CustomizableGreeter(prefix: String, postfix: String) extends Greeter {override def greet(name: String): Unit = {println(prefix + name + postfix)
  }
}

val greeter = new DefaultGreeter()
greeter.greet("Scala developer") // Hello, Scala developer!

val customGreeter = new CustomizableGreeter("How are you,", "?")
customGreeter.greet("Scala developer") // How are you, Scala developer?

但凡须要特质的中央,都能够由该特质的子类型来替换。

import scala.collection.mutable.ArrayBuffer

trait Pet {val name: String}

class Cat(val name: String) extends Pet
class Dog(val name: String) extends Pet

val dog = new Dog("Harry")
val cat = new Cat("Sally")

val animals = ArrayBuffer.empty[Pet]
animals.append(dog)
animals.append(cat)
animals.foreach(pet => println(pet.name))  // Prints Harry Sally

在这里 trait Pet 有一个形象字段 namename 由 Cat 和 Dog 的构造函数中实现。最初一行,咱们能调用 pet.name 的前提是它必须在特质 Pet 的子类型中失去了实现。

运算符

在 Scala 中,运算符是用于执行特定操作的符号或标记。Scala 具备丰盛的运算符,并且容许用户自定义运算符,以及在自定义类中应用运算符。上面是对于定义和应用运算符的解释和示例代码:

在 Scala 中,能够应用 def 关键字定义自定义运算符。自定义运算符能够是任何由字母、数字或下划线组成的标识符,以及一些特殊字符,例如 +-* 等。要定义一个运算符,能够在办法名后面加上一个操作符,而后在办法体中实现相应的逻辑。

上面是一个示例代码:

class Vector2D(val x: Double, val y: Double) {def +(other: Vector2D): Vector2D = {new Vector2D(x + other.x, y + other.y)
  }
}

val v1 = new Vector2D(1.0, 2.0)
val v2 = new Vector2D(3.0, 4.0)
val sum = v1 + v2

println(sum.x) // 输入:4.0
println(sum.y) // 输入:6.0

在上述示例中,定义了一个 Vector2D 类,示意二维向量。咱们通过 val 关键字定义了 xy 作为向量的坐标。

而后,咱们定义了一个自定义运算符 +,它承受另一个 Vector2D 对象作为参数,并返回一个新的 Vector2D 对象。在办法体内,咱们实现了向量的加法操作。

在主程序中,咱们创立了两个 Vector2D 对象 v1v2。而后,咱们应用自定义的运算符 + 来执行向量的加法,并将后果赋值给 sum

最初,咱们打印出 sumxy 坐标,验证加法操作的后果。

咱们能够像应用内置运算符一样应用自定义运算符。它们能够用于相应类型的实例上,并依照定义的逻辑执行操作。

上面是一个示例代码:

val num1 = 10
val num2 = 5

val sum = num1 + num2
val difference = num1 - num2
val product = num1 * num2

println(sum)        // 输入:15
println(difference)  // 输入:5
println(product)     // 输入:50

在上述示例中,咱们定义了两个整数变量 num1num2。而后,咱们应用内置的运算符 +-* 来执行加法、减法和乘法操作,并将后果别离赋值给 sumdifferenceproduct

传名参数

传名参数(Call-by-Name Parameters)是一种非凡的参数传递形式,它容许咱们将表达式作为参数传递给函数,并在须要时进行求值。传名参数应用 => 符号来定义,以示意传递的是一个表达式而不是具体的值。上面是对于传名参数的解释和示例代码:

传名参数的特点是,在每次应用参数时都会从新求值表达式,而不是在调用函数时进行求值。这样能够提早表达式的求值,只在须要时才进行计算。传名参数通常用于须要提早计算、惰性求值或者须要按需执行的场景。

上面是一个示例代码:

def callByName(param: => Int): Unit = {println("Inside callByName")
  println("Param 1:" + param)
  println("Param 2:" + param)
}

def randomNumber(): Int = {println("Generating random number")
  scala.util.Random.nextInt(100)
}

callByName(randomNumber())

在上述示例中,定义了一个名为 callByName 的函数,它承受一个传名参数 param。在函数体内,咱们打印出两次参数的值。

另外,定义了一个名为 randomNumber 的函数,它用于生成随机数。在该函数外部,咱们打印出生成随机数的音讯,并应用 scala.util.Random.nextInt 办法生成一个介于 0 到 100 之间的随机数。

在主程序中,咱们调用 callByName 函数,并将 randomNumber() 作为传名参数传递进去。

当程序执行时,会先打印出 “Inside callByName” 的音讯,而后两次调用 param,即 randomNumber()。在每次调用时,都会从新生成一个新的随机数,并打印出相应的值。

这阐明传名参数在每次应用时都会从新求值表达式,而不是在调用函数时进行求值。这样能够实现按需执行和提早计算的成果。

implicit

implicit 关键字用于定义隐式转换和隐式参数。它能够用来简化代码,让编译器主动执行一些操作。

上面是一些应用 implicit 关键字的示例:

  • 隐式转换:能够应用 implicit 关键字定义隐式转换函数,让编译器主动将一种类型的值转换为另一种类型的值。
implicit def intToString(x: Int): String = x.toString

val x: String = 1
println(x) // 输入 "1"

在这个例子中,定义了一个隐式转换函数 intToString,它承受一个 Int 类型的参数,并返回它的字符串示意。因为这个函数被定义为 implicit,因而编译器会在须要时主动调用它。

在主程序中,咱们将一个 Int 类型的值赋值给一个 String 类型的变量。因为类型不匹配,编译器会尝试寻找一个隐式转换函数来将 Int 类型的值转换为 String 类型的值。在这个例子中,编译器找到了咱们定义的 intToString 函数,并主动调用它将 1 转换为 "1"

  • 隐式参数:能够应用 implicit 关键字定义隐式参数,让编译器主动为办法提供参数值。
implicit val x: Int = 1

def foo(implicit x: Int): Unit = println(x)

foo // 输入 1

在这个例子中,定义了一个隐式值 x 并赋值为 1。而后咱们定义了一个办法 foo,它承受一个隐式参数 x

在主程序中,咱们调用了办法 foo,但没有显式地传入参数。因为办法 foo 承受一个隐式参数,因而编译器会尝试寻找一个隐式值来作为参数传入。在这个例子中,编译器找到了咱们定义的隐式值 x 并将其作为参数传入办法 foo

Object 和 Class

在 Scala 中,classobject 都能够用来定义类型,但它们之间有一些重要的区别。class 定义了一个类,它能够被实例化。每次应用 new 关键字创立一个类的实例时,都会创立一个新的对象。

class MyClass(x: Int) {def printX(): Unit = println(x)
}

val a = new MyClass(1)
val b = new MyClass(2)
a.printX() // 输入 1
b.printX() // 输入 2

结构器能够通过提供一个默认值来领有可选参数:

class Point(var x: Int = 0, var y: Int = 0)

val origin = new Point  // x and y are both set to 0
val point1 = new Point(1)
println(point1.x)  // prints 1

在这个版本的 Point 类中,xy 领有默认值 0 所以没有必传参数。然而,因为结构器是从左往右读取参数,所以如果仅仅要传个 y 的值,你须要带名传参。

class Point(var x: Int = 0, var y: Int = 0)
val point2 = new Point(y=2)
println(point2.y)  // prints 2

object 定义了一个单例对象。它不能被实例化,也不须要应用 new 关键字创立。在程序中,一个 object 只有一个实例。此外,object 中定义的成员都是动态的,这意味着它们能够在不创立实例的状况下间接拜访。而 class 中定义的成员只能在创立实例后拜访。

object MyObject {
  val x = 1
  def printX(): Unit = println(x)
}

MyObject.printX() // 输入 1

另外,在 Scala 中,如果一个 object 的名称与一个 class 的名称雷同,那么这个 object 被称为这个 class 的伴生对象。伴生对象和类能够互相拜访彼此的公有成员:

class MyClass(x: Int) {
  private val secret = 42
  def printCompanionSecret(): Unit = println(MyClass.companionSecret)
}

object MyClass {
  private val companionSecret = 24
  def printSecret(c: MyClass): Unit = println(c.secret)
}

val a = new MyClass(1)
a.printCompanionSecret() // 输入 24
MyClass.printSecret(a) // 输入 42

在这个例子中,定义了一个类 MyClass 和它的伴生对象 MyClass。类 MyClass 中定义了一个公有成员变量 secret 和一个办法 printCompanionSecret,用于打印伴生对象中的公有成员变量 companionSecret。而伴生对象 MyClass 中定义了一个公有成员变量 companionSecret 和一个办法 printSecret,用于打印类 MyClass 的实例中的公有成员变量 secret

在主程序中,创立了一个类 MyClass 的实例 a,并调用了它的 printCompanionSecret 办法。而后咱们调用了伴生对象 MyClassprintSecret 办法,并将实例 a 作为参数传入。

这就是 Scala 中类和伴生对象之间相互拜访公有成员的根本用法。

样例类

样例类(case class)是一种非凡的类,罕用于形容不可变的值对象 (Value Object)。它们非常适合用于不可变的数据。定义一个样例类非常简单,只需在类定义前加上case 关键字即可。例如,上面是一个简略的样例类定义:

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

创立样例类的实例时,不须要应用 new 关键字,间接应用类名即可。例如,上面是一个创立样例类实例并批改其成员变量的示例:

object Test01 {case class Person(var name: String, var age: Int)

  def main(args: Array[String]): Unit = {val z = Person("张三", 20)
    z.age = 23
    println(s"z = $z")
  }
}

_(下划线)

在 Scala 中,下划线 _ 是一个非凡的符号,它能够用在许多不同的中央,具备不同的含意。

  • 作为通配符:下划线能够用作通配符,示意匹配任意值。例如,在模式匹配中,能够应用下划线来示意匹配任意值。
x match {
  case 1 => "one"
  case 2 => "two"
  case _ => "other"
}
  • 作为疏忽符:下划线也能够用来疏忽不须要的值。例如,在解构赋值时,能够应用下划线来疏忽不须要的值。
val (x, _, z) = (1, 2, 3)
  • 作为函数参数占位符:下划线还能够用作函数参数的占位符,示意一个匿名函数的参数。例如,在调用高阶函数时,能够应用下划线来简化匿名函数的定义。
val list = List(1, 2, 3)
list.map(_ * 2)
  • 将办法转换为函数:在办法名称后加一个下划线,会将其转化为偏利用函数(partially applied function),就能间接赋值了。
def add(x: Int, y: Int) = x + y
val f = add _

这只是下划线在 Scala 中的一些常见用法。因为下划线在不同的上下文中具备不同的含意,因而在应用时须要依据具体情况进行判断。

println

println 函数用于向规范输入打印一行文本。它能够承受多种不同类型的参数,并将它们转换为字符串进行输入。

上面是一些常见的应用 println 函数进行输入的形式:

  • 输入字符串:间接将字符串作为参数传入 println 函数,它会将字符串原样输入。
println("Hello, world!")
  • 输入变量:将变量作为参数传入 println 函数,它会将变量的值转换为字符串并输入。
val x = 1
println(x)
  • 输入表达式:将表达式作为参数传入 println 函数,它会计算表达式的值并将其转换为字符串输入。
val x = 1
val y = 2
println(x + y)
  • 应用字符串插值:能够应用字符串插值来格式化输入。在字符串前加上 s 前缀,而后在字符串中应用 ${expression} 的模式来插入表达式的值。
val name = "Alice"
val age = 18
println(s"My name is $name and I am $age years old.")

这些是 println 函数的一些常见用法。你能够依据须要应用不同的形式来格式化输入。

汇合

在 Scala 中,汇合有三大类:序列 Seq、集 Set、映射 Map,所有的汇合都扩大自 Iterable,所以 Scala 中的汇合都能够应用 foreach 办法。在 Scala 中汇合有可变(mutable)和不可变(immutable)两种类型。

List

如咱们能够应用如下形式定义一个 List,其余汇合类型的定义形式也差不多。

object Main {def main(args: Array[String]): Unit = {
    // 定义一个空的字符串列表
    var emptyList: List[String] = List()
    // 定义一个具备数据的列表
    var intList = List(1, 2, 3, 4, 5, 6)
    // 定义空列表
    var emptyList2 = Nil
    // 应用:: 运算符连贯元素
    var numList = 1 :: (2 :: (3 :: Nil))
    println(emptyList)
    println(intList)
    println(emptyList2)
    println(numList)
  }
}

输入:List()
List(1, 2, 3, 4, 5, 6)
List()
List(1, 2, 3)

上面是一些 List 的罕用办法:

val list = List(1, 2, 3, 4)

// 获取列表的长度
val length = list.length

// 获取列表的第一个元素
val first = list.head

// 获取列表的最初一个元素
val last = list.last

// 获取列表除第一个元素外残余的元素
val tail = list.tail

// 获取列表除最初一个元素外残余的元素
val init = list.init

// 反转列表
val reversed = list.reverse

// 在列表头部增加元素
val newList1 = 0 +: list

// 在列表尾部增加元素
val newList2 = list :+ 5

// 连贯两个列表
val list1 = List(1, 2)
val list2 = List(3, 4)
val concatenatedList = list1 ++ list2

// 查看列表是否为空
val isEmpty = list.isEmpty

// 查看列表是否蕴含某个元素
val containsElement = list.contains(1)

// 过滤列表中的元素
val filteredList = list.filter(_ > 2)

// 映射列表中的元素
val mappedList = list.map(_ * 2)

// 折叠列表中的元素(从左到右)val sum1 = list.foldLeft(0)(_ + _)

// 折叠列表中的元素(从右到左)val sum2 = list.foldRight(0)(_ + _)

// 拉链操作
val names = List("Alice", "Bob", "Charlie")
val ages = List(25, 32, 29)
val zipped = names.zip(ages) // List(("Alice", 25), ("Bob", 32), ("Charlie", 29))

// 拉链操作后解压缩
val (unzippedNames, unzippedAges) = zipped.unzip // (List("Alice", "Bob", "Charlie"), List(25, 32, 29))

更多办法不再赘述,网上很容易查阅到相干文章。

Map

object Main {def main(args: Array[String]): Unit = {
    // 定义一个空的映射
    val emptyMap = Map()
    // 定义一个具备数据的映射
    val intMap = Map("key1" -> 1, "key2" -> 2)
    // 应用元组定义一个映射
    val tupleMap = Map(("key1", 1), ("key2", 2))
    println(emptyMap)
    println(intMap)
    println(tupleMap)
  }
}

输入:Map()
Map(key1 -> 1, key2 -> 2)
Map(key1 -> 1, key2 -> 2)

上面是 map 罕用的一些办法:

val map = Map("key1" -> 1, "key2" -> 2)

// 获取映射的大小
val size = map.size

// 获取映射中的所有键
val keys = map.keys

// 获取映射中的所有值
val values = map.values

// 查看映射是否为空
val isEmpty = map.isEmpty

// 查看映射是否蕴含某个键
val containsKey = map.contains("key1")

// 获取映射中某个键对应的值
val value = map("key1")

// 获取映射中某个键对应的值,如果不存在则返回默认值
val valueOrDefault = map.getOrElse("key3", 0)

// 过滤映射中的元素
val filteredMap = map.filter {case (k, v) => v > 1 }

// 映射映射中的元素
val mappedMap = map.map {case (k, v) => (k, v * 2) }

// 遍历映射中的元素
map.foreach {case (k, v) => println(s"key: $k, value: $v") }

这里的 case 关键字起到匹配的作用。

Range

Range属于序列(Seq)这一类汇合的子集。它示意一个整数序列,能够用来遍历一个整数区间内的所有整数。例如,1 to 5示意一个从 1 到 5 的整数序列,包含 1 和 5。

Range 常见于 for 循环中,如下可定义一个 Range:

// 定义一个从 1 到 5 的整数序列,包含 1 和 5
val range1 = 1 to 5

// 定义一个从 1 到 5 的整数序列,包含 1 但不包含 5
val range2 = 1 until 5

// 定义一个从 1 到 10 的整数序列,步长为 2
val range3 = 1 to 10 by 2

// 定义一个从 10 到 1 的整数序列,步长为 -1
val range4 = 10 to 1 by -1

如果咱们想把 Range 转为 List,咱们能够这样做:

val range = 1 to 5
val list = range.toList

Range继承自 Seq,因而它领有Seq 的所有罕用办法,例如 lengthheadlasttailinitreverseisEmptycontainsfiltermapfoldLeftfoldRight等。它还领有一些非凡的办法,例如:

val range = 1 to 10 by 2

// 获取序列的起始值
val start = range.start

// 获取序列的完结值
val end = range.end

// 获取序列的步长
val step = range.step

// 获取一个包含完结值的新序列
val inclusiveRange = range.inclusive

迭代器

迭代器(Iterator)是一种用于遍历汇合中元素的工具。它提供了一种办法来拜访汇合中的元素,而不须要裸露汇合的内部结构。在 Scala 中,你能够应用 iterator 办法来获取一个汇合的迭代器。

object Main {def main(args: Array[String]): Unit = {val list = List(1, 2, 3)
    val iterator = list.iterator

    // 1. 应用 hasNext 办法来查看迭代器中是否还有元素
    val hasMoreElements = iterator.hasNext
    println(s"Has more elements: $hasMoreElements")

    // 2. 应用 next 办法来获取迭代器中的下一个元素
    val nextElement = iterator.next()
    println(s"Next element: $nextElement")

    // 留神:下面的代码曾经将迭代器挪动到了第二个元素,因而上面的代码将从第二个元素开始执行

    // 3. 应用 size 办法来获取迭代器中元素的个数
    val size = iterator.size
    println(s"Size: $size")

    val size1 = iterator.size
    println(s"Size1: $size1")

    // 留神:下面的代码曾经将迭代器挪动到了开端,因而上面的代码将不再无效

    // 4. 应用 contains 办法来查看迭代器中是否蕴含某个元素
    val containsElement = iterator.contains(2)
    println(s"Contains element: $containsElement")
  }
}

输入:Has more elements: true
Next element: 1
Size: 2
Size1: 0
Contains element: false

特地留神:迭代器是一次性的,所以在应用结束后就不能再次应用。因而,在下面的代码中,咱们在调用 next 办法后就不能再应用其余办法来拜访迭代器中的元素了。所以 size1 输入为 0

Tuple

Tuple 从汇合中抽出来讲述是因为 Tuple 不属于汇合。它是一种用来将多个值组合在一起的数据结构。一个 Tuple 能够蕴含不同类型的元素,每个元素都有一个固定的地位。Scala 中的元组蕴含一系列类:Tuple2,Tuple3 等,直到 Tuple22。

示例如下:

object Main {def main(args: Array[String]): Unit = {
    // 定义一个蕴含两个元素的 Tuple
    val tuple1 = (1, "hello")
    println(tuple1)

    // 定义一个蕴含三个元素的 Tuple
    val tuple2 = (1, "hello", true)
    println(tuple2)

    // 定义一个蕴含多个不同类型元素的 Tuple
    val tuple3 = (1, "hello", true, 3.14)
    println(tuple3)

    // 拜访 Tuple 中的元素
    val firstElement = tuple3._1
    val secondElement = tuple3._2
    println(s"first element: $firstElement, second element: $secondElement")
  }
}

输入:(1,hello)
(1,hello,true)
(1,hello,true,3.14)
first element: 1, second element: hello

上面是一些 Tuple 的罕用办法:

object Main {def main(args: Array[String]): Unit = {val tuple = (1, "hello")
    // 替换二元组的元素
    // 输入:(hello,1)
    val swapped = tuple.swap

    // 应用 copy 办法来创立一个新的 Tuple,其中某些元素被替换为新值
    // 输入:(1,world)
    val newTuple = tuple.copy(_2 = "world")

    // 遍历元素
    // 输入:1 hello
    tuple.productIterator.foreach(println)

    // 转换为字符串
    // 输入:(1,hello)
    val stringRepresentation = tuple.toString

    // 应用 Tuple.productArity 办法来获取 Tuple 中元素的个数
    // 输入:2
    val arity = tuple.productArity

    // 应用 Tuple.productElement 办法来拜访 Tuple 中的元素
    // 输入:1
    val firstElement = tuple.productElement(0)
  }
}

提取器对象

提取器对象是一个蕴含有 unapply 办法的单例对象。apply 办法就像一个结构器,承受参数而后创立一个实例对象,反之 unapply 办法承受一个实例对象而后返回最后创立它所用的参数。提取器罕用在模式匹配和偏函数中。

上面是一个应用提取器对象(Extractor Object)的 Scala 代码示例:

object Email {def apply(user: String, domain: String): String = s"$user@$domain"
  
  def unapply(email: String): Option[(String, String)] = {val parts = email.split("@")
    if (parts.length == 2) Some(parts(0), parts(1))
    else None
  }
}

// 测试
val address = "john.doe@example.com"
address match {case Email(user, domain) => println(s"User: $user, Domain: $domain")
  case _ => println("Invalid email address")
}

在上述示例中,定义了一个名为 Email 的提取器对象。提取器对象具备两个办法:applyunapply

apply办法接管用户名和域名作为参数,并返回一个残缺的电子邮件地址。在这个示例中,咱们简略地将用户名和域名拼接成电子邮件地址的字符串。

unapply办法接管一个电子邮件地址作为参数,并返回一个 Option 类型的元组。在这个示例中,咱们应用 split 办法将电子邮件地址宰割为用户名和域名两局部,并通过 Some 将它们封装到一个 Option 中返回。如果宰割后的局部不是两局部,即电子邮件地址不合乎预期的格局,咱们返回None

在测试局部,咱们创立了一个电子邮件地址字符串 address。而后,咱们应用match 表达式将 address 与提取器对象 Email 进行匹配。如果匹配胜利,咱们提取出用户名和域名,并打印出对应的信息。如果匹配失败,即电子邮件地址有效,咱们打印出相应的错误信息。

流程判断

while 和 if

object Main {def main(args: Array[String]): Unit = {println("----while----")
    var i = 0
    while (i < 5) {println(i)
      i += 1
    }

    println("----if----")
    val x = 3
    if (x > 0) {println("x 大于 0")
    } else {println("x 小于 0")
    }
  }
}

输入:----while----
0
1
2
3
4
----if----
x 大于 0 

Scala 中的 while 和 if 跟 Java 中的办法简直没有区别。

for

object Main {def main(args: Array[String]): Unit = {println("----for 循环 ----")
    for (i <- 1 to 5) {println(i)
    }
  }
}

输入:----for 循环 ----
1
2
3
4
5

for 循环跟 Java 稍微有点区别。其中 i <- 1 to 5 是 Scala 中 for 循环的一种常见模式。它示意遍历一个序列,序列中的元素顺次为 1、2、3、4、5。

多重 for 循环简写

Scala 中对于多重 for 循环能够进行简写,例如咱们要用 Java 写多重 for 循环是上面这样:

public class Main {public static void main(String[] args) {
    // 多重 for 循环
    for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {System.out.println(i + " " + j);
      }
    }
  }
}

而用 Scala 咱们能够间接简写为上面这样:

object Main {def main(args: Array[String]): Unit = {
    // 多重 for 循环
    for (i <- 0 until 3; j <- 0 until 3) {println(i + " " + j)
    }
  }
}

输入:0 0
0 1
0 2
1 0
1 1
1 2
2 0
2 1
2 2

能够看出 scala 的 for 循环语法更加的精简。代码行数更少。

yield

在 for 循环的过程中咱们能够应用 yield 来对 for 循环的元素进行 操作收集:

object Main {def main(args: Array[String]): Unit = {val numbers = for (i <- 1 to 5) yield i * 2
    println(numbers)
  }
}

输入:Vector(2, 4, 6, 8, 10)

模式匹配(pattern matching)

在 Scala 语言中,没有 switchcase关键字。相同,咱们能够应用模式匹配(pattern matching)来实现相似于 switch 语句的性能。它是 Java 中的 switch 语句的升级版,同样能够用于代替一系列的 if/else 语句。上面是一个简略的例子,它展现了如何应用模式匹配来实现相似于 switch 语句的性能:

object Main {def main(args: Array[String]): Unit = {def matchTest(x: Any): String = x match {
      case 1 => "one"
      case "two" => "two"
      case y: Int => "scala.Int"
      case _ => "many"
    }

    println(matchTest(1))
    println(matchTest("two"))
    println(matchTest(3))
    println(matchTest("test"))
  }
}

输入:one
two
scala.Int
many

在下面的例子中,定义了一个名为 matchTest 的函数,它承受一个类型为 Any 的参数 x。在函数体中,咱们应用了一个模式匹配表达式来匹配参数x 的值。

在模式匹配表达式中,咱们定义了四个 case 子句。第一个 case 子句匹配值为 1 的状况;第二个 case 子句匹配值为 ”two” 的状况;第三个 case 子句匹配类型为 Int 的状况;最初一个 case 子句匹配所有其余状况。

样例类(case classes)的匹配

样例类非常适合用于模式匹配。

abstract class Notification

case class Email(sender: String, title: String, body: String) extends Notification

case class SMS(caller: String, message: String) extends Notification

def showNotification(notification: Notification): String = {
  notification match {case Email(sender, title, _) =>
      s"You got an email from $sender with title: $title"
    case SMS(number, message) =>
      s"You got an SMS from $number! Message: $message"
  }
}

val someSms = SMS("12345", "Are you there?")
val someEmail = Email("John Doe", "Meeting", "Are we still meeting tomorrow?")

println(showNotification(someSms))
println(showNotification(someEmail))

这段代码定义了一个抽象类 Notification,以及两个扩大自 Notification 的样例类 EmailSMS。而后定义了一个函数 showNotification,它承受一个 Notification 类型的参数,并应用模式匹配来查看传入的告诉是 Email 还是 SMS,并相应地生成一条音讯。

最初,咱们创立了两个实例:一个 SMS 和一个 Email,并应用 showNotification 函数来显示它们的音讯。

模式守卫(Pattern guards)

为了让匹配更加具体,能够应用模式守卫,也就是在模式前面加上if <boolean expression>

def checkNumberType(number: Int): String = number match {
  case n if n > 0 && n % 2 == 0 => "Positive even number"
  case n if n > 0 && n % 2 != 0 => "Positive odd number"
  case n if n < 0 && n % 2 == 0 => "Negative even number"
  case n if n < 0 && n % 2 != 0 => "Negative odd number"
  case _ => "Zero"
}

// 测试
println(checkNumberType(10))    // 输入: Positive even number
println(checkNumberType(15))    // 输入: Positive odd number
println(checkNumberType(-4))    // 输入: Negative even number
println(checkNumberType(-9))    // 输入: Negative odd number
println(checkNumberType(0))     // 输入: Zero

在上述示例中,咱们定义了一个名为 checkNumberType 的办法,它接管一个整数参数 number 并返回一个形容数字类型的字符串。

通过应用模式守卫,咱们能够对 number 进行多个条件的匹配,并依据条件来返回相应的后果。在每个 case 语句中,咱们应用模式守卫来进一步过滤匹配的数字。

例如,case n if n > 0 && n % 2 == 0 示意当 number 大于 0 且为偶数时执行该分支。相似地,其余的 case 语句也应用了模式守卫来进行更准确的匹配。

在测试局部,咱们调用了 checkNumberType 办法并传入不同的整数进行测试。依据不同的输出,办法将返回相应的字符串形容数字类型。

仅匹配类型

当不同类型对象须要调用不同办法时,仅匹配类型的模式十分有用

def processValue(value: Any): String = value match {
  case str: String => s"Received a String: $str"
  case num: Int => s"Received an Int: $num"
  case lst: List[_] => s"Received a List: $lst"
  case _: Double => "Received a Double"
  case _ => "Unknown value"
}

// 测试
println(processValue("Hello"))                // 输入: Received a String: Hello
println(processValue(10))                     // 输入: Received an Int: 10
println(processValue(List(1, 2, 3)))           // 输入: Received a List: List(1, 2, 3)
println(processValue(3.14))                    // 输入: Received a Double
println(processValue(true))                    // 输入: Unknown value

在上述示例中,定义了一个名为 processValue 的办法,它接管一个任意类型的参数value,并返回一个形容值类型的字符串。

通过应用类型模式匹配,咱们能够依据不同的值类型来执行相应的逻辑。在每个 case 语句中,咱们应用类型模式匹配来匹配特定类型的值。

例如,case str: String 示意当 value 的类型为 String 时执行该分支,并将其绑定到变量 str。相似地,其余的 case 语句也应用了类型模式匹配来匹配不同的值类型。

在测试局部,咱们调用了 processValue 办法并传入不同类型的值进行测试。依据值的类型,办法将返回相应的形容字符串。

Scala 的模式匹配是我感觉十分实用和灵便的一个性能,比 Java 的 switch 语句更加弱小和灵便。Scala 的模式匹配能够匹配不同类型的值,包含数字、字符串、列表、元组等。而 Java 的 switch 语句只能匹配整数、枚举和字符串类型的值

密封类

特质(trait)和类(class)能够用 sealed 标记为密封的,这意味着其所有子类都必须与之定义在雷同文件中,从而保障所有子类型都是已知的。密封类限度了可扩大的子类类型,并在模式匹配中确保所有可能的类型都被解决,进步了代码的安全性和可靠性。

上面是一个应用密封类(sealed class)和模式匹配的 Scala 代码示例:

sealed abstract class Shape

case class Circle(radius: Double) extends Shape
case class Rectangle(width: Double, height: Double) extends Shape
case class Square(side: Double) extends Shape

def calculateArea(shape: Shape): Double = shape match {case Circle(radius) => math.Pi * radius * radius
  case Rectangle(width, height) => width * height
  case Square(side) => side * side
}

// 测试
val circle = Circle(5.0)
val rectangle = Rectangle(3.0, 4.0)
val square = Square(2.5)

println(s"Area of circle: ${calculateArea(circle)}")         // 输入: Area of circle: 78.53981633974483
println(s"Area of rectangle: ${calculateArea(rectangle)}")   // 输入: Area of rectangle: 12.0
println(s"Area of square: ${calculateArea(square)}")         // 输入: Area of square: 6.25

在上述示例中,咱们定义了一个密封类 Shape,它是一个抽象类,不能间接实例化。而后,咱们通过扩大Shape 类创立了 CircleRectangleSquare这三个子类。

calculateArea 办法中,咱们应用模式匹配对传入的 shape 进行匹配,并依据不同的 Shape 子类执行相应的逻辑。在每个 case 语句中,咱们依据具体的形态类型提取相应的属性,并计算出面积。

在测试局部,咱们创立了一个 Circle 对象、一个 Rectangle 对象和一个 Square 对象,并别离调用 calculateArea 办法计算它们的面积。

嵌套办法

当在 Scala 中定义一个办法时,咱们能够抉择将其嵌套在另一个办法外部。这样的嵌套办法只在内部办法的作用域内可见,而对于内部办法以外的代码是不可见的。这能够帮忙咱们组织和封装代码,进步代码的可读性和可维护性。

def calculateDiscountedPrice(originalPrice: Double, discountPercentage: Double): Double = {def applyDiscount(price: Double, discount: Double): Double = {val discountedPrice = price - (price * discount)
    discountedPrice
  }

  def validateDiscount(discount: Double): Double = {
    val maxDiscount = 0.8 // 最大折扣为 80%
    if (discount > maxDiscount) {maxDiscount} else {discount}
  }

  val validatedDiscount = validateDiscount(discountPercentage)
  val finalPrice = applyDiscount(originalPrice, validatedDiscount)
  finalPrice
}

// 调用内部办法
val price = calculateDiscountedPrice(100.0, 0.9)
println(s"The final price is: $price")

在上述示例中,定义了一个内部办法 calculateDiscountedPrice,它接管原始价格originalPrice 和折扣百分比 discountPercentage 作为参数,并返回最终价格。

calculateDiscountedPrice 办法的外部,咱们定义了两个嵌套办法:applyDiscountvalidateDiscountapplyDiscount 办法用于计算折扣后的价格,它接管价格和折扣作为参数,并返回折扣后的价格。validateDiscount办法用于验证折扣百分比是否超过最大折扣限度,并返回一个无效的折扣百分比。

在内部办法中,咱们首先调用 validateDiscount 办法来获取无效的折扣百分比,而后将其与原始价格一起传递给 applyDiscount 办法,计算最终价格。最初,咱们打印出最终价格。

正则表达式模型

正则表达式是用来找出数据中的指定模式(或短少该模式)的字符串。.r办法可使任意字符串变成一个正则表达式。

object Main extends App {val emailPattern = "([a-zA-Z0-9_.+-]+)@([a-zA-Z0-9-]+)\\.([a-zA-Z0-9-.]+)".r

  def validateEmail(email: String): Boolean = email match {case emailPattern(username, domain, extension) =>
      println(s"Valid email address: $email")
      true
    case _ =>
      println(s"Invalid email address: $email")
      false
  }

  // 测试
  validateEmail("john.doe@example.com")        // 输入: Valid email address: john.doe@example.com
  validateEmail("jane.doe@invalid")            // 输入: Invalid email address: jane.doe@invalid
}

在上述示例中,咱们首先创立了一个名为 emailPattern 的正则表达式对象,用于匹配电子邮件地址的模式。

而后,定义了一个名为 validateEmail 的办法,它接管一个字符串类型的电子邮件地址作为参数,并应用正则表达式模式匹配来验证电子邮件地址的有效性。

在模式匹配的 case 语句中,咱们应用 emailPattern 对传入的电子邮件地址进行匹配,并将匹配后果中的用户名、域名和扩大提取到相应的变量中。如果匹配胜利,咱们打印出验证通过的音讯,并返回 true 示意电子邮件地址无效。如果没有匹配胜利,则打印出验证失败的音讯,并返回 false 示意电子邮件地址有效。

在测试局部,咱们调用 validateEmail 办法别离传入一个无效的电子邮件地址和一个有效的电子邮件地址进行测试。依据匹配后果,咱们打印出相应的验证音讯。

型变

在 Scala 中,协变(covariance)和逆变(contravariance)是用来形容类型参数在子类型关系中的行为的概念。协变和逆变是用来指定泛型类型参数的子类型关系的形式,以确保类型安全性。

协变

协变(Covariance): 协变示意类型参数在子类型关系中具备雷同的方向。如果一个泛型类的类型参数是协变的,那么子类型的关系将放弃不变,即父类型能够被替换为子类型。在 Scala 中,能够应用 + 符号来示意协变。

上面是一个应用协变的示例代码,应用 + 符号示意类型参数 A 是协变的:

class Animal
class Dog extends Animal

class Cage[+A]

val dogCage: Cage[Dog] = new Cage[Dog]
val animalCage: Cage[Animal] = dogCage

在上述示例中,咱们定义了一个协变类 Cage[+A],它承受一个类型参数 A,并应用 + 符号来示意 A 是协变的。咱们创立了一个 dogCage,它是一个 Cage[Dog] 类型的实例。而后,咱们将 dogCage 赋值给一个类型为 Cage[Animal] 的变量 animalCage,这是非法的,因为 Cage[+A] 的协变性容许咱们将子类型的 Cage 赋值给父类型的 Cage

逆变

逆变(Contravariance): 逆变示意类型参数在子类型关系中具备相同的方向。如果一个泛型类的类型参数是逆变的,那么子类型的关系将反转,即父类型能够替换为子类型。在 Scala 中,能够应用 - 符号来示意逆变。

上面是一个应用逆变的示例代码,应用 - 符号示意类型参数 A 是逆变的:

class Animal
class Dog extends Animal

class Cage[-A]

val animalCage: Cage[Animal] = new Cage[Animal]
val dogCage: Cage[Dog] = animalCage

在上述示例中,定义了一个逆变类 Cage[-A],它承受一个类型参数 A,并应用 - 符号来示意 A 是逆变的。咱们创立了一个 animalCage,它是一个 Cage[Animal] 类型的实例。而后,咱们将 animalCage 赋值给一个类型为 Cage[Dog] 的变量 dogCage,这是非法的,因为 Cage[-A] 的逆变性容许咱们将父类型的 Cage 赋值给子类型的 Cage
通过协变和逆变,咱们能够在 Scala 中实现更灵便的类型关系,并确保类型安全性。这在解决泛型汇合或函数参数时特地有用。上面是一个更具体的示例:

abstract class Animal {def name: String}

class Dog(val name: String) extends Animal {def bark(): Unit = println("Woof!")
}

class Cat(val name: String) extends Animal {def meow(): Unit = println("Meow!")
}

class Cage[+A](val animal: A) {def showAnimal(): Unit = println(animal.name)
}

def printAnimalNames(cage: Cage[Animal]): Unit = {cage.showAnimal()
}

val dog: Dog = new Dog("Fido")
val cat: Cat = new Cat("Whiskers")

val dogCage: Cage[Dog] = new Cage[Dog](dog)
val catCage: Cage[Cat] = new Cage[Cat](cat)

printAnimalNames(dogCage) // 输入:Fido
printAnimalNames(catCage) // 输入:Whiskers

在上述示例中,定义了一个抽象类 Animal,以及它的两个子类 DogCatDogCat 类都实现了 name 办法。

而后,定义了一个协变类 Cage[+A],它承受一个类型参数 A,并应用协变符号 + 示意 A 是协变的。Cage 类有一个名为 animal 的属性,它的类型是 A,也就是动物的类型。咱们定义了一个名为 showAnimal() 的办法,它打印出 animal 的名称。

接下来,定义了一个名为 printAnimalNames() 的函数,它承受一个类型为 Cage[Animal] 的参数,并打印出其中动物的名称。

咱们创立了一个 Dog 类型的对象 dog 和一个 Cat 类型的对象 cat。而后,咱们别离创立了一个 Cage[Dog] 类型的 dogCage 和一个 Cage[Cat] 类型的 catCage

最初,咱们别离调用 printAnimalNames() 函数,并传入 dogCagecatCage。因为 Cage 类是协变的,所以能够将 Cage[Dog]Cage[Cat] 赋值给 Cage[Animal] 类型的参数,而不会产生类型谬误。

类型限界

在 Scala 中,类型上界(Upper Bounds)和类型下界(Lower Bounds)是用于限度泛型类型参数的范畴的概念。它们容许咱们在泛型类或泛型函数中指定类型参数必须满足某种条件。上面是对于类型上界和类型下界的解释和示例代码:

类型上界

类型上界(Upper Bounds): 类型上界用于指定泛型类型参数必须是某个类型或其子类型。咱们应用 <: 符号来定义类型上界。例如,A <: B 示意类型参数 A 必须是类型 B 或其子类型。

上面是一个应用类型上界的示例代码:

abstract class Animal {def name: String}

class Dog(val name: String) extends Animal {def bark(): Unit = println("Woof!")
}

class Cage[A <: Animal](val animal: A) {def showAnimal(): Unit = println(animal.name)
}

val dog: Dog = new Dog("Fido")
val cage: Cage[Animal] = new Cage[Dog](dog)

cage.showAnimal() // 输入:Fido

在上述示例中,定义了一个抽象类 Animal,以及它的子类 DogDog 类继承自 Animal 类,并实现了 name 办法。

而后,定义了一个泛型类 Cage[A <: Animal],它承受一个类型参数 A,并应用类型上界 A <: Animal 来确保 AAnimal 类型或其子类型。Cage 类有一个名为 animal 的属性,它的类型是 A。咱们定义了一个名为 showAnimal() 的办法,它打印出 animal 的名称。

创立了一个 Dog 类型的对象 dog。而后,咱们创立了一个 Cage[Animal] 类型的 cage,并将 dog 对象作为参数传递给它。

最初,调用 cageshowAnimal() 办法,它胜利打印出了 Dog 对象的名称。

类型下界

类型下界(Lower Bounds): 类型下界用于指定泛型类型参数必须是某个类型或其父类型。咱们应用 > 符号来定义类型下界。例如,A >: B 示意类型参数 A 必须是类型 B 或其父类型。

上面是一个应用类型下界的示例代码:

class Animal {def sound(): Unit = println("Animal sound")
}

class Dog extends Animal {override def sound(): Unit = println("Dog barking")
}

class Cat extends Animal {override def sound(): Unit = println("Cat meowing")
}

def makeSound[A >: Dog](animal: A): Unit = {animal.sound()
}

val dog: Dog = new Dog
val cat: Cat = new Cat

makeSound(dog) // 输入:Dog barking
makeSound(cat) // 输入:Animal sound

在上述示例中,定义了一个基类 Animal,以及两个子类 DogCat。这些类都有一个 sound() 办法,用于输入不同的动物声音。

接下来,定义了一个泛型函数 makeSound[A >: Dog](animal: A),其中类型参数 A 的下界被定义为 Dog,即 A >: Dog。这意味着 A 必须是 Dog 类型或其父类型。

makeSound() 函数外部,咱们调用传入的 animal 对象的 sound() 办法。

而后,创立了一个 Dog 对象 dog 和一个 Cat 对象 cat

最初,别离调用 makeSound() 函数,并将 dogcat 作为参数传递进去。因为类型下界被定义为 Dog,所以 dog 参数符合条件,而 cat 参数被隐式地向上转型为 Animal,也满足条件。因而,调用 makeSound() 函数时,输入了不同的声音。

通过类型上界和类型下界,咱们能够对泛型类型参数的范畴进行限度,以确保类型的束缚和类型安全性。这使得咱们可能编写更灵便、可复用且类型平安的代码。

外部类

在 Scala 中,外部类是一个定义在另一个类外部的类。外部类能够拜访外部类的成员,并具备更严密的关联性。上面是一个对于 Scala 中外部类的解释和示例代码:

在 Scala 中,外部类能够分为两种类型:成员外部类(Member Inner Class)和部分外部类(Local Inner Class)。

成员外部类: 成员外部类是定义在外部类的作用域内,并能够间接拜访外部类的成员(包含公有成员)。成员外部类能够应用外部类的实例来创立和拜访。

上面是一个示例代码:

class Outer {
  private val outerField: Int = 10

  class Inner {def printOuterField(): Unit = {println(s"Outer field value: $outerField")
    }
  }
}

val outer: Outer = new Outer
val inner: outer.Inner = new outer.Inner
inner.printOuterField() // 输入:Outer field value: 10

在上述示例中,定义了一个外部类 Outer,它蕴含一个公有成员 outerField。外部类 Inner 定义在 Outer 的作用域内,并能够拜访外部类的成员。

在主程序中,创立了外部类的实例 outer。而后,咱们应用 outer.Inner 来创立外部类的实例 inner。留神,咱们须要应用外部类的实例来创立外部类的实例。

最初,调用外部类 innerprintOuterField() 办法,它胜利拜访并打印了外部类的公有成员 outerField

部分外部类: 部分外部类是定义在办法或代码块外部的类。部分外部类的作用域仅限于所在办法或代码块外部,无奈从内部拜访。

上面是一个示例代码:

def outerMethod(): Unit = {
  val outerField: Int = 10

  class Inner {def printOuterField(): Unit = {println(s"Outer field value: $outerField")
    }
  }

  val inner: Inner = new Inner
  inner.printOuterField() // 输入:Outer field value: 10}

outerMethod()

在上述示例中,定义了一个内部办法 outerMethod。在办法外部,咱们定义了一个局部变量 outerField 和一个部分外部类 Inner

在办法外部,创立了外部类 Inner 的实例 inner。留神,外部类的作用域仅限于办法外部。

最初,调用外部类 innerprintOuterField() 办法,它胜利拜访并打印了内部变量 outerField

通过应用外部类,咱们能够在 Scala 中实现更严密的关联性和封装性,同时容许外部类拜访外部类的成员。外部类在某些场景下能够提供更清晰和组织良好的。

复合类型

在 Scala 中,复合类型(Compound Types)容许咱们定义一个类型,它同时具备多个特质(Traits)或类的个性。复合类型能够用于限度一个对象的类型,以便它同时具备多个个性。上面是对于复合类型的解释和示例代码:

复合类型应用 with 关键字将多个特质或类组合在一起,造成一个新的类型。

上面是一个示例代码:

trait Flyable {def fly(): Unit
}

trait Swimmable {def swim(): Unit
}

class Bird extends Flyable {override def fly(): Unit = println("Flying...")
}

class Fish extends Swimmable {override def swim(): Unit = println("Swimming...")
}

def action(obj: Flyable with Swimmable): Unit = {obj.fly()
  obj.swim()}

val bird: Bird = new Bird
val fish: Fish = new Fish

action(bird) // 输入:Flying...
action(fish) // 输入:Swimming...

在上述示例中,定义了两个特质 FlyableSwimmable,别离示意可航行和可游泳的个性。而后,咱们定义了两个类 BirdFish,别离实现了相应的特质。

接下来,定义了一个办法 action,它承受一个类型为 Flyable with Swimmable 的参数。这示意参数必须同时具备 FlyableSwimmable 的个性。

在主程序中,创立了一个 Bird 对象 bird 和一个 Fish 对象 fish

最初,别离调用 action 办法,并将 birdfish 作为参数传递进去。因为它们都同时具备 FlyableSwimmable 的个性,所以能够胜利调用 fly()swim() 办法。

通过应用复合类型,能够在 Scala 中定义一个类型,它同时具备多个特质或类的个性,从而实现更灵便和准确的类型束缚。这有助于编写更牢靠和可复用的代码。

多态办法

在 Scala 中,多态办法(Polymorphic Methods)容许咱们定义能够承受多种类型参数的办法。这意味着同一个办法能够依据传入参数的类型执行不同的逻辑。上面是对于多态办法的解释和示例代码:

多态办法应用类型参数来定义方法的参数类型,并应用泛型来示意能够承受多种类型参数。在办法外部,能够依据类型参数的理论类型执行不同的逻辑。

上面是一个示例代码:

def printType[T](value: T): Unit = {
  value match {case s: String => println("String:" + s)
    case i: Int => println("Int:" + i)
    case d: Double => println("Double:" + d)
    case _ => println("Unknown type")
  }
}

printType("Hello") // 输入:String: Hello
printType(123) // 输入:Int: 123
printType(3.14) // 输入:Double: 3.14
printType(true) // 输入:Unknown type

在上述示例中,定义了一个多态办法 printType,它承受一个类型参数 T。依据传入参数的类型,咱们应用模式匹配来判断其理论类型,并执行相应的逻辑。

在办法外部,应用 match 表达式对传入的参数 value 进行模式匹配。对于不同的类型,咱们别离输入相应的类型信息。

在主程序中,屡次调用 printType 办法,并传入不同类型的参数。依据传入的参数类型,办法会执行相应的逻辑并输入对应的类型信息。

函数

Scala 中一个简略的函数定义如下,咱们能够在 Scala 中应用 JDK 的类:

import java.util.Date
object Main {def main(args: Array[String]): Unit = {printCurrentDate() // 输入以后日期和工夫
  }
  def printCurrentDate(): Unit = {val currentDate = new Date()
    println(currentDate.toString)
  }
}

函数默认值

在 Scala 中,能够为函数参数指定默认值。这样,当调用函数时如果没有提供参数值,将应用默认值。上面是一个简略的示例:

object Main {def main(args: Array[String]): Unit = {greet() // 输入 "Hello, World!"
    greet("Alice") // 输入 "Hello, Alice!"
  }

  def greet(name: String = "World"): Unit = {println(s"Hello, $name!")
  }
}

高阶函数

高阶函数是指应用其余函数作为参数、或者返回一个函数作为后果的函数。在 Scala 中函数是“一等公民”,所以容许定义高阶函数。这里的术语可能有点让人困惑,咱们约定,应用函数值作为参数,或者返回值为函数值的“函数”和“办法”,均称之为“高阶函数”。

def applyFuncToList(list: List[Int], f: Int => Int): List[Int] = {list.map(f)
}

val numbers = List(1, 2, 3, 4)
val double = (x: Int) => x * 2
val doubledNumbers = applyFuncToList(numbers, double) // List(2, 4, 6, 8)

在这个例子中,applyFuncToList 函数承受一个整数列表和一个函数 f,该函数将一个整数作为输出并返回一个整数。而后,applyFuncToList 函数应用 map 办法将函数 f 利用于列表中的每个元素。在下面的代码中,咱们定义了一个 double 函数,它将输出乘以 2,并将其传递给 applyFuncToList 函数以对数字列表中的每个元素进行加倍。

匿名函数

在 Scala 中,匿名函数是一种没有名称的函数,能够用来创立简洁的函数字面量。它们通常用于传递给高阶函数,或作为部分函数应用。

例如,上面是一个简略的匿名函数,它承受两个整数参数并返回它们的和:

object Main {def main(args: Array[String]): Unit = {val add = (x: Int, y: Int) => x + y
    println(add(1, 2))  // 输入:3
  }
}

偏利用函数

简略来说,偏利用函数就是一种只对输出值的某个子集进行解决的函数。它只会对合乎特定条件的输出值进行解决,而对于不符合条件的输出值则会抛出异样。

举个例子:

object Main {def main(args: Array[String]): Unit = {println(divide.isDefinedAt(0)) // false
    println(divideSafe.isDefinedAt(0)) // true

    println(divide(1)) // 42
    println(divideSafe(1)) // Some(42)

    // println(divide(0)) // 抛出异样
    println(divideSafe(0)) // None
  }

  val divide: PartialFunction[Int, Int] = {case d: Int if d != 0 => 42 / d}

  val divideSafe: PartialFunction[Int, Option[Int]] = {case d: Int if d != 0 => Some(42 / d)
    case _ => None
  }

}

这个例子中,divide 是一个偏利用函数,它只定义了对非零整数的除法运算。如果咱们尝试用 divide 函数去除以零,它会抛出一个异样。其中isDefinedAt 是一个办法,它用于查看偏利用函数是否在给定的输出值上定义。如果偏利用函数在给定的输出值上定义,那么 isDefinedAt 办法会返回 true,否则返回 false

为了防止这种状况,咱们能够应用 divideSafe 函数,它返回一个 Option 类型的后果。如果除数为零,它会返回 None 而不是抛出异样。

柯里化函数

柯里化(Currying)是一种将多参数函数转换为一系列单参数函数的技术。咱们能够应用柯里化来定义函数,例如:

def add(a: Int)(b: Int): Int = a + b

这个 add 函数承受两个参数 ab,并返回它们的和。因为它是一个柯里化函数,所以咱们能够将它看作是一个承受单个参数 a 的函数,它返回一个承受单个参数 b 的函数。

咱们能够这样调用这个函数:

val result = add(1)(2) // 3

或者这样:

val addOne = add(1) _
val result = addOne(2) // 3

在下面的例子中,咱们首先调用 add 函数并传入第一个参数 1,而后咱们失去一个新的函数 addOne,它承受一个参数并返回它与 1 的和。最初,咱们调用 addOne 函数并传入参数 2,失去后果 3。

柯里化函数能够帮忙咱们实现参数复用和提早执行等性能。

柯里化函数的益处之一是它能够让咱们给一个函数传递较少的参数,失去一个曾经记住了某些固定参数的新函数。这样,咱们就能够在不同的中央应用这个新函数,而不须要每次都传递雷同的参数²。

此外,柯里化函数还能够帮忙咱们实现函数的提早计算。当咱们传递局部参数时,它会返回一个新的函数,能够在新的函数中持续传递前面的参数。这样,咱们就能够依据须要来决定何时执行这个函数。

惰性函数

能够应用 lazy 关键字定义惰性函数。惰性函数的执行会被推延,直到咱们首次对其取值时才会执行。

上面是一个简略的例子,展现了如何定义和应用惰性函数:

def sum(x: Int, y: Int): Int = {println("sum 函数被执行了...")
  x + y
}

lazy val res: Int = sum(1, 2)

println("-----")
println(res)

在这个例子中,咱们定义了一个函数 sum,它承受两个参数并返回它们的和。而后咱们定义了一个惰性值 res 并将其赋值为 sum(1, 2)

在主程序中,咱们首先打印了一行分隔符。而后咱们打印了变量 res 的值。因为 res 是一个惰性值,因而在打印它之前,函数 sum 并没有被执行。只有当咱们首次对 res 取值时,函数 sum 才会被执行。

这就是 Scala 中惰性函数的根本用法。你能够应用 lazy 关键字定义惰性函数,让函数的执行被推延。


本篇文章就到这里,感激浏览,如果本篇博客有任何谬误和倡议,欢送给我留言斧正。文章继续更新,能够关注公众号第一工夫浏览。

退出移动版