知识大全 面向Java开发人员的Scala指南: 包和访问修饰符

Posted

篇首语:白米饭好吃,五谷田难种。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 面向Java开发人员的Scala指南: 包和访问修饰符相关的知识,希望对你有一定的参考价值。

面向Java开发人员的Scala指南: 包和访问修饰符  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!

  在现实生活中 代码一定要引用并打包 在本期(第七期) 面向 Java 开发人员的 Scala 指南 系列中 Ted Neward 介绍了 Scala 的包和访问修饰符功能 纠正了以前的疏忽 然后 他继续探讨了 Scale 中的函数内容 apply 机制

  最近 读者的反馈让我意识到在制作本系列的过程中我遗漏了 Scala 的语言的一个重要方面 Scala 的包和访问修饰符功能 所以在研究该语言的函数性元素 apply 机制前 我将先介绍包和访问修饰符

  打包

  为了有助于隔离代码 使其不会相互冲突 Java&# ; 代码提供了 package 关键词 由此创建了一个词法命名空间 用以声明类 本质上 将类 Foo 放置到名为 tedneward util 包中就将正式类名修改成了 tedneward util Foo 同理 必须按该方法引用类 如果没有 Java 编程人员会很快指出 他们会 import 该包 避免键入正式名的麻烦 的确如此 但这仅意味着根据正式名引用类的工作由编译器和字节码完成 快速浏览一下 javap 的输出 这点就会很明了

   关于本系列

  Ted Neward 将和您一起深入探讨 Scala 编程语言 在这个新的 developerWorks 系列 中 您将深入了解 Sacla 并在实践中看到 Scala 的语言功能 进行比较时 Scala 代码和 Java 代码将放在一起展示 但(您将发现)Scala 中的许多内容与您在 Java 编程中发现的任何内容都没有直接关联 而这正是 Scala 的魅力所在!如果用 Java 代码就能够实现的话 又何必再学习 Scala 呢?

  然而 Java 语言中的包还有几个特殊的要求 一定要在包所作用的类所在的 java 文件的顶端声明包(在将注释应用于包时 这一点会引发很严重的语言问题) 该声明的作用域为整个文件 这意味着两个跨包进行紧密耦合的类一定要在跨文件时分离 这会致使两者间的紧密耦合很容易被忽略

  Scala 在打包方面所采取的方法有些不同 它结合使用了 Java 语言的 declaration 方法和 C# 的 scope(限定作用域)方法 了解了这一点 Java 开发人员就可以使用传统的 Java 方法并将 package 声明放在 scala 文件的顶部 就像普通的 Java 类一样 包声明的作用域为整个文件 就像在 Java 代码中一样 而 Scala 开发人员则可以使用 Scala 的包 (scoping)限定作用域 方法 用大括号限制 package 语句的作用域 如清单 所示

  清单 简化的打包

  package package tedneward package scala package demonstration object App def main(args : Array[String]) : Unit = System out println( Howdy from packaged code! ) args foreach((i) => System out println( Got + i) )

  这个代码有效地声明了类 App 或者更确切的说是一个称为 tedneward scala demonstration App 的单个类 注意 Scala 还允许用点分隔包名 所以清单 中的代码可以更简洁 如清单 所示

  清单 简化了的打包(redux)

  package tedneward scala demonstration object App def main(args : Array[String]) : Unit = System out println( Howdy from packaged code! ) args foreach((i) => System out println( Got + i) )

  用哪一种样式看起来都比较合适 因为它们都编译出一样的代码构造(Scala 将继续编译并和 javac 一样在声明包的子目录中生成 class 文件)

  导入

  与包相对的当然就是 import 了 Scala 使用它将名称放入当前词法名称空间 本系列的读者已经在此前的很多例子中见到过 import 了 但现在我将指出一些让 Java 开发人员大吃一惊的 import 的特性

  首先 import 可以用于客户机 Scala 文件内的任何地方 并非只可以用在文件的顶部 这样就有了作用域的关联性 因此 在清单 中 java math BigInteger 导入的作用域被完全限定到了在 App 对象内部定义的方法 其他地方都不行 如果 mathfun 内的其他类或对象要想使用 java math BigInteger 就需要像 App 一样导入该类 如果 mathfun 的几个类都想使用 java math BigInteger 可以在 App 的定义以外的包级别导入该类 这样在包作用域内的所有类就都导入 BigInteger 了

  清单 导入的作用域

  package package tedneward package scala // package mathfun object App import java math BigInteger def factorial(arg : BigInteger) : BigInteger = if (arg == BigInteger ZERO) BigInteger ONE else arg multiply (factorial (arg subtract BigInteger ONE)) def main(args : Array[String]) : Unit = if (args length > ) System out println( factorial + args( ) + = + factorial(new BigInteger(args( )))) else System out println( factorial = )

  不只如此 Scala 还不区分高层成员和嵌套成员 所以您不仅可以使用 import 将嵌套类型的成员置于词法作用域中 其他任何成员均可 例如 您可以通过导入 java math BigInteger 内的所有名称 使对 ZERO 和 ONE 的限定了作用域的引用缩小为清单 中的名称引用

  清单 静态导入

  package package tedneward package scala // package mathfun object App import java math BigInteger import BigInteger _ def factorial(arg : BigInteger) : BigInteger = if (arg == ZERO) ONE else arg multiply (factorial (arg subtract ONE)) def main(args : Array[String]) : Unit = if (args length > ) System out println( factorial + args( ) + = + factorial(new BigInteger(args( )))) else System out println( factorial = )

  您可以使用下划线(还记得 Scala 中的通配符吧?)有效地告知 Scala 编译器 BigInteger 内的所有成员都需要置入作用域 由于 BigInteger 已经被先前的导入语句导入到作用域中 因此无需显式地使用包名限定类名 实际上 可以将所有这些都结合到一个语句中 因为 import 可以同时导入多个目标 目标间用逗号隔开(如清单 所示)

  清单 批量导入

  package package tedneward package scala // package mathfun object App import java math BigInteger BigInteger _ def factorial(arg : BigInteger) : BigInteger = if (arg == ZERO) ONE else arg multiply (factorial (arg subtract ONE)) def main(args : Array[String]) : Unit = if (args length > ) System out println( factorial + args( ) + = + factorial(new BigInteger(args( )))) else System out println( factorial = )

  这样您可以节省一两行代码 注意这两个导入过程不能结合 先导入 BigInteger 类本身 再导入该类中的各种成员

  也可以使用 import 来引入其他非常量的成员 例如 考虑一下清单 中的数学工具库(或许不一定有什么价值)

  清单 Enron 的记帐代码

  package package tedneward package scala // package mathfun object BizarroMath def bizplus(a : Int b : Int) = a b def bizminus(a : Int b : Int) = a + b def bizmultiply(a : Int b : Int) = a / b def bizdivide(a : Int b : Int) = a * b

  使用这个库会越来越觉得麻烦 因为每请求它的一个成员 都需要键入 BizarroMath 但是 Scala 允许将 BizarroMath 的每一个成员导入最高层的词法空间 因此简直就可以把它们当成全局函数来使用(如清单 所示)

  清单 计算 Enron的开支

  package package tedneward package scala package demonstration object App def main(args : Array[String]) : Unit = import tedneward scala mathfun BizarroMath _ System out println( + = + bizplus( ))

  还有其他的一些构造很有趣 它们允许 Scala 开发人员写出更自然的 bizplus 但是这些内容本文不予讨论(想了解 Scala 潜在的可以用于其他用途的特性的读者可以看一下 Odersky Spoon 和 Venners 所著的 Programming in Scala 中谈到的 Scala implicit 构造)

  访问

  打包(和导入)是 Scala 封装的一部分 和在 Java 代码中一样 在 Scala 中 打包很大一部分在于以选择性方式限定访问特定成员的能力 — 换句话说 在于 Scala 将特定成员标记为 公有(public) private(私有) 或介于两者之间的成员的能力

  Java 语言有四个级别的访问 公有(public) 私有(private) 受保护的(protected )和包级别(它没有任何关键词)访问 Scala

