开发者学堂课程【Scala 核心编程-基础:包对象的介绍和底层机制】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/609/detail/8958
包对象的介绍和底层机制
内容介绍
一、包对象
二、底层机制
三、总结
一、包对象
1、基本介绍
包对象是与包相关的,可以看作是一个对象。在包里可以写类,trait 接口,object,但是不能包含函数或方法或变量的定义,这是 java 虚拟机的限制,java 虚拟机在编译时认为函数方法或变量应该归属于一个对象。为了弥补这点不足,scala 提供了包对象的概念来解决这个问题。
2、包对象的应用案例
要解决的问题是:给包增加函数或者方法变量。解决方法:增加一个 package object scala,那么在这里面所写的变量或者方法就可以被它的同包使用。
(1)、以 scala 包为例,在这个包中可以写类,person,class,object等,但是在包中直接写方法或者定义变量就会报错。比如直接在scala 包中写 Val name=”king”就会报错,因为在 java 虚拟机中一个变量不能够属于一个包。同样,直接写 def sayHi(),也是错误的。
通过使用包对象的技术来解决不能在包中直接写方法或者定义变量这个问题。
(2)、package object scala 表示创建一个包对象 scala,它是 com.atguigu.scala 这个包对应的包对象。
(3)、每个包都可以有一个包对象,而且只能有一个。
(4)、包对象的名称要和子包的名称保持一致,如果名称不一致会导致包对应不上,不能使用。
(5)、在包对象中可以定义变量,方法。
(6)、在包对象中定义的变量和方法就可以在对应的包中使用。
package object scala{
Val name=”king”
def sayHi()
:
unit={
p
rintln(“package object scala sayHi~”)
}
}
//书写以上代码,格式化后没有报错。
//在 Package scala 包中使用这个包对象。
Package scala{
//包 com.atguigu.scala,因为 Test100 在 scala 包中,所以就可以调用包对象。
object Test100 {
//表示在 com.atguigu.scala 创建 object Test
def main(args: Array[string]): unit = {
//直接使用包对象中的名字,不需要任何过渡。
printLn(“使用 name =”+
name
)
这里调用包对象的方法出现了错误,猜想可能是出现了同名,把 def sayHi 改为 def sayHiv,但是仍然不能使用,猜想可能是 object 中涉及到引用的问题。所以改为在 class user 中使用包对象中的方法。User 是属于 scala 包的,代码如下:
class
user
def
testuser
():unit={
p
rintln(“name= ”+name)
再次调用 sayHiv 方法还是出现错误,为了找出错误,新建一个 scala class,命名为 TestPackageObject,把提前写好的代码拷贝进去。代码如下:
package com.atguigu{
//每个包都可以有一个包对象。需要在父包(com.atguigu)中定义它,且名称与子包一样。
package object scala{
var name = "jack"
def sayOk():unit ={
printin("package object sayOk!")
}
}
//包
package scala{
class Test {
def
te
st():unit ={
//这里的 name 就是包对象 scala 中声明的 name
printIn(name)
sayok()
//这个 sayok 就是包对象 scala 中声明的 sayok。
}
}
object Testobj {
def main(args:Array[String]): unit={
valt = new Test()
t.test()
//因为 Testobje 和 scala 这个包对象在同一包,因此也可以使用。println("name=" + name)
Sayok()
}
}}}
//这段代码没有报错。在这段代码中有包对象 scala,包对象中定义了名字和方法。scala 包中有个类 test,在这个类中成功的使用到了包对象的名字和 sayok,而在 object Testobj 中也可以成功调用 sayok()。
将两段代码做对比:在 TestPackageObject 的代码中,建了一个包对象,包对象中有名字和方法。package scala 是它的包,包中有一个 test 类,在 test 类中成功调用了包对象的名字和 sayok ,同样在 Testobj 中也成功调用了 name 和 sayok ,这段代码和之前的代码没有什么区别。
//修改相关代码找出错误。首先注销无关的代码,然后运行剩下的代码。
Package
com
.atguigu
package object scala{
Val name=”king”
def sayHi():unit={
println(“package object scala sayHi~”)
}
}
Package scala{
//包 com.atguigu.scala
class user
def testuser ():unit={
println(“name= ”+name)
object Test100 {
//表示在 com.atguigu.scala 创建object Test
def main(args: Array[string]): unit = {
println
(“uuuuuuu~~~~”)
}
}
运行报错,信息提示:包已经在别的地方定义过了。所以注销掉 TestPackageObject 中的代码和 manager。再次运行报错,信息提示,包已经被定义过了。原因是在 com\atguigu\temp\下写了一个 saclapackage,删除 saclapackage 下的 packageobject。再次运行该代码,没有报错。
//在代码中调用包的名字和 sayhi。
package object scala{
Val name=”king”
def sayHi():unit={
println(“package object scala sayHi~”)
}
}
Package scala{
//包 com.atguigu.scala
class Person {
//表示在 com.atguigu.scala 下创建类 Person
val name = "Nick"
def play (message:string) : unit ={
println(this.name+””+message)
}
}
class user
def testuser ():unit={
println(“name= ”+name)
object Test100 {
//表示在 com.atguigu.scala 创建object Test
def main(args: Array[string]): unit = {
println(“
name
=”+
name
)
sayhiv
()
}
}
}
把之前注销的代码打开,运行所有代码。
表示成功。
总结:包对象中定义的变量和方法,可以在同包中使用。不管是在 object 中,还是在类中都可以使用。
二、底层机制
1、当创建包对象后,在该包下生成 public final class package 和 public final class package$ 两个类。
在反编译工具中查看包对象生成了什么,打开 scala 文件,package 和 package$ 文件是新生成的,这两个文件就对应了包对象,即 package object scala。在 package.class 中有 sayhiv 方法和 name 方法,package$.class 中又生成了一个 package$,包含了 sayhiv 方法,name 方法和 package 的构造函数。
通过下图解释是如何调用的。
包对象对应了两个文件,分别是 package 和 package$ 。在 package 中有 sayhiv 方法和对名字进行获取的方法,在 package$ 中有 name 方法,并且是一个独的方法,如果在代码中改为 name=“yy“是不支持的。原因是在包对象中设置了 Val name=”king”,如果将它修改为 Var name=”king”, name=“yy“就是可以支持的。重新编译 scala ,就意味着底层会重新生成一个对 name 的设置方法。要完成对调用的理解就必须要查看以下代码的文件。
println(“name=”+name)
name
=”yy”
s
a
yhiv()
查看 test100 文件,发现调用的是 test100$。查看 test100$,一个 object 会生成两个类,test100$ 又封装了一次 main。test100$ 调用了 package$,查看 package$,这就相当于调用了 package$ 中静态对象里的 name 和 sayhiv 。
从底层机制看,package 文件暂时没有用到。当程序员输入这段代码时,println(“name=”+name),name=”yy”,sayhiv(),这个 name 直接使用了 package$ 文件中的 name 。准确而言,name 是使用了 package$ 中 MODULE$ 静态实例中的 name 方法;sayhiv()使用的是 package$ 中 MODULE$ 中的 sayhiv方法。
2、用 java 模拟包的底层实现机制
public class Temp100 {
public static void main(String[] args){
System.out.printin(package$$ MODULES name());
Package$$.MODULES.sayOk():
}}
final class package$$
{
public static final package$$ MODULES;
private final String name ="ok";
static {MODULES=new package$$():}
public void sayOk()
{System.out.printin("scala pkg obj sayok"):}
public String name() { return this.name;}
}
三、总结
1、需要包对象的原因:包可以包含类、对象和特质 trait,但不能包含函数/方法或变量的定义。为了弥补这一点不足,scala 提供了包对象的概念来解决这个问题。
2、包对象的应用实例:
Package
com
.atguigu
package object scala{
Val name=”king”
def sayHi():unit={
println(“package object scala sayHi~”)
}
}
Package scala{
//包 com.atguigu.scala
class Person {
//表示在 com.atguigu.scala 下创建类 Person
val name = "Nick"
def play (message:string) : unit ={
println(this.name+””+message)
}
}
class user
def testuser ():unit={
println(“name= ”+name)
object Test100 {
//表示在 com.atguigu.scala 创建 object Test
def main(args: Array[string]): unit = {
println(“name=”+name)
name
=”yy”
syhiv()
}
}
}
3、分析了包对象底层的实现机制:一个包对象对应生成 public final class package 和 public final class package$ 两个类。从源码的角度分析,package 暂时没有用到。原因是反编译工具可能不够准确,或者是与设计者的最初意图有所不同,因为 package 和 package$ 最大的差别是 package 提供的是静态方法,静态方法不需要 new 实例就可以直接调用方法。但是出于种种原因没有调用这个静态方法,而是在调用包对象的方法时直接使用了 package$ 中静态的对象来调用方法。
如图所示,一个包对象会生成两个类 package 和 package$,类的名字是统一的。如果有两个包对象,名字就会发生冲突,所以一个包只能有一个包对象。而且从理论而言,一个包对象就足够满足需求了。
调用机制如下图:println(“name=”+name)等价于 package$.MODULE$.name();sayhiv()等价于package$.MODULE$ sayhiv()。
如图所示:说明了包使用包对象的变量或者方法的原理。