Scala 剖析01: Object 和 Class

Scala 剖析01: Object 和 Class,第1张

目录

前言

定义一个简单的 Object

定义一个简单的 Class

Predef

Type

类型的导入及别名


前言

 scala 同 java 一样,运行在 JVM 上.其语法的灵活和易用可以理解为都是在 java 语言的基础上封装了丰富的语法糖.那scala 不同的语言特性"翻译"为 java 又是什么样子,Scala 剖析系列将会从零开始进行探索.

本文主要讲述了 scala 中的类和 java 中类的异同点以及一些编程小 tips.

定义一个简单的 Object

使用scala 代码定义一个简单的object. scala 中没有静态的概念,所有静态的东西都放在 object 中.所以 object Test 可以理解一个静态类.看下编译的内容. object Test 代码如下

object Test {
    def main(args: Array[String]): Unit = {
        println("hello world")
    }
}

在字节码反编译的结果上是体现了两个文件,Test.class 和 Test$.class

Test.class 代码如下

public final class Test {
    public static void main(String[] paramArrayOfString) {
        Test$.MODULE$.main(paramArrayOfString);
    }
}

Test$.class 代码如下

public final class Test$ {
    public static Test$ MODULE$;
    public void main(String[] args) {
        scala.Predef$.MODULE$.println("hello world");
    }

    private Test$() {
        MODULE$ = this;
    }
}

可以看到当只定义了 object 不定义 class 时,字节码中同样还是会存在class,只不是在 class 类中没有主体逻辑,调用了对应静态类 Test$的 main 方法.Test 中调用了Test$的静态本类实例.

还能看出,Test$类的代码结构和 Java 设计模式中的单例模式(饿汉式)实现是一模一样的,所以 scala 中的 object 天生就是单例模式.

代码中调用的 println 方法在字节码中编译为调用了Predef$的方法,也就是 object Predef中的代码.

定义一个简单的 Class

如果定义一个简单的 class 不存在静态内容,基本可以推断出不会生成 test$.class 这个文件了.我们编译一下看看.

class Test {
    def main(args: Array[String]): Unit = {
        println("hello world")
    }
}

编译情况跟我们预想的相同.结果只存在一个文件 Test.class.字节码反编译的内容如下

public class Test {
    public void main(String[] args) {
        Predef$.MODULE$.println("hello world");
    }
}

可以看到即便是没有静态内容,在方法实际执行的时候还是会调用 predef 的方法.

Predef

Predef 个人理解对应着 Java 的 lang 包,是强大的基础功能包.该 object 的声明为

object Predef extends LowPriorityImplicits with DeprecatedPredef

其中' LowPriorityImplicits '类提供了在所有Scala编译单元中都有效的隐式值,无需显式限定,但会被对象' Predef '中的高优先级转换部分覆盖,所以是LowPriority. 类中主要定义了 java 中基础类型对其装箱类型的转换和 array 到 wrappedarray 的转换.目的是为了消除一些类型使用上的隐患.

DeprecatedPredef则是对一些已经弃用的隐式转换方法进行了@deprecated注解的修饰,用以对直接调用者给出显示提醒.

除了以上描述的两个类以外,Predef 中对一些基础类型进行了定义.比如 String,Set等

type String = java.lang.String

/** @group aliases */
type Function[-A, +B] = Function1[A, B]

/** @group aliases */
type Map[A, +B] = immutable.Map[A, B]

/** @group aliases */
type Set[A] = immutable.Set[A]

/** @group aliases */
val Map = immutable.Map

/** @group aliases */
val Set = immutable.Set

可以清晰的看到,Scala 中的 String 其实就是 Java 中的 String.所以一些 Java 中的方法都可以直接调用.对于其他的常用数据类型比如 Map 和 Set 等,都对应了 imutable 包中的定义,所以在 scala 中定义数据对象,默认的都是使用 immutable 的.

Type

Predef 中对类型的定义用到了很多 type 关键字.对于初识 scala 的同学可能不太清楚.可以暂时只将它理解为对类型取别名.

比如下面的代码

type String = java.lang.String

我们直接创建 String 类型对象的时候,就是实际创建了java.lang.String的对象.如果我们写

type A = java.lang.String

那创建 A 类型对象的时候就是创建了java.lang.String的对象.在黑窗口中执行如下代码测试

scala> type A = java.lang.String
defined type alias A // 可以直观看到,定义的是类型别名

scala> var a = new A("1")
a: String = 1

Predef 在 scala 中是默认加载的,所以一些基础类型的预定义更方便我们高效编程.

类型的导入及别名

使用 scala 实际编程过程中,经常会遇到 Java 的数据类型和 Scala 本身的类型不好区分的情况.因为 scala 对 java 中所有的数据结构天生就是支持的,这个功能很好,但也时常会让我们在编程时混淆名字相似的类型.

既然 type 关键字可以给类型取别名,通过 Type 关键字对所有 java 类型进行单独定义可以很大程度上缓解这个问题.我们可以写如下的代码

object Test {
    def main(args: Array[String]): Unit = {
        type javaInt = java.lang.Integer // 取别名

        val jInt = new javaInt(1)
        println(jInt.getClass.getSimpleName) // java 的 Integer

        val sInt = 1
        println(sInt.getClass.getSimpleName) // scala 的 int
    }
}

不过需要注意的是 java.lang包下的类类型我们都可以这样 *** 作,但是其他数据类型比如 java.util.ArrayList在进行 type 设置别名时就会报错,即便导入该类型也不行.所以我们只能对运行时预加载的类型进行如上的 *** 作.

除了预加载的类其他类型不能进行别名设定,显得这个功能特别鸡肋.所以 scala 对用户提供了一个统一的别名命名方式

import java.util.{ArrayList => javaList} //别名
import java.lang.{Integer => jInt} //别名

object Test {
    def main(args: Array[String]): Unit = {
        val jList = new javaList[Int]()
    }
}

这样导入时就可以按照我们自己的想法进行类型别名设定了,不管是不是 lang 包下的都支持.需要注意,为了不破坏代码的可读性,在命名时一定要见名知意,别埋坑.

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/721624.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存