废除了包级别的限制(在某种程度上) 默认使用 公有 指定 私有 表示 只有此作用域可访问

  相反 Scala 定义 protected 的方式与在 Java 代码中不同 Java protected 成员对于子类和在其中定义成员的包来说是可访问的 Scala 中则仅有子类可访问 这意味着 Scala 版本的 protected 限制性要比 Java 版本更严格(虽然按理说更加直观)

  然而 Scala 真正区别于 Java 代码的地方是 Scala 中的访问修饰符可以用包名来 限定 用以表明直到 哪个访问级别才可以访问成员 例如 如果 BizarroMath 包要将成员访问权限授权给同一包中的其他成员(但不包括子类) 可以用清单 中的代码来实现

  清单 Enron 的记帐代码

  package package tedneward package scala // package mathfun object BizarroMath def bizplus(a : Int b : Int) = a b def bizminus(a : Int b : Int) = a + b def bizmultiply(a : Int b : Int) = a / b def bizdivide(a : Int b : Int) = a * b private[mathfun] def bizexp(a : Int b: Int) =

  注意此处的 private[mathfun] 表达 本质上 这里的访问修饰符是说该成员直到 包 mathfun 为止都是私有的 这意味着包 mathfun 的任何成员都有权访问 bizexp 但任何包以外的成员都无权访问它 包括子类

  这一点的强大意义就在于任何包都可以使用 private 或者 protected 声明甚至 (乃至 _root_ 它是根名称空间的别名 因此本质上 private[_root_] 等效于 public 同)进行声明 这使得 Scala 能够为访问规范提供一定程度的灵活性 远远高于 Java 语言所提供的灵活性

  实际上 Scala 提供了一个更高程度的访问规范 对象私有 规范 用 private[this] 表示 它规定只有被同一对象调用的成员可以访问有关成员 其他对象里的成员都不可以 即使对象的类型相同(这弥合了 Java 访问规范系统中的一个缺口 这个缺口除对 Java 编程问题有用外 别无他用 )

  注意访问修饰符必须在某种程度上在 JVM 之上映射 这致使定义中的细枝末节会在从正规 Java 代码中调用或编译时丢失 例如 上面的 BizarroMath 示例(用 private[mathfun] 声明的成员 bizexp)将会生成清单 中的类定义(当用 javap 来查看时)

  Listing Enron 的记帐库 JVM 视图

  

  Compiled from packaging scala public final class tedneward scala mathfun BizarroMath extends java lang Object public static final int $tag(); public static final int bizexp(int int); public static final int bizdivide(int int); public static final int bizmultiply(int int); public static final int bizminus(int int); public static final int bizplus(int int);

  在编译的 BizarroMath 类的第二行很容易看出 bizexp() 方法被赋予了 JVM 级别的 public 访问修饰符 这意味着一旦 Scala 编译器结束访问检查 细微的 private[mathfun] 区别就会丢失 因此 对于那些要从 Java 代码使用的 Scala 代码 我宁愿坚持传统的 private 和 public 的定义(甚至 protected 的定义有时最终映射到 JVM 级别的 public 所有不确定的时候 请对照实际编译的字节码参考一下 javap 以确认其访问级别 )

  应用

  在本系列上一期的文章中( 集合类型 ) 当谈及 Scala 中的数组时(确切地说是 Array[T])我说过 获取数组的第 i 个元素 实际上是 那些名称很有趣的方法中的一种…… 尽管当时是因为我不想深入细节 但不管怎么说事实证明这种说法严格来说 是不对的

  好吧 我承认 我说谎了

  技术上讲 在 Array[T] 类上使用圆括号要比使用 名称有趣的方法 复杂一点 Scala 为特殊的字符序列(即那些有左右括号的序列)保留了一个特殊名称关联 因为它有着特殊的使用意图 做 ……(或按函数来说 将…… 应用 到……)

  换句话说 Scala 有一个特殊的语法(更确切一些 是一个特殊的语法关系)来代替 应用 操作符 () 更精确地说 当用 () 作为方法调用来调用所述对象时 Scala 将称为 apply() 的方法作为调用的方法 例如 一个想充当仿函数(functor)的类(一个充当函数的对象)可以定义一个 apply 方法来提供类似于函数或方法的语义

  清单 使用 Functor!

  class ApplyTest import junit _ Assert _ @Test def simpleApply = class Functor def apply() : String = Doing something without arguments def apply(i : Int) : String = if (i == ) Done else Applying + apply(i ) val f = new Functor assertEquals( Doing something without arguments f() ) assertEquals( Applying Applying Applying Done f( ))

  好奇的读者会想是什么使仿函数不同于匿名函数或闭包呢?事实证明 它们之间的关系相当明显 标准 Scala 库中的 Function 类型(指包含一个参数的函数)在其定义上有一个 apply 方法 快速浏览一些为 Scala 匿名函数生成的 Scala 匿名类 您就会明白生成的类是 Function (或者 Function 或 Function 这要看该函数使用了几个参数)的后代

  这意味着当匿名的或者命名的函数不一定适合期望设计方法时 Scala 开发人员可以创建一个 functor 类 提供给它一些初始化数据 保存在字段中 然后通过 () 执行它 无需任何通用基类(传统的策略模式实现需要这个类)

  清单 使用 Functor!

  class ApplyTest import junit _ Assert _ // @Test def functorStrategy = class GoodAdder def apply(lhs : Int rhs : Int) : Int = lhs + rhs class BadAdder(inflateResults : Int) def apply(lhs : Int rhs : Int) : Int = lhs + rhs * inflateResults val calculator = new GoodAdder assertEquals( calculator( )) val enronAccountant = new BadAdder( ) assertEquals( enronAccountant( ))

  任何提供了被适当赋予了参数的 apply 方法的类 只要这些参数都按数字和类型排列了起来 它们都会在被调用时运行

  结束语

  Scala 的打包 导入和访问修饰符机制提供了传统 Java 编程人员从未享受过的更高级的控制和封装 例如 它们提供了导入一个对象的选择方法的能力 使它们看起来就像全局方法一样 而且还克服了全局方法的传统的缺点 它们使得使用那些方法变得极其简单 尤其是当这些方法提供了诸如本系列早期文章( Scala 控制结构内部揭密 )引入的虚构的 tryWithLogging 函数这样的高级功能时

