8.大数据技术之Scala_十三到十四章:注释及类型参数

十三  注解

注解就是标签。

标签是用来标记某些代码需要特殊处理的。

处理的手段可以在代码运行时操作,也可以在编译期操作。

13.1  什么可以被注解

1)  可以为类,方法,字段局部变量,参数,表达式,类型参数以及各种类型定义添加注解

@Entity class Student

@Test def play() {}

@BeanProperty var username = _

def doSomething(@NotNull message: String) {}

@BeanProperty @Id var username = _

2)  构造器注解,需要在主构造器之前,类名之后,且需要加括号,如果注解有参数,则写在注解括号里

class Student @Inject() (var username: String, var password: String)

3)  为表达式添加注解,在表达式后添加冒号

(map1.get(key): @unchecked) match {...}

4)  泛型添加注解

class Student[@specialized T]

5)  实际类型添加注解

String @cps[Unit]

13.2  注解参数

Java注解可以有带名参数:

@Test(timeout = 100, expected = classOf[IOException])

// 如果参数名为value,则该名称可以直接略去。

@Named("creds") var credentials: Credentials = _  // value参数的值为 “creds”

// 注解不带参数,圆括号可以省去

@Entity class Credentials

Java 注解的参数类型只能是:

数值型的字面量

字符串

类字面量

Java枚举

其他注解

上述类型的数组(但不能是数组的数组)

Scala注解可以是任何类型,但只有少数几个Scala注解利用了这个增加的灵活性。

13.3  注解实现

你可以实现自己的注解,但是更多的是使用Scala和Java提供的注解。

注解必须扩展Annotation特质:

class unchecked extends annotation.Annotation

13.4  针对Java的注解

1)  Java修饰符:对于那些不是很常用的Java特性,Scala使用注解,而不是修饰符关键字。

@volatile var done = false  // JVM中将成为volatile的字段

@transient var recentLookups = new HashMap[String, String]  // 在JVM中将成为transient字段,该字段不会被序列化。

@strictfp def calculate(x: Double) = ...

@native def win32RegKeys(root: Int, path: String): Array[String]

2)  标记接口:Scala用注解@cloneable和@remote 而不是 Cloneable和Java.rmi.Remote“标记接口”来标记可被克隆的对象和远程的对象。

@cloneable class Employee

3)  受检异常:和Scala不同,Java编译器会跟踪受检异常。如果你从Java代码中调用Scala的方法,其签名应包含那些可能被抛出的受检异常。用@throws注解来生成正确的签名。

class Book {

@throws (classOf[IOException]) def read(filename: String) { ... }

...

}

Java版本的方法签名:

void read(String fileName) throws IOException

// 如果没有@throws注解,Java代码将不能捕获该异常

try {//Java代码

book.read("war-and-peace.txt");

} catch (IOException ex) {

...

}

即:Java编译期需要在编译时就知道read方法可以抛IOException异常,否则Java会拒绝捕获该异常。

13.5  由于优化的注解

尾递归的优化

啥玩是尾递归?

尾递归:

def story(): Unit = {从前有座山,山上有座庙,庙里有个老和尚,一天老和尚对小和尚讲故事:story()}

尖叫提示:进入下一个函数不再需要上一个函数的环境了,得出结果以后直接返回。

非尾递归:

def story(): Unit =  {从前有座山,山上有座庙,庙里有个老和尚,一天老和尚对小和尚讲故事:story(),小和尚听了,找了块豆腐撞死了}

尖叫提示:下一个函数结束以后此函数还有后续,所以必须保存本身的环境以供处理返回值。

递归调用有时候能被转化成循环,这样能节约栈空间:

object Util {

def sum(xs: Seq[Int]): BigInt = {

 if (xs.isEmpty) 0 else xs.head + sum(xs.tail)

}

...

}

上面的sum方法无法被优化,因为计算过程中最后一步是加法,而不是递归调用。调整后的代码:

def sum2(xs: Seq[Int], partial: BigInt): BigInt = {

if (xs.isEmpty) partial else sum2(xs.tail, xs.head + partial)

}

Scala编译器会自动对sum2应用“尾递归”优化。如果你调用sum(1 to 1000000) 将会发生一个栈溢出错误。不过sum2(1 to 1000000, 0) 将会得到正确的结果。

