阅读本文大概需要 9 分钟。
大家好,我是 polarisxu。
在正式工作之前,一直使用 Java,虽然这些年对 Java 的关注变少了,但很显然,Java 用户群体特别大。不过,我也知晓,有不少 Java 用户在学 Go。我尝试写一系列文章,为 Java 开发者讲解 Go 语言。
这是第一篇,从大的层面简单对比下 Go 和 Java,算作是一次漫游。
整体上,Java 和 Go 之间有许多明显而微妙的区别,包括语言层面和运行时层面。我们这里主要在语言层面漫游。
这里的比较,没有贬低哪门语言的意思,旨在客观指出各自的特点。
Go 和 Java 都是 C 系语言,但 Go 更接近 C,包括风格、库等。
与 C/C++ 一样,Go 语言源码会直接编译成目标计算机体系结构的机器语言。而 Java 源码编译成虚拟机语言,即字节码,并由 Java 虚拟机(JVM)进行解释(interpreted)。为了提高性能,字节码通常在运行时被动态编译成机器语言。JVM 本身是为特定的操作系统和硬件体系结构构建的。
而且 Go 是静态编译,一旦编译完成,Go 程序只需要一个操作系统就可以运行。Java 程序在运行之前需要计算机上安装有 JRE(特定版本)。许多 Java 程序可能还需要额外的第三方代码。
所以,虽然 Go 和 Java 都是跨平台的,但实现形式还是很不一样。
这两门语言都支持包含方法和字段的数据结构的概念。在 Go 中,它们被称为 struct(结构体),而在Java中,它们被称为 class(类)。这些结构被收集到称为包(package)的分组中。在这两门语言中,包都可以分层排列(即嵌套包)。
Java 包顶层只包含类型声明。Go 包可以各种声明,如变量、常量、函数以及派生类型声明。
两门语言都通过导入(import)来访问不同包中的代码。在 Java 中,可以选择使用导入的类型(String 或 Java.lang.String)。在 Go 中,所有导入的名称必须始终是限定的(虽然可以本地导入,但不建议使用)。
这方面涉及到的内容不少,无法一一列出。这里提一些:
1)Go 与众不同,变量声明,类型放在后面。语言通常省略分号。
Java:int x, y, z;
Go:var x, y, z int
2)Java 方法只能返回一个值。Go 函数/方法可以返回许多值。
3)Java 方法和字段必须在它们所属的类型内声明。Go 方法是在所属类型之外定义的。Go 支持独立于任何类型的函数和变量。Java 没有真正的静态共享变量;静态字段只是某些类(相对于实例)的字段。Go 支持在可执行映像中分配的真正静态(全局)变量。
4)Java 只允许其他类型(类、枚举和接口)的类型扩展,而 Go 可以基于任何现有类型创建新类型,包括基本类型(如整数和浮点)和其他用户定义的类型。Go 可以支持这些自定义类型中的任何一种。
5)Go 和 Java 接口的工作方式非常不同。在 Java 中,类(或枚举)实现接口时,必须显式指定。在 Go 中,任何类型都可以通过实现接口的方法来实现接口,即隐式实现接口,所谓的鸭子类型。
6)Java 通过 try/catch 处理异常。Go 中是 error,另外有 panic 和 recover。
面向对象的三大特性:封装、继承和多态。
Go 没有继承的概念,认为组合优于继承。不过,在 Go 中,可以通过内嵌来模仿部分类似继承的功能,但本质还是组合。
此外,Go 只在接口层面有多态,没有方法重载。
许多 Java 库(特别是框架,比如Spring),都充分利用了 Java 的注解(Annotation)。注解提供元数据(通常在运行时使用),以修改库提供的行为。Go 没有注解,因此缺少此功能。Go 可以使用代码生成(go generate)获得与注释类似的结果。Go 有一种简单的注解形式,称为 tag,可用于自定义某些库行为,如 JSON 或 XML 格式。
这两门语言都使用 stack 和 heap 来保存数据。栈主要用于函数局部变量,堆用于其他动态创建的数据。在 Java 中,所有对象都在堆上分配。在 Go 中,堆上只分配可在函数生命周期之外使用的数据(通过逃逸分析确认)。在 Java 和 Go 中,堆都是垃圾收集的;堆对象由代码显式分配,但总是由垃圾收集器回收。
Java 没有指向对象的指针的概念,只引用位于堆中的对象。Go 允许访问指向任何数据值的指针(或地址)。在大多数情况下,Go 的指针可以像 Java 引用一样使用。
Go 的垃圾收集实现比 Java 的更简单,通常 Go GC 需要调优的情况较少(没有太多选项可配置)。
Java 有线程(Thread)的概念。而 Go 是 Goroutine,它是由语言提供的。Goroutine 通常被称为轻量级线程。Go 运行时支持使用比 JRE 支持的线程多得多的 Goroutine。
Java 支持同步控制。Go 具有类似的库函数。Go 和 Java 都支持跨 Thread/Goroutine 安全更新的原子值的概念。两者都支持显式锁定库。
Go 提供了通信顺序进程(CSP)的概念,作为 Goroutine 在没有显式同步和锁定的情况下进行交互的主要方式。Goroutine 通过 channel 进行通信,channel 是有效的管道(FIFO 队列),通常与 select 语句相结合使用。
Go 的运行时比 JRE 提供的运行时小得多。没有 JVM 等价物,但两者中都存在类似的组件,如垃圾收集。Go 没有字节码解释器。
Go 拥有大量的标准库。Go 社区提供了更多库。但是 Java 标准库和社区库在功能的广度和深度上都远远超过了当前的 Go 库(毕竟 Java 比 Go 早太多年了,而且生态确实好)。尽管如此,Go 库仍然足够丰富,可以开发许多有用的应用程序,特别是服务端程序。
所有使用过的库都会嵌入到 Go 可执行文件中,即前面提到的静态编译。可执行文件是运行程序所需的一切(而 Java 库在首次使用时动态加载)。这使得 Go 程序二进制文件通常比 Java 二进制文件(单个 “main” 类)大,但当加载 JVM 和所有依赖类时,Java 的总内存占用通常更大。
Java程序是在运行时构造的类的合并,通常来自多个源(供应商)。这使得Java程序非常灵活,特别是当通过网络下载时。Go 程序是在执行之前静态构建的。启动时,可执行映像中的所有代码都可用。这提供了更大的完整性和可预测性,部署特别方便,但牺牲了一些灵活性。这使得 Go 更适合集装箱化部署。
Go 程序通常由 “go builder” 构建,这是一种结合了编译器、依赖项管理器、链接器和可执行构建器等的工具。它包含在标准 Go 安装中。Java 类被单独编译(由 Java 开发工具包(JDK)提供的 javac 工具编译),然后通常被组装成包含相关类的归档文件(JAR/WAR)。程序是从一个或多个存档中加载的。档案的创建,特别是包括任何依赖项,通常由独立于标准 JRE 的程序(如 Maven)完成。
其他方面,比如 Java 和 Go 都是过程式语言,此外,在函数式编程方面,Go 一开始,函数就是一等公民,而 Java 8 才有较好的支持。
这个系列主要参考以下资料:
我是 polarisxu,北大硕士毕业,曾在 360 等知名互联网公司工作,10多年技术研发与架构经验!2012 年接触 Go 语言并创建了 Go 语言中文网!著有《Go语言编程之旅》、开源图书《Go语言标准库》等。
坚持输出技术(包括 Go、Rust 等技术)、职场心得和创业感悟!欢迎关注「polarisxu」一起成长!也欢迎加我微信好友交流:gopherstudio