Scala详解

1       快速入门... 4java

1.1             分号... 4es6

1.2             常变量声明... 4编程

1.2.1         val常量... 4并发

1.2.2         var变量... 4分布式

1.2.3         类型推导... 5函数式编程

1.2.4         函数编程风格... 5函数

1.3             Range. 5工具

1.4             定义函数... 6post

1.5             while、if6性能

1.6             foreach、for. 7

1.7             读取文件... 7

1.8             类、字段和方法... 7

1.9             Singleton单例对象... 9

1.10          Scala入口程序... 10

1.11          基本类型... 11

1.12          字面量... 12

1.12.1       整数字面量... 12

1.12.2       浮点数字面量... 12

1.12.3       字符字面量... 13

1.12.4       字符串字面量... 13

1.12.5       Symbol符号字面量... 14

1.12.6       布尔字面量... 15

1.13          操做符和方法... 15

1.14          数学运算... 16

1.15          关系和逻辑操做... 17

1.16          位操做符... 17

1.17          对象相等性... 18

1.18          操做符优先级... 19

1.19          基本类型富包装类型... 19  

 

1       快速入门

1.1               分号

分号表示语句的结束;

若是一行只有一条语句时,能够省略,多条时,须要分隔

通常一行结束时,表示表达式结束,除非推断该表达式未结束:

// 末尾的等号代表下一行还有未结束的代码.

def equalsign(s: String) =

  println("equalsign: " + s)

 

// 末尾的花括号代表下一行还有未结束的代码.

def equalsign2(s: String) = {

  println("equalsign2: " + s)

}

 

//末尾的逗号、句号和操做符均可以代表,下一行还有未结束的代码.

def commas(s1: String,

           s2: String) = Console.

  println("comma: " + s1 +

          ", " + s2)

 

多个表达式在同一行时,须要使用分号分隔

 

1.2              常变量声明

1.2.1      val常量

定义的引用不可变,不能再指向别的对象,至关于Java中的final

 

Scala中一切皆对象,因此,定义一切都是引用(包括定义的基本类型变量,实质上是对象)

val定义的引用不可变,指不能再指向其余变量,但指向的内容是能够变的:

val定义的常量必需要初始化

 

val的特性能并发或分布式编程颇有好处

1.2.2      var变量

定义的引用能够再次改变(内容就更能够修改了),但定义时也须要初始化

 

在Java中有原生类型(基础类型),即char、byte、short、int、long、float、double和boolean,这些都有相应的Scala类型(没有基本类型,但比如Java中相应的包装类型),Scala编译成字节码时将这些类型尽量地转为Java中的原生类型,使你能够获得原生类型的运行效率

 

用val和var声明变量时必须初始化,但这两个关键字都可以用在构造函数的参数中,这时变量是该类的一个属性,所以显然没必要在声明时进行初始化。此时若是用val声明,该属性是不可变的;若是用var声明,则该属性是可变的:

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

即姓名不可变,但年龄是变化的

val p = new Person("Dean Wampler", 29)

 

 

var和val关键字只标识引用自己是否能够指向另外一个不一样的对象,它们并未代表其所引用的对象内容是否可变

1.2.3      类型推导

定义时能够省略类型,会根据值来推导出类型

scala> var str = "hello"

str: String = hello

scala> var int = 1

int: Int = 1

定义时也可明确指定类型:

scala> var str2:String = "2"

str2: String = 2

1.2.4      函数编程风格

之前传统Java都是指令式编程风格,若是代码根本就没有var,即仅含有val,那它或许是函数式编程风格,所以向函数式风格转变的方式之一,多使用val,尝试不用任何var编程

 

指令式编程风格:

  def printArgs(args: Array[String]): Unit = {

    var i = 0

    while (i < args.length) {

      println(args(i))

      i += 1

    }

  }

函数式编程风格:

  def printArgs(args: Array[String]): Unit = {

    for (arg <- args)

      println(arg)

  }

或者:

  def printArgs(args: Array[String]): Unit = {

    //若是函数字面量只有一行语句而且只带一个参数,

    //则么甚至连指代参数都不须要

    args.foreach(println)

  }

 

1.3  Range

数据范围、序列

支持Range的类型包括Int、Long、Float、Double、Char、BigInt和BigDecimal

 

Range能够包含区间上限,也能够不包含区间上限;步长默认为1,也能够指定一个非1的步长:

 