尽管Scala编译器会尝试使用尾递归优化,但有时候某些不太明显的原因会造成它无法这样做。如果你想编译器无法进行优化时报错,则应该给你的方法加上@tailrec注解。

尖叫提示:对于消除递归,一个更加通用的机制叫做“蹦床”。蹦床的实现会将执行一个循环,不停的调用函数。每个函数都返回下一个将被调用的函数。尾递归在这里是一个特例,每个函数都返回它自己。Scala有一个名为TailCalls的工具对象,帮助我们轻松实现蹦床:

import scala.util.control.TailCalls._

def evenLength(xs: Seq[Int]): TailRec[Boolean] = {

  if(xs.isEmpty) done(true) else tailcall(oddLength(xs.tail))

}

 

def oddLength(xs: Seq[Int]): TailRec[Boolean] = {

  if(xs.isEmpty) done(false) else tailcall(evenLength(xs.tail))

}

 

// 获得TailRec对象获取最终结果,可以用result方法

evenLength(1 to 1000000).result

十四  类型参数(先了解即可)

14.1  泛类型

类和特质都可以带类型参数,用方括号来定义类型参数,可以用类型参数来定义变量、方法参数和返回值。带有一个或多个类型参数的类是泛型的。如下p1,如果实例化时没有指定泛型类型,则scala会自动根据构造参数的类型自动推断泛型的具体类型。

class Pair[T, S](val first: T, val second: S) {

  override def toString = "(" + first + "," + second + ")"

}

//从构造参数推断类型

val p1 = new Pair(42, "String")

//设置类型

val p2 = new Pair[Any, Any](42, "String")

14.2  泛型函数

函数或方法也可以有类型(泛型)参数。

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

    def getMiddle[T](strings: Array[T]) = strings.foreach(println)
    // 从参数类型来推断类型
    println(getMiddle(Array("Bob", "had", "a", "little", "brother")).getClass.getTypeName)

    println("================")

    //指定类型,并保存为具体的函数。
    val f = getMiddle[String] _
    
    println("================")

    println(f(Array("Bob", "had", "a", "little", "brother")))
  }
}

 

14.3  类型变量限定

在Java泛型里不表示某个泛型是另外一个泛型的子类型可以使用extends关键字,而在scala中使用符号“<:”,这种形式称之为泛型的上界

class Pair1[T <: Comparable[T]](val first: T, val second: T) {

  def smaller = if (first.compareTo(second) < 0) first else second

}

 

object Main1 extends App{

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

    val p = new Pair1("Fred", "Brooks")

    println(p.smaller)

  }

}

在Java泛型里表示某个泛型是另外一个泛型的父类型,使用super关键字,而在scala中,使用符号“>:”,这种形式称之为泛型的下界

class Pair2[T](val first: T, val second: T) {

  def replaceFirst[R >: T](newFirst: R) = new Pair2[R](newFirst, second)

  override def toString = "(" + first + "," + second + ")"

}

 

object Main2 extends App{

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

    val p = new Pair2("Nick", "Alice")

    println(p)

    println(p.replaceFirst("Joke"))

    println(p)

  }

}

在Java中,T同时是A和B的子类型,称之为多界,形式如:<T extends A & B>。

在Scala中,对上界和下界不能有多个,但是可以使用混合类型,如:[T <: A with B]。

在Java中,不支持下界的多界形式。如:<T super A &B>这是不支持的。

在Scala中,对复合类型依然可以使用下界,如:[T >: A with B]。

14.5  视图界定

在Scala中,如果你想标记某一个泛型可以隐式的转换为另一个泛型,可以使用:[T <% Comparable[T]],由于Scala的Int类型没有实现Comparable接口,所以我们需要将Int类型隐式的转换为RichInt类型,比如:

class Pair3[T <% Comparable[T]](val first: T, val second: T) {

  def smaller = if (first.compareTo(second) < 0) first else second

  override def toString = "(" + first + "," + second + ")"

}

 

object Main3 extends App {

  val p = new Pair3(4, 2)

  println(p.smaller)

}

14.6  上下文界定

视图界定 T <% V要求必须存在一个从T到V的隐式转换。上下文界定的形式为T:M,其中M是另一个泛型类,它要求必须存在一个类型为M[T]的隐式值。

下面类定义要求必须存在一个类型为Ordering[T]的隐式值,当你使用了一个使用了隐式值得方法时,传入该隐式参数。

 