cha138/Article/program/Java/JSP/201311/19333

相关参考

知识大全 面向Java开发人员的Scala指南: Scala控制结构内部揭密

面向Java开发人员的Scala指南:Scala控制结构内部揭密  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起

知识大全 面向Java开发人员的Scala指南: 面向对象的函数编程

面向Java开发人员的Scala指南:面向对象的函数编程  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下

知识大全 面向Java开发人员的Scala指南: 实现继承

面向Java开发人员的Scala指南:实现继承  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  S

知识大全 面向Java开发人员的Scala指南: 增强Scitter库

面向Java开发人员的Scala指南:增强Scitter库  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一

知识大全 面向Java开发人员的Scala指南: 深入了解Scala并发性 了解 Scala 如何简化并发编

面向Java开发人员的Scala指南:深入了解Scala并发性了解Scala如何简化并发编  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后

知识大全 面向Java开发人员的Scala指南: 关于特征和行为

面向Java开发人员的Scala指南:关于特征和行为  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!

知识大全 面向Java开发人员的Scala指南: 构建计算器,第1 部分

面向Java开发人员的Scala指南:构建计算器,第1部分  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一

知识大全 面向Java开发人员的Scala指南: 构建计算器,第 2 部分

面向Java开发人员的Scala指南:构建计算器,第2部分  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一

知识大全 面向Java开发人员的Scala指南: 深入了解Scala并发性 了解 actor 如何提供新的应

面向Java开发人员的Scala指南:深入了解Scala并发性了解actor如何提供新的应  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后

知识大全 Java开发者的Scala指南: Scala+Twitter=Scitter

Java开发者的Scala指南:Scala+Twitter=Scitter  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我