1.4               定义函数

函数是一种具备返回值(包括空Unit类型)的方法

 

函数体中最后一条语句即为返回值。若是函数会根据不一样状况返回不一样类型值时,函数的返回类型将是不一样值的通用(父)类型,或者是能够相互转换的类型(如Char->Int)

 

若是函数体只一条语句,能够省略花括号:

def max2(x:Int,y:Int)=if(x>y)x else y

 

scala> max2(3,5)

res2: Int = 5

 

Unit:返回类型为空,即Java中的void类型。若是函数返回为空,则能够省略

scala> def greet()=println("Hello")

greet: ()Unit

 

1.5               while、if

打印入口程序的外部传入的参数:

 

object Test {
  def main(args: Array[String]): Unit = {
    var i = 0
    while (i < args.length) {
      if (i != 0) print(" ")
      print(args(i))
      i += 1
    }
  }
}

  

注:Java有++i及i++,但Scala中没有。

 

与Java同样,while或if后面的布尔表达式必须放在括号里,不能写成诸如 if i < 10 的形式

1.6               foreach、for

object Test {
  def main(args: Array[String]): Unit = {
    var i = 0;
    args.foreach(arg => { if (i != 0) print(" "); print(arg); i += 1 })
  }
}

  

foreach方法参数要求传的是函数字面量(匿名函数),arg为函数字面量的参数,而且值为遍历出来的集合中的每一个元素,类型为String,已省略,如不省略,则应为:

    args.foreach((arg: String) => { if (i != 0) print(" "); print(arg); i += 1 })

 

若是函数字面量只有一行语句而且只带一个参数,则么甚至连指代参数都不须要:

    args.foreach(println)

 

也可使用for循环来代替:

    for (arg <- args) println(arg)

1.7               读取文件

object Test {

  def main(args: Array[String]): Unit = {

    import scala.io.Source

    //将文件中全部行读取到List列表中

    val lines = Source.fromFile(args(0)).getLines().toList

    //找到最长的行:相似冒泡排序,每次拿两个元素进行比较

    val longestLine = lines.reduceLeft((a, b) => if (a.length() > b.length()) a else b)

    //最长行的长度自己的宽度

    val maxWidth = widthOfLength(longestLine)

    for (line <- lines) {

      val numSpaces = maxWidth - widthOfLength(line)

      //让输出的每行宽度右对齐

      val padding = " " * numSpaces

      println(padding + line.length() + " | " + line)

    }

  }

 

  def widthOfLength(s: String) = s.length().toString().length()

}

 

1.8               类、字段和方法

类的方法以 def 定义开始,要注意的 Scala 的方法的参数都是 val 类型,而不是 var 类型,所以在函数体内不能够修改参数的值,好比若是你修改 add 方法以下:

 

 

使用class定义类:

class ChecksumAccumulator {}

而后就可使用 new 来实例化:

val cal = new ChecksumAccumulator

类里面能够放置字段和方法,这都称为成员(member)。

字段,不论是使用val仍是var,都是指向对象的变量(即Java中的引用)

方法,使用def进行定义

 

class ChecksumAccumulator {

  var sum = 0

}

 

object Test {

  def main(args: Array[String]): Unit = {

    val acc = new ChecksumAccumulator

    val csa = new ChecksumAccumulator

  }

}

刚实例化时内存的状态以下:

 

注:在Scala中,因为数字类型(整型与小数)都是final类型的,即不可变,因此在内存中若是是同一数据,则是共享的

 

因为上面是使用var进行定义的字段,而不是val,因此能够从新赋值:

    acc.sum = 3

如今内存状态以下:

 

 

因为修改了acc中sum的内容,因此acc.sum指向了3所在的内存。

 

对象的稳定型就是要保证对象状态的稳定型,即对象中字段值在对象整个生命周期中持续有效。这须要将字段设为private以阻止外界直接对它进行访问与修改,由于私有字段只能被同一类里的方法访问,因此更新字段的代码将被锁定在类里:

class ChecksumAccumulator {

  private var sum = 0

  def add(b: Byte): Unit = {

    sum += b

  }

}

与Java 不一样的,Scala 的缺省修饰符为 public,也就是若是不带有访问范围的修饰符 public,protected,private,Scala 缺省定义为 public

 

