专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Scala之:运算符与操作流程

本章重点介绍Scala基础之运算符与操作流程。

与Java不同,Scala的运算符可以进行重载。而选择,循环分支在Scala这里都做出了非常大的改变,比如说:Scala 中并移除了古老的switch分支;for循环可以用来收集元素……等等。

从本章开始,Scala的灵活性和复杂性就开始体现出来了。可能正因为Scala过于灵活,导致它的上手门槛要比 Java 更加困难,愿意拥抱这门语言的开发人员并不多。比如,目前国内绝大部分程序员仍然选择使用Java开发Spark项目。

这印证了”世界上没有免费的午餐”定理:一个灵活的语言必然带来了上手难度的提高。

Scala 运算符

无论是何种编程语言,运算符无非分为三类:算数运算符赋值运算符关系运算符,So does Scala。

% 与 /

笔者有时会搞混这两个符号的区别……%符号是取余数/则是取商(向下取整)

/又称之为div操作,%又称之为mod操作。

println(10 / 3) // == 3
println(10 % 3) // == 1

Scala中没有++和–

Scala中取消了++--运算符,具体原因不明,但至少不用再去思考++i还是i++的问题了。

若要实现自增操作,则只能使用复古的+=1去解决问题:

var i = 3

// wrong : i++
i += 1

注意:虽然Scala中没有++--操作,但是我们可以人为地对这些运算符进行重载操作。有关于重载部分,我们在后续部分会给出。

Scala移除了三目运算符

Scala同样取消了原先的:?三目运算符,而是改用更加易懂的if-else分支来表述:

var a = 3

//b =  (a > 2) ? 2 : 3
val b = if(a > 2) 2 else 3

换句话说,Scala就是使用if-else选择分支取代了原本的三目运算符,究其原因是Scala中的if-else具备返回值的。因此即使写一个更加复杂的if-else分支来决定b的赋值完全可行!此时,传统的:?就不能再达到同样的效果了。

 val b = if(a > 2) 2 else if(a ==2) 3 else 4

简单了解Scala的输入语句

如何实现如同C语言的scanf功能呢?只需要导入scala.io.StdIn,而后调用StdInreadXXX功能即可。

import scala.io.StdIn

val msg = StdIn.readLine()

//$符号形式打印字符串。
println(s"get message + $msg")

[重要]Scala的控制流程

Scala的选择分支

前文已经介绍过,Scala的if-else内的每个分支都具备返回值

当然,这并不意味着我们一定会使用到这个返回值。当你的判断分支不需要用到返回值的时候,则可以忽略掉它。则此时,每个默认分支返回的是一个Unit。(Unit在Scala中的作用和void相当)

当程序运行时发现没有任何一个分支满足条件时,这个if-else分支本身同样也会返回一个Unit

if (a > 3) {
  println("a > 3")
} else if (a < 3) {
  println("a  < 3")
} else {
  println("a == 3")
}

if-else会默认选择每个分支中最后一个具有返回值的语句,也可以使用return显示声明(但并没有必要)。比如下面的代码块,b会根据a与3的比较结果而被赋予不同的值。

由于每个分支内返回的都是Int类型的值,因此Scala的编译器能自动推断出来b的类型同样属于Int

val b = if (a > 3) {
  println("a > 3")

  //return 4
  4
} else if (a < 3) {
  println("a  < 3")

  //return 2
  2
} else {
  println("a == 3")

  //return 3
  3
}

//b可以推断出是int类型。
println(b.getClass)

换句话说,如果每个分支返回的类型不一样,则编译器就无能为力了。它只能认为b是一个Any类型的某个上转型对象。

val b : Any = if (a > 3) {
  println("a > 3")

  //return a Int value
  4
} else if (a < 3) {
  println("a  < 3")

  //return a String
  "2"
} else {
  println("a == 3")

  //return a Double value
  3.00
}

//b可能是属于Any类的Double,Int,或者是String。    
println(b.getClass)

Scala取消了“古老的”switch分支

它被Scala被淘汰的原因就和:?一样:Scala本身有非常健壮与复杂模式匹配来完美替换了switch的功能。模式匹配涉及到了相当多的内容,现在介绍它为时尚早。因此笔者选择在靠后的篇章中介绍它。

Scala 循环分支—for循环

总体来说,Scala的for循环在原来的Java for循环基础上做了相当,相当多的改动和包装。本小节会逐一介绍细节。

Scala的for循环直接融合了Java中for-each增强for循环的思想:你不止可以使用下标去遍历,也可以用元素去遍历。

//我们在后续的篇幅中介绍数组和集合。
val arr: Array[Int] = Array[Int](1,2,3,4,5)

//相当于Java中的for-each循环。
for(i <- arr) println(i)

仅需寥寥一行,就可以打印出这个arr数组内的所有元素了。其中,i <- arr代表着:arr数组内的每一个元素i

1. to 与 until

我们可以利用tountil直观地表达出from...to(until)...的涵义,而不再需要i=0;i<(=)...;i...这种三段式的写法了。

tountil的区别就在于:是否包括最后一个元素。放到之前的for循环结构来说,就是判断条件中:<(until)和<=(to)的区别。

for(i <- 1 to 10) println(i) //输出 1 到 10,等价于i=1;i<=10;i++
for(i <- 1 until  10) println(i) //输出 1 到9,等价于i=1;i<10;1++