class Pair4[T: Ordering](val first: T, val second: T) {

  def smaller(implicit ord: Ordering[T]) = {

    println(ord)

    if (ord.compare(first, second) < 0) first else second

  }

 

  override def toString = "(" + first + "," + second + ")"

}

 

object Main4 extends App{

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

    val p4 = new Pair4(1, 2)

    println(p4.smaller)

  }

}

 

object ContextBoundsDemo {
  implicit val personComparetor = new Ordering[Person4]{
    override def compare(x: Person4, y: Person4): Int = x.age - y.age
  }

  def main(args: Array[String]): Unit = {
    val p1 = new Person4("marry",30)
    val p2 = new Person4("smith",35)

    println(new CompareComm4(p1, p2).geatter)
    println(new CompareComm5(p1, p2).geatter)
    println(new CompareComm6(p1, p2).geatter)

  }

}

class Person4(val name: String, val age: Int) {
  override def toString: String = this.name + "\t" + this.age
}

//1.接受T类型的对象
class CompareComm4[T: Ordering](obj1: T, obj2: T)(implicit comparetor: Ordering[T]) {
  def geatter = if (comparetor.compare(obj1, obj2) > 0) obj1 else obj2
}

//2.将隐式参数放到方法内
class CompareComm5[T: Ordering](o1: T, o2: T) {
  def geatter = {
    def f1(implicit cmpter: Ordering[T]) = cmpter.compare(o1, o2)

    if (f1 > 0) o1 else o2
  }

  def lowwer = {
    def f1(implicit cmptor: Ordering[T]) = cmptor.compare(o1, o2)
    if (f1 > 0) o2 else o1
  }

}

//3. 使用implicitly语法糖,建议使用
class CompareComm6[T:Ordering](o1:T,o2:T){
  def geatter = {
    val comparetor = implicitly[Ordering[T]]
    println(comparetor.hashCode())
    if(comparetor.compare(o1,o2) > 0) o1 else o2
  }
}

 

 

14.7  Manifest上下文界定

Manifest是scala2.8引入的一个特质,用于编译器在运行时也能获取泛型类型的信息。在JVM上,泛型参数类型T在运行时是被“擦拭”掉的,编译器把T当作Object来对待,所以T的具体信息是无法得到的;为了使得在运行时得到T的信息,scala需要额外通过Manifest来存储T的信息,并作为参数用在方法的运行时上下文。

def test[T] (x:T, m:Manifest[T]) { ... }

有了Manifest[T]这个记录T类型信息的参数m,在运行时就可以根据m来更准确的判断T了。但如果每个方法都这么写,让方法的调用者要额外传入m参数,非常不友好,且对方法的设计是一道伤疤。好在scala中有隐式转换、隐式参数的功能,在这个地方可以用隐式参数来减轻调用者的麻烦。

def foo[T](x: List[T])(implicit m: Manifest[T]) = {

  println(m)

  if (m <:< manifest[String])

    println("Hey, this list is full of strings")

  else

    println("Non-stringy list")

}

 

foo(List("one", "two"))

foo(List(1, 2))

foo(List("one", 2))

隐式参数m是由编译器根据上下文自动传入的,比如上面是编译器根据 "one","two" 推断出 T 的类型是 String,从而隐式的传入了一个Manifest[String]类型的对象参数,使得运行时可以根据这个参数做更多的事情。

不过上面的foo 方法定义使用隐式参数的方式,仍显得啰嗦,于是scala里又引入了“上下文绑定”,

def foo[T](x: List[T]) (implicit m: Manifest[T])

可以简化为:

def foo[T:Manifest] (x: List[T])

在引入Manifest的时候,还引入了一个更弱一点的ClassManifest,所谓的弱是指类型信息不如Manifest那么完整,主要针对高阶类型的情况

scala在2.10里却用TypeTag替代了Manifest,用ClassTag替代了ClassManifest,原因是在路径依赖类型中,Manifest存在问题:

scala> class Foo{class Bar}

defined class Foo

 

scala> def m(f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar]) = ev

warning: there were 2 deprecation warnings; re-run with -deprecation for details

m: (f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar])Manifest[f.Bar]

 

scala> val f1 = new Foo;val b1 = new f1.Bar

f1: Foo = Foo@681e731c

b1: f1.Bar = Foo$Bar@271768ab

 