类的方法以 def 定义开始,要注意的 Scala 的方法的参数都是 val 类型,而不是 var 类型,所以在函数体内不能够修改参数的值,好比若是你修改 add 方法以下:

  def add(b: Byte): Unit = {

    b = 1 // 编译出错,由于b是val :error: reassignment to val

    sum += b

  }

 

若是某个方法的方法体只有一条语句,则能够去掉花括号:

def add(b: Byte): Unit = sum += b

 

类的方法分两种,一种是有返回值的,一种是不含返回值

 

若是方法没有返回值(或为Unit),则定义方法时能够去掉结果类型和等号 =,并把方法体放在花括号里:

def add(b: Byte) { sum += b }

 

定义方法时,若是去掉方法体前面的等号 =,则方法的结果类型就必定是Unit,这无论方法体最后语句是啥,由于编译器能够把任何类型转换为Unit,如结果是String,但返回结果类型声明为Unit,那么String将被转换为Unit并丢弃原值。下面是明肯定义返回类型为Unit:

scala> def f(): Unit = "this String gets lost"

f: ()Unit

去掉等号的方法返回值类型也必定是Unit:

scala> def g() { "this String gets lost too" }

g: ()Unit

 

加上等号时,若是没有肯定定义返回类型,则会根据方法体最后语句来推导:

scala> def h() = { "this String gets returned!" }

h: ()java.lang.String

scala> h

res0: java.lang.String = this String gets returned!

 

Scala 代码无需使用“;”结尾,也不须要使用 return返回值,函数的最后一行的值就做为函数的返回值

1.9               Singleton单例对象

Scala中不能定义静态成员,而是以定义成单例对象(singleton object)来代替,即定义类时,使用的object关键字,而非class关键字,但看上去就像定义class同样:

class ChecksumAccumulator {

  private var sum = 0

  def add(b: Byte): Unit = {

    sum += b

  }

  def checksum(): Int = {

    return ~(sum & 0xFF) + 1

  }

}

 

import scala.collection.mutable.Map

object ChecksumAccumulator {

  private val cache = Map[String, Int]()

  def calculate(s: String): Int =

    if (cache.contains(s))

      cache(s)

    else {

      val acc = new ChecksumAccumulator

      for (c <- s)

        acc.add(c.toByte)

      val cs = acc.checksum()

      cache += (s -> cs)

      cs

    }

}

当单例对象(object)与某个类(class)的名称相同时(上面都为ChecksumAccumulator),它就被称为是这个类的伴生对象。类和它的伴生对象必须定义在一个源文件里。类被称为这个单例对象的伴生类。类和它的伴生对象能够互相访问其私有成员

 

能够将单例对象看成Java中的静态方法工具类来使用,能够直接经过单例对象的名称来调用:

ChecksumAccumulator.calculate("Every value is an object.")

其实,单例对象就是一个对象,不须要实例化就能够直接经过单例对象名来访问其成员,即单例对象名就至关于变量名,已指向了某个类的实例,只不过该类不是由你来实例化,而是在访问它时由Scala实例化出来的,且在JVM只有一个这个的实例。在编译伴生对象时,会生成一个相应名为ChecksumAccumulator$(在单例对象名后加上美圆符号)的类:

 

类和单例对象的差异:单例对象定义时不带参数(Object关键字后面),而类能够(Class关键字后面能够带括号将类参数包围起来),由于单例对象不是使用new关键字实例化出来的(这也是 Singleton 名字的由来)

 

单例对象在第一次被访问的时候才会被始化

 

没有伴生类的单例对象被称为独立对象,通常做为相关功能方法的工具类,或者用做Scala应用的入口程序

 

1.10         Scala入口程序

在Java中,只要类中有以下签名的main方法,便可做为程序入口程序:

class T {

   public static void main(String[] args) {}

}

在Scala中,入口程序不是定义在类class中的,而是定义在单例对象中的,

object T {

  def main(args: Array[String]): Unit = {}

}

与Java 相似,Scala 中任何 Singleton对象(使用Object关键字定义),若是包含 main 方法,均可以做为应用的入口

 

Scala的每一个源文件都会自动引入包java.lang和scala包中的成员,和scala包中名为Predef的单例对象的成员,该单例对象中包含了许多有用的方法,例如,当在Scala源文件中写pringln的时候,实际调用了Predef.println,另外当你写assert,实质上是调用Predef.assert

 

Java的源文件扩展名为.java,而Scala的源文件扩展名为.scala

 

