快学Scala(第二版)-07-包和引入

在本章中,你将会了解到Scala中的包和引入语句时如何工作的。

7.1包

Scala的包和Java的包的目的是相同的:管理大型程序中的名称。举例来说,Map这个名称可以同时出现在scala.collection.immutable和scala.collection.mutable包中不会冲突。
要访问它们中的任何一个,你可以完全限定名称scala.collection.immutable.Map,也可以使用引入语句来提供一个更短小的别名——见7.7节。

要添加条目到包中,你可以将其包含在包语句中,比如:

package com{
	package scalabook{
		package impatient{
			class Employee
			....
		}
	}
}

这样依赖类名Employee就可以在任意位置以com.scala.impatient.Employee访问到了。

与对象或类的定义不同,同一个包可以定义在多个文件中
前面这段代码可能出现在文件Employ.scala中,而另一个名为Manager.scala的文件可能会包含:

package com{
	package scalabook{
		package impatient{
			class Manager
			....
		}
	}
}

源文件的目录和包之间没有强制关联关系。你不需要将Employee.scala和Manager.scala放在com/scala/impatient目录中。

换个角度讲,你也可以在同一个文件中为多个包贡献内容。Employee.scala可以包含:

package com{
	package scalabook{
		package impatient{
			class Employee
			....
		}
	}
}

package net{
	package java{
		package count{
			class Manager
			....
		}
	}
}

7.2 作用域规则

Scala的包和其他作用域一样支持嵌套。你可以访问上层作用域中的名称。例如:

package com{
	package horstman{
		object Utils{
			def percentof(value:Double,rate:Double)=value*rate/100
			....
		}
		
		package impatient{
			class Employee{
				...
				def giveRaise(rate:scala.Double){
					salary+=Utils.percentof(salary,rate)
				}
			}
		}
	}
}

注意Utils.percentof修饰符。Utils类定义于父包。所有父包中的内容都在作用域内,因此没必要使用com.horstman.Utils.percentof。(当然这么用没问题)

但是,这里有一个bug。

假如有如下代码:

package com{
    package horstman{
        package impatient{
            class Manager{
                val subordinantes = new collection.mutable.ArrayBuffer[Employee]
            }
        }
    }
}

这里我们利用到一个特性,那就是Scala包总是被引入。因此,collection包实际上指向的是scala.collection。

现在假如有人加入了如下包,其可能位于另一个文件中:

package com{
    package horstman{
        package collection{
            ...
        }
    }
}

这下Manager类将不再能通过编译。编译器尝试在com.horstman.collection中查找mutable成员未果。Manager类的本意是要使用顶级的scala包中collection包,而不是随便什么存在于可访问作用域中的子包。

在Java中,这个问题不会发生,因为包名总是绝对的,其从包层级的最顶端开始。但是在Scala中,包名是相对的,就像内部类的名称一样。内部类通常不会遇到这个问题,因为所有代码都在同一个文件中,有负责该文件的人直接控制。但是包不一样,任何人都可以在任何时候向任何包添加内容。

解决方法之一是使用绝对包名,以_root_开始,例如:

val subordinantes = new \_scala_.collection.mutable.ArrayBuffer[Employee]

另一种做法是使用“串联式”包语句,在下一节介绍。

大多数程序员都是用完整的包名,只是不加_root_前缀。只要大家都避免使用scala、java、com、net等名称来命名嵌套的包,这样就是安全的。

7.3 串联式包语句

包语句可以包含一个“串”,或者说路径区段,例如:

package com.horstman.impatient{
    //com和com.horstman的成员在这里不可见
    package people{
        class person
        ...
    }
}

这样的包语句限定了可见的成员,上层的成员在这里不可见。现在com.horstman.collection包不再能够以collection访问到了。

7.4 文件顶部标记法

除了我们目前看到的嵌套标记法,你还可以在文件顶部使用package语句,不带花括号。例如:

package com.horstman.impatient
package people

class person
...

这等同于

package com.horstman.impatient{
    package people{
        class person
        ....
        //直到文件末尾
    }
}

当文件中所有代码属于同一个包时(这也是通常的情形),这是更好的做法。
注意,在上面的示例中,文件的所有内容都属于com.horstman.impatient.people,但com.horstman.impatient包的内容是可见的,可以直接引用。

