scala语法知识

Scala中的=>符号可以看做是创建函数实例的语法糖。例如:A => TA,B => T表示一个函数的输入参数类型是“A”,“A,B”,返回值类型是T。请看下面这个实例:

scala> val f: Int => String = myInt => "The value of myInt is: " + myInt.toString()
f: Int => String = <function1>

scala> println(f(3))
The value of myInt is: 3

上面例子定义函数f:输入参数是整数类型,返回值是字符串。

另外,() => T表示函数输入参数为空,而A => Unit则表示函数没有返回值。

PredefineTest.scala

 

Scala代码   收藏代码
  1. object PredefineTest{  
  2.   def main(args: Array[String]):Unit = {  
  3.     val c : Char = 97.asInstanceOf[Char]  
  4.     "hello".asInstanceOf[String]  
  5.     1.asInstanceOf[Long]  
  6.     val it: Seq[String] = List("a""b")  
  7.     it.asInstanceOf[List[String]]  
  8.   
  9.     "hello".isInstanceOf[String]  
  10.   
  11.     classOf[String]  
  12.   }  
  13. }  
 

 

  使用scalac -Xprint:cleanup PredefineTest.scala,Scala编译器输出的main方法体内代码的抽象语法树(AST)信息如下:

 

Scala代码   收藏代码
  1. val c: Char = 97.toChar();  
  2. ("hello": java.lang.String);  
  3. 1.toLong();  
  4. val it: Seq = immutable.this.List.apply(scala.this.Predef.wrapRefArray(Array[java.lang.String]{"a""b"}.$asInstanceOf[Array[java.lang.Object]]()));  
  5. it.$asInstanceOf[List]();  
  6. "hello".$isInstanceOf[java.lang.String]();  
  7. {  
  8.   classOf[java.lang.String];  
  9.   ()  
  10. }  
 

 

  使用jd反编译工具查看对应代码如下:

 

Java代码   收藏代码
  1. char c = (char)97;  
  2. "hello";  
  3. 1;  
  4. Seq it = List..MODULE$.apply(Predef..MODULE$.wrapRefArray((Object[])new String[] { "a""b" }));  
  5. ((List)it);  
  6.   
  7. ("hello" instanceof String);  
  8. String.class;  

 

结合上面源码来进行分析

classOf[T]

 

  获取类型T的Class对象

  classOf方法定义在scala.Predef object:

 