在Java中,若是源文件中有public的class,则该public类的类名必须与Java源文件名一致,但在Scala中没有这种限制,但通常会将源文件名与类名设为一致

 

与Java同样,也有对应的编译与运行命令,它们分别是scalac(编译)与scala(运行),ava中的为javac、java,不论是Java仍是Scala程序,都会编译成.class的字节码文件

 

Scala 为 Singleton 对象的 main 定义了一个 App trait 类型

Scala的入口程序还能够继承scala.App特质(Trait,Scala中的Trait像Java中的Interface,但不一样的是能够有方法的实现),这样就不用写main方法(由于scala.App特质里实现了main方法),而直接将代码写在花括号里,花括号里的代码会被收集进单例对象的主构造器中,并在类被初始化时执行:

object T extends scala.App {

  println("T")

}

缺点:命令参数行args不能再被访问;某些JVM线程会要求main方法不能经过继承获得,必须本身行编写;

 

1.11         基本类型

Java 支持的基本数据类型,Scala 都有对应的支持,不过 Scala 的数据类型都是对象,并且这些基本类型均可以经过隐式自动转换的形式支持比 Java 基本数据类型更多的方法:好比调用 (-1).abs() ,Scala 发现基本类型 Int 没有提供 abs()方法,但能够发现系统提供了从 Int 类型转换为 RichInt 的隐式自动转换,而 RichInt 具备 abs 方法,那么 Scala 就自动将 1 转换为 RichInt 类型,而后调用 RichInt 的 abs 方法。

 

Scala 的基本数据类型有: Byte,Short,Int,Long 和 Char (这些成为整数类型)。整数类型加上 Float 和 Double 成为数值类型。此外还有 String 类型,除 String 类型在 java.lang 包中定义,其它的类型都定义在包 scala 中。好比 Int 的全名为 scala.Int。实际上 Scala 运行环境自动会载入包 scala 和 java.lang 中定义的数据类型,你可使用直接使用 Int,Short,String 而无需再引入包或是使用全称(如scala.xx与java.lang.xx)。

 

Scala的基本类型与Java对应类型范围彻底同样,这样可让Scala编译器直接把这些类型编译成Java中的原始类型

 

scala> var hex=0xa  //十六进制,整数默认就是Int类型

hex: Int = 10

scala> var hex:Short=0x00ff //若要Short类型,则要明确指定变量类型

hex: Short = 255

scala> var hex=0xaL //赋值时明确指定数据为Long型,不然默认为Int类型

hex: Long = 10

scala> val prog=2147483648L //若超过了Int范围,则后面必定要加上 L ,置换为Long类型

prog: Long = 2147483648

scala> val bt:Byte= 38 //若要Byte类型,则要在定义时明确指定变量的类型为Byte类型

bt: Byte = 38

 

scala> val big=1.23232 //小数默认就是Double类型

big: Double = 1.23232

 

scala> val big=1.23232f //若是要定义成Float,则可直接在小数后面加上F

big: Float = 1.23232

scala> val big=1.23232D //虽然默认就是Double,但也可在小数后面加上D

big: Double = 1.23232

 

scala> val a='A' //类型推导成Char类型

a: Char = A

scala> val f ='\u0041' //也可使用Unicode编码表示,以 \u 开头,u必定要小写,且\u后面接4位十六进制

f: Char = A

 

scala> val hello="hello" //类型推导成String类型

hello: String = hello

scala> val longString=""" Welcome to Ultamix 3000. Type "Help" for help.""" //以使用三个引号(""")开头和结尾,这样之间的字符都将看做是最原始的字符,不会被转义

longString: String = " Welcome to Ultamix 3000. Type "Help" for help." //注:开头与结尾的双引号不属于上面字符串的一部分,而是表示控制台上输出的是String

 

scala> val bool=true

bool: Boolean = true

1.12         字面量

字面量就是直接写在代码里的常量值

1.12.1              整数字面量

十六进制以 0x或0X开头:

scala> val hex = 0x5

hex: Int = 5

scala> val hex2 = 0x00FF

hex2: Int = 255

scala> val magic = 0xcafebabe

magic: Int = -889275714

注:不区分大小写

 

八进制以0开头

scala> val oct = 035 // (八进制35是十进制29)

oct: Int = 29

scala> val nov = 0777

nov: Int = 511

scala> val dec = 0321

dec: Int = 209

 

若是是非0开头,即十进制:

scala> val dec2 = 255