7.5包对象

包可以包含类、对象和特质,但不能包含函数或变量的定义。很不幸,这是Java虚拟机的局限性。把工具函数或常量添加到包而不是某个Utils对象,这是更常见的做法。包对象的出现正是为了解决这个局限性。

每个包都可以有一个包对象。你需要在父包中定义它,且名称于子包一样。例如:

package com.horstman.impatient

package object people{
    val defaultname="Chris"
}

package people{
    class person{
        var name=defaultname   //从包对象拿到的常量
    }
    ....
}

注意defaultname无需加限定词,因为它位于同一个包内。在其他地方,这个常量可以用com.horstman.impatient.people.defaultname访问到。

在幕后,包对象被编译成带有静态方法和字段的JVM类,名为package.class,位于相应的包下。对应到本例中,就是com.horstman.impatient.people.package,其中有一个静态字段defaultname。(在JVM中,你可以使用package作为类名)

对源文件使用相同的命名规则是好习惯,可以把包对象放到文件com/horstman/impatient/people/package.scala。这样一来,任何人想要对报增加函数或变量的话,都可以很容易找到对应的包对象。

7.6 包可见性

在Java中,没有声明为public、private或protected的类成员在包含该类的包中中可见。在Scala中,你可以通过修饰符达到同样的效果。以下方法在它自己的包中可见:

package com.horstman.impatient.people

class person{
    private[people] def description =s"A person with name $name"
    ...
}

你可以将空间度延展到上层包:

private[impatient] def description = s"A person with name $name"

7.7 引入

引入语句让你可以使用更短的名称而不是原来较长的名称。写法如下:

import java.awt.color

这样一来,你就可以在代码中写color而不是java.awt.color了。

你可以引入某个包的全部成员:

import java.awt._

这和Java中的通配符*一样。

你也可以引入类或对象的所有成员。

import java.awt.color._
val c1=RED   //color.RED
val c2=decode("#ff0000")  //color.decode

一旦你引入了某个包,就可以用较短的名称访问其子包。例如:

import java.awt._

def handler(evt: event.ActionEvent){   //java.awt.event.ActionEvent
	....
}

event包是java.awt包的成员,因此引入语句把它也带进了作用域。

7.8 任何地方都可以声明引入

在Scala中,import语句可以出现在任何地方,而并不是仅限于文件顶部。import语句的效果一直延伸到包含该语句的块末尾。例如:

class Manager{
	import scala.collection.mutable._
	val subordinates =new ArrayBuffer[employee]
	....
}

这是一个有用的特性,尤其是对于通配引入而言。从多个源引入大量名称总是让人担心。
通过引入放置在需要这些引入的地方,你可以大幅减少可能的名称冲突。

7.9 重命名和隐藏方法

如果你想要引入包中的几个成员变量,可以像这样使用选择器(selector)

import java.awt.{Color, Font}

选取器语法还允许你重命名选到的成员

import java.util.{HashMap=>JavaHashMap}
import scala.collection.mutable._

这样一来,JavaHashMap就是java.util.HashMap,而HashMap则对应scala.collectio.mutable.HashMap。

选取器HashMap=>_将隐藏某个成员而不是重命名它。这仅在你需要引入其他成员时有用:

import java.util.{HashMap=>_, _}
import scala.collection.mutable._

现在,HashMap无二地指向scala.collection.mutable.HashMap,因为java.util.HashMap被隐藏起来了。

7.10 隐式引入

每个Scala程序都隐式地以如下代码开始:

import java.lang._
import scala._
import Predef._

和Java一样,java.lang总是被引入。接下来,scala包也被引入,不过方式有些特殊。不像所有其他引入,这个引入被允许可以覆盖之前地引入。

举例来说,scala.StringBuilder会覆盖java.lang.StringBuilder而不是与之冲突。

最后Predef对象被引入。它包含了常用地类型、隐式转换和工具方法。

由于scala包默认被引入,因此对于那些以scala开头地包,你完全无须写全这个前缀。例如:

collection.mutable.HashMap

上述代码和以下写法一样好:

scala.collection.mutable.HashMap
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值