编译器为什么没有高亮显示to和until呢?因为实际上to和until是Scala已经替你实现的方法,它们属于一个RichInt的final class。

2. 根据数组下标迭代元素

在某些情况下,我们希望在遍历的时候顺便知道这个元素处于数组的第几个下标位。在老版本的for循环中,其实我们并不担心这个问题,因为大部分的for循环我们直接就是使用下标去遍历的:

//我们在Java中的做法
int[] arr = {1,2,3,4,5,6};

for(int i = 0 ; i< arr.length; i++)
{
    System.out.println(arr[i]);
}

在Scala中,数组提供一个indices方法来返回一个下标:

//arr.indices获取的是数组的下标,而非元素。
for(index <- arr.indices) println(s"第${index}个位置:${arr(index)}")

indices本质上是一个对0 until length的一个包装,其中length是每个数组的长度。

3. Scala的for循环没有continue

Scala中没有continue,取而代之是循环守卫的概念:即在执行for循环之前,插入if判断。只有if后面的表达式全部为真,才执行for循环的结构体,反之不执行。

比如输出0-10之内的偶数:

//输出0-10以内的偶数。
for(i <- 0 to 10 if i % 2 ==0) { println(i)}

循环守卫的引入,使得我们可以使用最精简的语句就可以实现判断 + 循环的功能。

4. Scala的嵌套for循环可以折叠

我们偶尔会使用到嵌套的for循环来解决问题,以冒泡排序为例。

//一个混乱的int数组
int[] arr = {3,7,6,8,1,4,2};

//冒泡排序进行小->大排列
for(int i = 0 ; i< arr.length; i++)
{
    for (int j = 0;j<arr.length-i-1; j++)
    {
        if (arr[j] > arr[j+1]){
            int temp = arr[j];
            arr[j] =arr[j+1];
            arr[j+1] = temp;
        }
    }
}

//输出
for (int i : arr){ System.out.println(i); }

而实际上只有在最内部的嵌套循环中执行了逻辑。因此在Scala中,我们可以做如下简化:

for (i <- arr.indices; j <- 0 until arr.length - i -1 if arr(j)>arr(j+1)) {
    val temp = arr(j)
    arr(j) = arr(j+1)
    arr(j+1) = temp
}

另外,当for循环的条件语句较多时,全部拥挤在一个()内可能不利于代码阅读。因此上述的代码还可以改写成如下格式:

for {
    i <- arr.indices 
    j <- 0 until arr.length - i -1 
    if arr(j)>arr(j+1)
}{
    val temp = arr(j)
    arr(j) = arr(j+1)
    arr(j+1) = temp
}

5. 使用Range类进行跨步长迭代

Scala中没法使用i=0;i<10;i+2这样的写法进行跨步迭代;因此要使用一个Range类进行辅助构造:

Range的构造函数如下,构造的是一个[start,end)区间。

Range(start:Int,end:Int,step:Int)
//即通过起始下标,终止下标,步长来生成一个等差为step的数列。注意,生成的序列中不包含end,但是包含start。

尝试生成1到11以内的奇数:

//1,3,5,7,9,但不包含11。
for(i <- Range(1,10,2)){
    println(i)
}

Range本质上是属于scala.collection.immutable下的一个不可变集合。

6. 利用yield收集元素

if-else分支一样,for循环分支同样拥有返回值。我们可以使用yield将这些元素收集起来。

//将每一个元素i装载到list内。
val ints: immutable.IndexedSeq[Int] = for(i <- Range(1,10,3)) yield i

yield后面实际上会承接一个语句块。同样的,yield会按照最后一个具有返回值的语句收集元素,并整体返回一个IndexSeq类型集合。

7. Scala利用抛出异常实现break

Scala不仅抛弃了continue关键字,break也被舍弃了。如果要实现中断功能,则需要通过主动抛出异常的方式进行。

在此之前,我们首先需要引入import scala.util.control.Breaks.__表示引入Breaks类的所有内容。

随后,我们在需要可能使用中断的语句块中使用breakable(()=>{...})包裹起来。然后再需要中断的地方调用break()语句。其作用相当于break关键字。

//breakable是一个控制抽象。内部执行一个()=>Unit的函数。
breakable(() => {
  var i = 0
  while (i < 10) {
    if (i == 5) break()
    i += 1
  }
}
)

这个breakable本质上是一个控制抽象,它接收一个()=>Unit类型的函数,并将这个函数体内的语句分配到一个线程当中去执行。笔者会在后续的 ”Scala之函数高级部分“ 中介绍高阶函数,控制抽象相关的知识点。

Scala保留了while/do-while循环

Scala中有while/do-while循环与Java无异,即无法用步长作为终止条件去控制循环分支时,会选择使用while循环。

但是,Scala的设计者马丁·奥德斯基并不推崇使用while,而是尽可能地使用for做循环。原因是:if分支和for循环都可以有返回值(for循环需要yield),而while循环并没有。因此如果要保存变量,或者判断条件,则不可避免地要用到外部的变量。而马丁认为:内部的代码不应该对外部的内容进行更改

最明显的例子是:Java 8 中,尝试在一个lambda表达式内去更改外部的值,则会报错

文章永久链接:https://tech.souyunku.com/41992

未经允许不得转载:搜云库技术团队 » Scala之:运算符与操作流程

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们