dec2: Int = 255

 

注:无论字面量是几进制,输出时都会转换为十进制

 

若是整数以L或l结尾,就是Long类型,不然默认就是Int类型:

scala> val prog = 0XCAFEBABEL

prog: Long = 3405691582

scala> val tower = 35L

tower: Long = 35

scala> val of = 31l

of: Long = 31

 

从上面能够看出,定义Int型时能够省去类型便可,若是是Long类型,定义时也可省略Long类型,此时在数字后面加上L或l便可,但也能够直接定义成Long也可:

scala> var lg:Long = 2

lg: Long = 2

若是要获得Byte或Short类型的变量时,需在定义时指定变量的相应类型:

scala> val little: Short = 367

little: Short = 367

scala> val littler: Byte = 38

littler: Byte = 38

1.12.2              浮点数字面量

scala> val big = 1.2345

big: Double = 1.2345

scala> val bigger = 1.2345e1

bigger: Double = 12.345

scala> val biggerStill = 123E45

biggerStill: Double = 1.23E47

 

小数默认就是Double类型,若是要是Float,则要以F结尾:

scala> val little = 1.2345F

little: Float = 1.2345

scala> val littleBigger = 3e5f

littleBigger: Float = 300000.0

 

固然Double类型也能够D结尾,不过是可选的

scala> val anotherDouble = 3e5

anotherDouble: Double = 300000.0

scala> val yetAnother = 3e5D

yetAnother: Double = 300000.0

 

固然,也要以在定义变量时明确指定类型也可:

scala> var f2 = 1.0

f2: Double = 1.0

scala> var f2:Float = 1

f2: Float = 1.0

1.12.3              字符字面量

使用单引号引发的单个字符

scala> val a = 'A'

a: Char = A

 

单引号之间除了直接是字符外,也能够是对应编码,编码是八进制或十六进制来表示

 

若是以八进制表示,则以 \ 开头,且为 '\0 到 '\377' (0377=255):

scala> val c = '\101'

c: Char = A

注:若是以八进制表示,则只能表示一个字节大小的字符,即0~255之间的ASCII码单字节字符,若是要表示大于255的Unicode字符,则只能使用十六进制来表示:

scala> val d = '\u0041'

d: Char = A scala>

val f = '\u0044'

f: Char = D

scala> val c = '\u6c5f'

c: Char = 江

注:以十六进制表示时,需以 \u(小写)开头,即后面跟4位十六进制的编码(两个字节)

 

实际上,十六进制能够出如今Scala程序的任何地方,如能够用在变量名里:

scala> val B\u0041\u0044 = 1

BAD: Int = 1

 

转义字符:

scala> val backslash = '\\'

backslash: Char = \

 

1.12.4              字符串字面量

使用双引号引发来的0个或多个字符

scala> val hello = "hello"

hello: java.lang.String = hello

 

特殊字符也需转义:

scala> val escapes = "\\\"\'"

escapes: java.lang.String = \"'

 