Scala代码   收藏代码
  1. object Predef extends LowPriorityImplicits {  
  2.       /** Return the runtime representation of a class type.  This is a stub method.  
  3.        *  The actual implementation is filled in by the compiler.  
  4.        */  
  5.       def classOf[T]: Class[T] = null  
  6.     ...  

  classOf的注释翻译过来的意思是:返回类型的运行时呈现状态。这是一个存根方法。实际的实现是由编译器填补(自动生成)

 

  Predef object是默认导入的,所以classOf方法相当于一个全局方法

 

 

isInstanceOf[T]

 

  判断对象是否为T类型的实例。

 

  isInstanceOf和asInstanceOf 由scala.Any类定义,Scala类层级的根类;其中class scala.AnyRef 继承自Any,是所有应引用类型的基类;trait scala.AnyVal 也继承自Any,是所有基本类型的实现的trait。所以所有对象都自动拥有isInstanceOf和asInstanceOf这两个方法

  特别注意的是 Any 和AnyRef 这两个类属于“编译时类型”(虚拟类型?),不存在于运行时。所以这两者在Scala中都未提供源码,其语义由编译器在编译时构建。

 

  再看一下例子:

Scala代码   收藏代码
  1. scala> 1.isInstanceOf[String]  
  2. res0: false  
  3.   
  4. scala> List(1).isInstanceOf[List[String]]  
  5. res0: true  

 

  由于Scala像Java一样泛型存在类型擦除的原因,List(1).isInstanceOf[List[String]]及相当于List(1).isInstanceOf[List[_]], List(1) 是List的实例.

 

asInstanceOf[T]

 

  将对象类型强制转换为T类型。

  还是由于泛型存在类型擦除的原因,1.asInstanceOf[String]在运行时会抛出ClassCastException异常,而List(1).asInstanceOf[List[String]]将不会。

  在scala 讨论组里有人问道这样一个问题

 

”I expect "new AnyRef().isInstanceOf[AnyVal]" to be false, but I get true instead“
scala> new AnyRef().isInstanceOf[AnyVal]
res0: Boolean = true

 

  大家有兴趣看以看看后面的解答,不过试了scala 2.9, 这种用法 已经被编译器禁止了:

 

scala> new AnyRef().isInstanceOf[AnyVal]
<console>:8: error: type AnyVal cannot be used in a type pattern or isInstanceOf test
new AnyRef().isInstanceOf[AnyVal]

 

  还有,值得提一下的一个小细节就是,通过观察编译输出的AST,  知道对于在基本类型如Int等的对象上调用asInstanceOf[T], Scala会将其转换为调用相应的toT方法, 如 1.asInstanceOf[Char], 就会转换为 97.toChar, 其中toChar 定义在 scala.Int:

 

Scala代码   收藏代码
  1. final class Int extends AnyVal {  
  2.   ...  
  3.   def toChar: Char = sys.error("stub")  
  4.   ...  
  5. }  

 

  而后, Scala编译器会进一步将其编译成与“(char)97”相同的字节码。

 

结论

  总而言之,我们把classOf[T]看成Java里的T.class, obj.isInstanceOf[T]看成 obj instanceof T, obj.asInstanceOf[T]看成(T)obj就对了。scala为我们提供了语法糖,但也免不了类型擦除问题的影响。




Scala中使用List

方法不应该有副作用是函数风格编程的一个很重要的理念。方法唯一的效果应该是计算并返回值。用这种方式工作的好处就是方法之间很少纠缠在一起,因此就更加可靠和可重用。另一个好处(静态类型语言里)是传入传出方法的所有东西都被类型检查器检查,因此逻辑错误会更有可能把自己表现为类型错误。把这个函数式编程的哲学应用到对象世界里意味着使对象不可变。

如你所见,Scala数组是一个所有对象都共享相同类型的可变序列。比方说Array[String]仅包含String。尽管实例化之后你无法改变Array的长度,它的元素值却是可变的。因此,Array是可变的对象。

说到共享相同类型的不可变对象序列,Scala的List类才是。和数组一样,List[String]包含的仅仅是String。Scala的List,scala.List,不同于Java的java.util.List,总是不可变的(而Java的List可变)。更通常的说法,Scala的List是设计给函数式风格的编程用的。

创建一个List很简单。代码3.3做了展示:

 
 
  1. val oneTwoThree = List(1, 2, 3) 

代码 3.3 创造和初始化列表

代码3.3中的代码完成了一个新的叫做oneTwoThree的val,并已经用带有整数元素值1,2和3的新List[Int]初始化。 因为List是不可变的,他们表现得有些像Java的String:当你在一个List上调用方法时,似乎这个名字指代的List看上去被改变了,而实际上它只是用新的值创建了一个List并返回。比方说,List有个叫“:::”的方法实现叠加功能。你可以这么用:

 
 
  1. val oneTwo = List(1, 2)  
  2. val threeFour = List(3, 4)  
  3. val oneTwooneTwoThreeFour = oneTwo ::: threeFour  
  4. println(oneTwo + " and " + threeFour + " were not mutated.")  
  5. println("Thus, " + oneTwoThreeFour + " is a new List.")  

如果你执行这个脚本,你会看到:

 
 
  1. List(1, 2) and List(3, 4) were not mutated.  
  2. Thus, List(1, 2, 3, 4) is a new List.  

或许List最常用的操作符是发音为“cons”的‘::’。Cons把一个新元素组合到已有List的最前端,然后返回结果List。例如,若执行这个脚本:

 
 
  1. val twoThree = list(2, 3)  
  2. val oneTwoThree = 1 :: twoThree  
  3. println(oneTwoThree)  

你会看到:

 
 
  1. List(1, 2, 3) 

注意

表达式“1 :: twoThree”中,::是它右操作数,列表twoThree,的方法。你或许会疑惑::方法的关联性上有什么东西搞错了,不过这只是一个简单的需记住的规则:如果一个方法被用作操作符标注,如a * b,那么方法被左操作数调用,就像a.*(b)——除非方法名以冒号结尾。这种情况下,方法被右操作数调用。因此,1 :: twoThree里,::方法被twoThree调用,传入1,像这样:twoThree.::(1)。

5.8节中将描述更多操作符关联性的细节。

由于定义空类的捷径是Nil,所以一种初始化新List的方法是把所有元素用cons操作符串起来,Nil作为最后一个元素。

比方说,下面的脚本将产生与之前那个同样的输出

 
 
  1. “List(1, 2, 3)”:   
  2. val oneTwoThree = 1 :: 2 :: 3 :: Nil  
  3. println(oneTwoThree)  

Scala的List包装了很多有用的方法,表格3.1罗列了其中的一些。列表的全部实力将在第十六章释放。

为什么列表不支持append?

类List没有提供append操作,因为随着列表变长append的耗时将呈线性增长,而使用::做前缀则仅花费常量时间。如果你想通过添加元素来构造列表,你的选择是把它们前缀进去,当你完成之后再调用reverse;或使用ListBuffer,一种提供append操作的可变列表,当你完成之后调用toList。ListBuffer将在22.2节中描述。

 类型List的一些方法和作用

类型List的一些方法和作用

Scala中使用Tuple

另一种有用的容器对象是元组:tuple。与列表一样,元组也是不可变的,但与列表不同,元组可以包含不同类型的元素。而列表应该是List[Int]或List[String]的样子,元组可以同时拥有Int和String。元组很有用,比方说,如果你需要在方法里返回多个对象。Java里你将经常创建一个JavaBean样子的类去装多个返回值,Scala里你可以简单地返回一个元组。而且这么做的确简单:实例化一个装有一些对象的新元组,只要把这些对象放在括号里,并用逗号分隔即可。一旦你已经实例化了一个元组,你可以用点号,下划线和一个基于1的元素索引访问它。代码3.4展示了一个例子:

 
 
  1. val pair = (99, "Luftballons")  
  2. println(pair._1)  
  3. println(pair._2)  

代码 3.4 创造和使用元组

代码3.4的第一行,你创建了元组,它的第一个元素是以99为值的Int,第二个是"luftballons"为值的String。Scala推断元组类型为Tuple2[Int, String],并把它赋给变量pair。第二行,你访问_1字段,从而输出第一个元素,99。第二行的这个“.”与你用来访问字段或调用方法的点没有区别。本例中你正用来访问名叫_1的字段。如果执行这个脚本,你能看到:

 
 
  1. 99  
  2. Luftballons  

元组的实际类型取决于它含有的元素数量和这些元素的类型。因此,(99, "Luftballons")的类型是Tuple2[Int, String]。('u', 'r', 'the', 1, 4, "me")是Tuple6[Char, Char, String, Int, Int, String]。

访问元组的元素

你或许想知道为什么你不能像访问List里的元素那样访问元组的,就像pair(0)。那是因为List的apply方法始终返回同样的类型,但是元组里的或许类型不同。_1可以有一个结果类型,_2是另外一个,诸如此类。这些_N数字是基于1的,而不是基于0的,因为对于拥有静态类型元组的其他语言,如Haskell和ML,从1开始是传统的设定。

本文节选自《Programming in Scala》



猜你喜欢

转载自blog.csdn.net/baibaichenchen/article/details/75132573