scala> val f2 = new Foo;val b2 = new f2.Bar

f2: Foo = Foo@3e50039c

b2: f2.Bar = Foo$Bar@771d16b9

 

scala> val ev1 = m(f1)(b1)

warning: there were 2 deprecation warnings; re-run with -deprecation for details

ev1: Manifest[f1.Bar] = Foo@681e731c.type#Foo$Bar

 

scala> val ev2 = m(f2)(b2)

warning: there were 2 deprecation warnings; re-run with -deprecation for details

ev2: Manifest[f2.Bar] = Foo@3e50039c.type#Foo$Bar

 

scala> ev1 == ev2 // they should be different, thus the result is wrong

res28: Boolean = true

了解之后,我们总结一下,TypeTag到底有啥用呢?看下面的例子:

请留意:

=:=,意思为:type equality

<:< ,意思为:subtype relation

类型判断不要用 == 或 !=

class Animal{}

class Dog extends Animal{}

 

object MainFoo extends App{

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

    val list1 = List(1, 2, 3)

    val list2 = List("1", "2", "3")

    val list3 = List("1", "2", 3)

 

    def test1(x: List[Any]) = {

      x match {

        case list: List[Int] => "Int list"

        case list: List[String] => "String list"

        case list: List[Any] => "Any list"

      }

    }

    println(test1(list1))

    println(test1(list2))

    println(test1(list3))

 

    import scala.reflect.runtime.universe._

    def test2[A : TypeTag](x: List[A]) = typeOf[A] match {

      case t if t =:= typeOf[String] => "String List"

      case t if t <:< typeOf[Animal] => "Dog List"

      case t if t =:= typeOf[Int] => "Int List"

    }

 

    println(test2(List("string")))

    println(test2(List(new Dog)))

    println(test2(List(1, 2)))

  }

}

14.8  多重界定

不能同时有多个上界或下界,变通的方式是使用复合类型

T <: A with B

T >: A with B

可以同时有上界和下界,如

T >: A <: B

这种情况下界必须写在前边,上界写在后边,位置不能反。同时A要符合B的子类型,A与B不能是两个无关的类型。

可以同时有多个view bounds

T <% A <% B

这种情况要求必须同时存在 T=>A的隐式转换,和T=>B的隐式转换。

class A{}

class B{}

implicit def string2A(s:String) = new A

implicit def string2B(s:String) = new B

def foo2[ T <% A <% B](x:T)  = println("foo2 OK")

foo2("test")

可以同时有多个上下文界定

T : A : B

这种情况要求必须同时存在C[T]类型的隐式值,和D[T]类型的隐式值。

class C[T];

class D[T];

implicit val c = new C[Int]

implicit val d = new D[Int]

def foo3[ T : C : D ](i:T) = println("foo3 OK")

foo3(2)

14.9  类型约束

类型约束,提供了限定类型的另一种方式,一共有3中关系声明:

T =:= U意思为:T类型是否等于U类型

T <:< U意思为:T类型是否为U或U的子类型

T <%< U意思为:T类型是否被隐式(视图)转换为U

如果想使用上面的约束,需要添加“隐式类型证明参数”比如:

class Pair5[T] (val first: T, val second: T)(implicit ev: T <:< Comparable[T]){}

使用举例:

import java.io.File

 

class Pair6[T](val first: T, val second: T) {

  def smaller(implicit ev: T <:< Ordered[T]) = {

    if(first < second) first else second

  }

}

 

object Main6 extends App{

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

    //构造Pair6[File]时,注意此时是不会报错的

    val p6 = new Pair6[File](new File(""), new File(""))

    //这就报错了

    p6.smaller

  }

}

14.10  协变,逆变和不变

术语:

英文

中文

示例

Variance

型变

Function[-T, +R]

Nonvariant

不变

Array[A]

Covariant

协变

Supplier[+A]

Contravariant

逆变

Consumer[-A]

Immutable

不可变

String

Mutable

可变

StringBuilder

其中,Mutable常常意味着Nonvariant,但是Noncovariant与Mutable分别表示两个不同的范畴。

即:可变的,一般意味着“不可型变”,但是“不可协变”和可变的,分别表示两个不同范畴。

型变(Variance)拥有三种基本形态:协变(Covariant), 逆变(Contravariant), 不变(Nonconviant),可以形式化地描述为:

一般地,假设类型C[T]持有类型参数T;给定两个类型A和B,如果满足A <: B,则C[A]与 C[B]之间存在三种关系:

如果C[A] <: C[B],那么C是协变的(Covariant);

如果C[A] :> C[B],那么C是逆变的(Contravariant);

否则,C是不变的(Nonvariant)。

Scala的类型参数使用+标识“协变”,-标识“逆变”,而不带任何标识的表示“不变”(Nonvariable):

trait C[+A]   // C is covariant

trait C[-A]   // C is contravariant

trait C[A]    // C is nonvariant

如何判断一个类型是否有型变能力:

一般地,“不可变的”(Immutable)类型意味着“型变”(Variant),而“可变的”(Mutable)意味着“不变”(Nonvariant)。

其中,对于不可变的(Immutable)类型C[T]

如果它是一个生产者,其类型参数应该是协变的,即C[+T];

如果它是一个消费者,其类型参数应该是逆变的,即C[-T]。

 

package UpperBoundsDemo01

object Demo {
  def main(args: Array[String]): Unit = {
    val t33:Temp3[Sub] = new Temp3[Sub]("hello")
//    val t33:Temp3[Sub] = new Temp3[Super]("hello") //No
//    val t34:Temp3[Super] = new Temp3[Sub]("hello") // No
    val t4:Temp4[Super] = new Temp4[Sub]("hello") // +A
    val t5:Temp5[Sub] = new Temp5[Sub]("hello")
    val t6:Temp5[Sub] = new Temp5[Super]("hello")
  }
}

//父类,支持协变
class Super{}

//Sub是Super的子类
class Sub extends Super

//不变
class Temp3[A](title:String){
  override def toString: String = title
}


//协变
class Temp4[+A](title:String){
  override def toString: String = title
}

//逆变
class Temp5[-A](title:String){
  override def toString: String = title
}

 

14.11  上界(Upper Bounds)介绍和使用.

在 Java 泛型里表示某个类型是 A 类型的子类型,使用 extends 关键字,这种形式叫 upper

bounds(上限或上界),语法如下:

<T extendsA>

//或用通配符的形式:

<? extendsA>

scala 中上界

在 scala 里表示某个类型是 A 类型的子类型,也称上界或上限,使用 <: 关键字,语法如下:

[T <: A]

//或用通配符:

[_ <:A]十五  隐式转换和隐式参数

package UpperBoundsDemo01

import java.lang

object UpperBoundsDemo01 {
  def main(args: Array[String]): Unit = {
    val compareInt = new ComparableInt(10, 40)

    //第一个用法
    val cc1 = new CommonCompare(Integer.valueOf(10),Integer.valueOf(40))
    println(cc1.greater)

    //第二个用法
    val cc2 = new CommonCompare(java.lang.Float.valueOf(1.1f),java.lang.Float.valueOf(2.1f))
    println(cc2.greater)

    //第三个用法
    val cc3 = new CommonCompare[Float](10.1f, 21.1f)
    println(cc3.greater)


  }
}


class ComparableInt(n1: Int, n2: Int) {
  //返回较大的值
  def greater = if (n1 > n2) n1 else n2
}

class CommonCompare[T <: Comparable[T]](obj1: T, obj2: T) {
  def greater = if (obj1.compareTo(obj2) > 0) obj1 else obj2
}

 

14.12  下界(Upper Bounds)介绍和使用.

Java 中下界

在 Java 泛型里表示某个类型是 A 类型的父类型,使用 super 关键字

<T superA>

//或用通配符的形式:

<? superA>

scala 中下界

在 scala 的下界或下限,使用 >: 关键字,语法如下:

[T >: A]

//或用通配符:

[_ >:A]

package UpperBoundsDemo01

object LowerBoundsDemo01 {
  def main(args: Array[String]): Unit = {
    //满足下界的约束
    biophony(Seq(new Earth)).map(_.sound())
    biophony(Seq(new Animal)).map(_.sound())
    biophony(Seq(new Bird)).map(_.sound())
  }

  def biophony[T >: Animal](things:Seq[T]) = things
}

class Earth{
  def sound()={
    println("hello")
  }
}

class Animal extends Earth{
  override def sound(): Unit = println("animal sound")
}

class Bird extends Animal{
  override def sound(): Unit = println("bird sounds")
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值