若是字符串中须要转义的字符不少时,可使用三个引号(""")开头和结尾,这样之间的字符都将看做是最原始的字符,不会被转义(固然三个连续的引号除外):

 

发现第二行前面的空格也会原样输出来,因此第二行前面看起来缩进了,若是要去掉每行前面的空白字符(ASCII编码小于等于32的都会去掉),则把管道符号(|)放在每行前面,而后对字符串调用stripMargin:

 

1.12.5              Symbol符号字面量

以单引号打头,后面跟一个或多个数字、字母或下划线,但第一个字符不能是数字,这种字面量会转换成预约义类scala.Symbol的实例,如 'cymbal编译器将会调用工厂方法Symbol("cymbal")转化成Symbol实例。

scala> val s = 'aSymbol

s: Symbol = 'aSymbol

scala> s.name

res20: String = aSymbol

 

符号字面量 'x 是表达式 scala.Symbol("x") 的简写

 

Java中String的intern()方法:String类内部维护一个字符串池(strings pool),当调用String的intern()方法时,若是字符串池中已经存在该字符串,则直接返回池中字符串引用,若是不存在,则将该字符串添加到池中,并返回该字符串对象的引用。执行过intern()方法的字符串,咱们就说这个字符串被拘禁了(interned),即放入了池子。默认状况下,代码中的字符串字面量和字符串常量值都是被拘禁的,例如:

      String s1 = "abc";

      String s2 = new String("abc");

      System.out.println(s1 == s2);//false

      System.out.println(s1 == s2.intern());//true

同值字符串的intern()方法返回的引用都相同,例如:

      String s2 = new String("abc");

      String s3 = new String("abc");

      System.out.println(s2 == s3);// false

      System.out.println(s2.intern() == s3.intern());// true

 

      String str1 = "abc";

      String str2 = "abc";

      System.out.println(str1 == str2);//true

      String str3 = new String("abc");

      System.out.println(str1 == str3);//false

 

Sysmbol实质上也是一种字符串,其好好处:

1. 节省内存

在Scala中,Symbol类型的对象是被拘禁的(interned,即会被放入池中),任意的同名symbols都指向同一个Symbol对象,避免了因冗余而形成的内存开销:

    val s1 = "aSymbol"

    val s2 = "aSymbol"

    println(s1.eq(s2)) //true :代表s1与s2指向同一对象

    val s3 = new String("aSymbol") //因为在编译时就肯定,因此仍是会放入常量池

    println(s1.eq(s3)) //false : 代表s1与s3不是同一对象

    println(s1 == s3) //true:虽然不是同一对象,可是它们的内容相同

 

    val s = 'aSymbol

    println(s.eq('aSymbol)) //true

println(s.eq(Symbol("aSymbol"))) //true:只要是同名的Symbol,则都是指向同一对象

    //即便s与s3的内容相同,但eq比较的是对象地址,因此不等

    println(s.name.eq(s3)) //false

    println(s.name == s3) //true:但内容相同

println(s.name.eq(s1)) //true : s与s1的的内容都会放入池中,因此指向的是同一对象

注:在Scala中,若是要基于引用地址进行比较,则要使用eq方法,而不是==,这与Java是不同的

2. 快速比较

因为Symbol类型的对象会自动拘禁的(interned),任意的同名symbols(准确的说是值)都指向同一个Symbol对象,而相同值的字符串并不必定是同一个instance,因此symbols对象之间的比较操做符==速度会很快:由于它只基于地址进行比较,若是发现不是同一Symbols对象,则就认为不相同,不会在对内容进行比较(由于不一样名的Symbols的值确定也不相同)

Symbol类型的应用

Symbol类型通常用于快速比较,例如用于Map类型:Map<Symbol, Data>,根据一个Symbol对象,能够快速查询相应的Data, 而Map<String, Data>的查询效率则低不少。

虽然说利用String的intern方法也能够实现Map<String, Data>的键值快速比较,可是因为须要显式地调用intern()方法,在编码时会形成不少的麻烦,并且若是忘了调用intern()方法,还会形成难以寻找的bug。从这个角度看,Scala的Symbol类型会自动进行intern操做(加入到池中),因此简化了编码的复杂度;而Java中除了字符串常量,是不会自动进行intern的,须要对相应对象手动调用interned方法

1.12.6              布尔字面量

scala> val bool = true

bool: Boolean = true

scala> val fool = false

fool: Boolean = false

1.13         操做符和方法

操做符如+加号,实质上是类型中有名为 + 的方法:

scala> val sum = 1 + 2 // Scala调用了(1).+(2)

sum: Int = 3

scala> val sumMore = (1).+(2)

sumMore: Int = 3

实际上Int类型包含了名为 + 的各类不一样参数的重载方法,如Int+Long:

scala> val longSum = 1 + 2L // Scala调用了(1).+(2L)

longSum: Long = 3

 

既然+是名为加号的方法,能够以 1+2 这种操做模式来调用,那么其余方法也是能够以下方式来调用:

scala> val s = "Hello, world!"

s: java.lang.String = Hello, world!

scala> s indexOf 'o' // 调用了s.indexOf(’o’)

res0: Int = 4

若是有多个参数,则要使用括号:

scala> s indexOf ('o', 5) // Scala调用了s.indexOf(’o’, 5)

res1: Int = 8

 

在 Scala 中任何方法均可以是操做符:Scala里的操做符不是特殊的语法,任何方法均可以是操做符,究竟是方法仍是操做符取决于你如何使用它。若是写成s.indexOf('o'),indexOf就不是操做符。不过若是写成,s indexOf 'o',那么indexOf就是操做符了

 

上面看到的是中缀操做符,仍是前缀操做符,如 -7里的“-”,后缀操做符如 7 toLong里的“toLong”( 实为 7.toLong )。

前缀操做符与后缀操做都只有一个操做数,是一元(unary)操做符,如-2.0、!found、~0xFF,这些操做符对应的方法是在操做符前加上前缀“unary_”:

scala> -2.0 // Scala调用了(2.0).unary_-

res2: Double = -2.0

scala> (2.0).unary_-

res3: Double = -2.0

 

能够看成前缀操做符用的标识符只有+、-、!和~。所以,若是你定义了名为unary_!的方法,就能够对值或变量用 !P 这样的前缀操做符方式调用方法。可是若是你定义了名为unary_*的方法,就没办法将其用成前缀操做符了,由于*不是四种能够看成前缀操做符用的标识符之一。

 

后缀操做符是不用点与括号调用的不带任何参数的方法:

scala> val s = "Hello, world!"

s: java.lang.String = Hello, world!

scala> s.toLowerCase()

res4: java.lang.String = hello, world!

因为不带参数,则可能省略括号

scala> s.toLowerCase

res5: java.lang.String = hello, world!

还能够省去点:

scala> import scala.language.postfixOps//须要导一下这个来激活后缀操做符使用方式,不然会警告

import scala.language.postfixOps

scala> s toLowerCase

res6: java.lang.String = hello, world!

1.14         数学运算

scala> 1.2 + 2.3

res6: Double = 3.5

scala> 3 - 1

res7: Int = 2

scala> 'b' - 'a'

res8: Int = 1

scala> 2L * 3L

res9: Long = 6

scala> 11 / 4

res10: Int = 2

scala> 11 % 4

res11: Int = 3

scala> 11.0f / 4.0f

res12: Float = 2.75

scala> 11.0 % 4.0

res13: Double = 3.0

 

数字类型还提供了一元的前缀 + 和 - 操做符(方法 unary_+ 和 unary_-)

scala> val neg = 1 + -3

neg: Int = -2

scala> val y = +3

y: Int = 3

scala> -neg

res15: Int = 2

1.15         关系和逻辑操做

scala> 1 > 2

res16: Boolean = false

scala> 1 < 2

res17: Boolean = true

scala> 1.0 <= 1.0

res18: Boolean = true

scala> 3.5f >= 3.6f

res19: Boolean = false

scala> 'a' >= 'A'

res20: Boolean = true

 

可使用一元操做符!(unary_!方法)改变Boolean值:

scala> val thisIsBoring = !true

thisIsBoring: Boolean = false

scala> !thisIsBoring

res21: Boolean = true

 

逻辑与(&&)和逻辑或(||):

scala> val toBe = true

toBe: Boolean = true

scala> val question = toBe || !toBe

question: Boolean = true

scala> val paradox = toBe && !toBe

paradox: Boolean = false

 

与Java里同样,逻辑与和逻辑或有短路:

scala> def salt() = { println("salt"); false }

salt: ()Boolean

scala> def pepper() = { println("pepper"); true }

pepper: ()Boolean

scala> pepper()&& salt()

pepper

salt

res22: Boolean = false

scala> salt()&& pepper()

salt

res23: Boolean = false

 

scala> pepper() || salt()

pepper

res24: Boolean = true

scala> salt() || pepper()

salt

pepper

res25: Boolean = true

1.16         位操做符

按位与运算(&):都为1时才为1,不然为0

按位或运算(|):只要有一个为1 就为1,不然为0

按位异或运算(^):相同位产生0,不一样产生1,所以0011 ^ 0101产生0110。

scala> 1 & 2  // 0001 & 0010 = 0000 = 0

res24: Int = 0

scala> 1 | 2 // 0001 | 0010 = 0011 = 3

res25: Int = 3

scala> 1 ˆ 3 // 0001 ^ 0011 = 0010 = 2

res26: Int = 2

scala> ~1  // ~0001 = 1110(负数原码:从最末一位向前除符号位各位取反便可) = 1010 = -2

res27: Int = -2

负数补码:反码+1

 

左移(<<),右移(>>)和无符号右移(>>>)。左移和无符号右移在移动的时候填入零。右移则在移动时填入左侧整数的最高位(符号位)。

scala> -1 >> 31 //1111 1111 1111 1111 1111 1111 1111 1111 向右移动后,仍是 1111 1111 1111 1111 1111 1111 1111 1111,左侧用符号位1填充

res38: Int = -1

scala> -1 >>> 31 //1111 1111 1111 1111 1111 1111 1111 1111 向右无符号移动后,为0000 0000 0000 0000 0000 0000 0000 0001,左侧用0填充

es39: Int = 1

scala> 1 << 2 //0000 0000 0000 0000 0000 0000 0000 0001 向左位移后,为0000 0000 0000 0000 0000 0000 0000 0100,右侧用0填充

res40: Int = 4

1.17         对象相等性

基本类型比较:

scala> 1 == 2
res31: Boolean = false
scala> 1 != 2
res32: Boolean = true
scala> 2 == 2
res33: Boolean = true

 

这些操做对全部对象都起做用,而不只仅是基本类型,如列表的比较:

scala> List(1, 2, 3) == List(1, 2, 3)
res34: Boolean = true
scala> List(1, 2, 3) == List(1, 3, 2)
res35: Boolean = false

 

还能够对不一样类型进行比较,如:

scala> 1 == 1.0
res36: Boolean = true
scala> List(1, 2, 3) == "hello"
res37: Boolean = false

 

甚至能够与null进行比较:

scala> List(1, 2, 3) == null
res38: Boolean = false

 

== 操做符在比较以前,会先判断左侧的操做符是否为null,不为null时再调用左操做数的equals方法进行比较,比较的结果主要是看这个左操做数的equals方法是怎么实现的。只要比较的二者内容相同且而且equals方法是基于内容编写的,不一样对象之间比较也可能为true。x == that判断表达式判断的过程实质以下:

    if (x.eq(null))

      that eq null

    else

      x.equals(that)

equals方法是检查值是否相等,而eq方法检查的是引用是否相等。因此若是使用 == 操做符比较两个对象时,若是左操做数是null,那么调用eq判断右操做数也是否为null;若是左操做数不是null的状况则调用equals基于对象内容进行比较

 

!= 就是 == 计算结果取反

Java中的==便可以比较原始类型,也能够比较引用类型。对于原始类型,Java的==比较值的相等性,与Scala一致,而对于引用类型,Java的==是比较这两个引用是否都指向了同一个对象,不过Scala比较对象地址不是 == 而是使用eq方法,因此Scala中的 == 操做符做用于对象时,会转换调用equals方法来比较对象的内容(在左操做数非null状况下)

 

AnyRef的equals方法默认调用eq方法实现,也就是说,默认状况下,判断两个变量相等,要求必须指向同一个对象实例

 

注:在Scala中,若是要基于引用地址进行比较,则要使用eq方法,而不是==,这与Java是不同的

1.18         操做符优先级

Scala中没有操做符,操做符只是方法的一种表达方式,其优先级是根据做用符的第一个字符来判断的(也有例外,请看后面以等号 = 字符结束的一些操做符),如规定第一个字符*就比+的优先级高。以操做符的第一个字符为依据,优先级以下:

(全部其余的特殊字符)
* / %
+ -
:
= !
< >
&

^

|
(全部字母)

(全部赋值操做)

上面同一行的字符具备一样的优先级

 

scala> 2 << 2 + 2 // 2<< (2 + 2)
res41: Int = 32

<<操做符第一个字符为<,根据上表 << 要比 + 优先级低

 

若是操做符以等号字符( =)结束 , 且操做符并不是比较操做符<=,> =, ==,或=,那么这个操做符的优先级与赋值符( =)相同。也就是说,它比任何其余操做符的优先级都低。例如:

x *= y + 1

与下面的相同:

x *= (y + 1)

操做符 *= 以 = 结束,被看成赋值操做符,它的优先级低于+,尽管操做符的第一个字符是*看起来高于+。

 

任何以“:”字符结尾的方法由它的右操做数调用,并传入左操做数;其余结尾的方法与之相反,它们被左操做数调用,并传入右操做:a * b 变成 a.*(b), a:::b 变成 b.:::(a)。

 

多个同优先级操做符出现时,若是方法以:结尾,它们就被从右往左进行分组;反之,就从左往右进行分组,如:a ::: b ::: c 会被看成 a :::

(b ::: c),而 a * b * c 被看成(a * b) * c

1.19         基本类型富包装类型

基本类型除了一些常见的算术操做外,还有一些更为丰富的操做,这些操做能够直接使用,更多须要参考API中对应的富包装类:

上述相应的富操做都是由下面相应的富包装类提供的,使用前会先自动进行隐式转换:

 

相关文章
相关标签/搜索