Go与Java开发语言的比较

Go与Java开发语言的比较

Go和Java都是C族语言,这意味着它们共享相似的语言语法。因此,Java开发人员经常发现阅读Go代码相当容易,反之亦然。

技术开发 编程 技术框架 技术发展

 

Go与Java开发语言的比较

Go和Java都是C族语言,这意味着它们共享相似的语言语法。因此,Java开发人员经常发现阅读Go代码相当容易,反之亦然。

我可以坦白地说,我很喜欢Java。我建立了我的 专业知识,软件开发 与过去15年的后端技术,如EJB2,DB2和甲骨文最近几年在工作作为前辈,团队领导软件开发工作的 螺旋侦察。多年来,我转向基于自然语言处理的机器人,包括Spring Boot,Redis,RabbitMQ,Open NLP,IBM Watson和UIMA。多年来,我选择的语言是Java,并且一直有效,甚至有时很有趣。

测试开始

在2017年初,我接手了一个非常有趣的项目,该项目围绕编程自动化系统来监控和种植水培植物。该软件的原始代码库包含用于三个不同系统(Windows,MacOS和ARM)的Go网关。

Go完全不熟悉,随着项目的进展,我的工作迅速发展为既学习又可以同时实施这种新语言的组合。挑战是艰巨的,特别是由于现有代码库的结构复杂。用CGo编写的针对特定平台的部件为三种不同的操作系统支持程序,实质上意味着对三种不同的系统进行部署,测试和运行维护。此外,该代码以单例设计模式编写,从而使系统严重相互依赖,常常无法预测,并且很难理解。最后,我选择使用更具Java风格的方法设计新版本的网关,并且最终也变得相当丑陋和令人困惑。

当我搬到Spiral Scout时,我目前是我们最大的美国客户之一的技术主管,在使用Go进行开发时,我停止尝试利用Java操舵室。相反,我决定通过以尽可能多的Go语言开发并对其进行使用来拥抱该语言。我发现它是一种创新且全面的语言,我们的团队仍然每天将其用于各种项目。

但是,与任何编程语言一样,Go也有其弱点和缺点,而且我不会撒谎,有时候我真的很想念Java。

如果我的编程经验对我有什么启发,那就是软件开发方面没有灵丹妙药。我将在下面详细说明根据我的经验,一种传统语言和一名新手如何工作。

Go与Java:相似之处

Go和Java都是C族语言,这意味着它们共享相似的语言语法。因此,Java开发人员经常发现阅读Go代码相当容易,反之亦然。Go在语句的末尾不使用分号(';'),但在少数情况下除外。对我来说,Go的行分隔语句更清晰,更易读。

Go和Java都使用了我最喜欢的功能之一,即垃圾收集器(GC),以帮助防止内存泄漏。与C ++不同,C系列程序员必须担心内存泄漏,垃圾回收器是使内存管理自动化并因此简化工作的那些功能之一。

Go的GC并未使用弱世代假设,但它的表现仍然非常出色,并且停靠世界(STW)的时间非常短。在1.5版中,STW降低得更多,并且基本上保持不变,而在1.8版中,它降至不到1ms。

Go的GC仅具有一些设置,即唯一的GOGC变量可设置初始垃圾回收目标百分比。在Java中,您有4个不同的垃圾收集器,每个垃圾收集器都有大量的设置。

尽管Java和Go都被认为是跨平台的,但是Java需要Java虚拟机(JVM)来解释编译后的代码。Go可以简单地将代码编译为任何给定平台的二进制文件。实际上,我认为Java与Go相比,与Java的依赖程度更小,因为Go每次为单个平台编译代码时都需要创建一个二进制文件。从测试和DevOps的角度来看,分别编译不同平台的二进制文件非常耗时,并且跨平台的Go编译在某些情况下不起作用,尤其是当我们使用CGo部件时。同时,使用Java,您可以在拥有JVM的任何地方使用相同的jar。Go需要更少的RAM,并且不需要任何其他有关安装和管理虚拟机的注意事项。

反射。与Java反射方便,流行且常用的Java不同,Go的反射似乎更复杂且不那么明显。Java是一种面向对象的语言,因此除原语之外的所有内容都被视为对象。如果要使用反射,则可以为对象创建一个类,并从该类中获取所需的信息,如下所示:

Class cls = obj.getClass();

Constructor constructor = cls.getConstructor();

Method[] methods = cls.getDeclaredFields();

这使您可以访问构造函数,方法和属性,以便可以调用或设置它们。

在Go中,没有类的概念,并且结构仅包含已声明的字段。因此,我们需要“反射”包以提供所需的信息:

类型

type Foo struct {

A int `tag1:"First Tag" tag2:"Second Tag"`

B string

}


f := Foo{A: 10, B: "Salutations"}


fType := reflect.TypeOf(f)


switch t.Kind(fType)

case reflect.Struct:

for i := 0; i < t.NumField(); i++ {

f := t.Field(i)

…..

}

}

我意识到这不是一个大问题,但是由于Go中没有用于结构的构造函数,因此结果是许多原始类型必须分别处理以及必须考虑到指针。在Go中,我们还可以通过指针或值传递某些东西。Go结构可以具有作为字段的功能,而不是方法。所有这些使Go中的反射更加复杂且不可用。

辅助功能。Java具有私有,保护和公共修饰符,以便为范围提供对数据,方法和对象的不同访问。Go具有与Java中的public和private修饰符相似的导出/未导出标识符。没有修饰符。以大写字母开头的所有内容都将导出,并且在其他软件包中将可见。未导出-小写变量或函数仅在当前包中可见。Go与Java:大差异

Golang不是OOP语言。Go的核心是缺少Java的继承,因为它没有通过继承实现传统的多态性。实际上,它没有对象,只有结构。它可以通过提供接口并实现结构的接口来模拟一些面向对象的模式。同样,您可以将结构彼此嵌入,但是嵌入式结构无法访问宿主结构的数据和方法。Go使用组合而不是继承来组合一些所需的行为和数据。

Go是一种命令式语言,而Java则是一种声明式语言。在Go中,我们不需要依赖注入。相反,我们必须将所有内容明确地包装在一起。这就是为什么在Go中推荐的编程方法是使用尽可能少的魔术。对于外部代码审阅者来说,一切应该都是显而易见的,程序员应该了解Go代码如何使用内存,文件系统和其他资源的所有机制。

另一方面,Java需要开发人员更多地关注于自定义编写程序的业务逻辑部分,以确定如何创建,过滤,更改和存储数据。就系统基础架构和数据库管理而言,所有这些都是通过配置和通过Spring Boot等通用框架进行注释来完成的。我们对重复基础架构部分的枯燥乏味感到担忧,而将其留给框架。这很方便,但也会颠倒控制,并限制了我们优化整个过程的能力。

变量定义的顺序。在Java中,您可以编写如下内容:

String name;

。。。但是在Go中,您会这样写:

name string

当我第一次开始使用Go时,这显然令人困惑。

使用Go的优点

简单优雅的并发。 Go具有强大的并发模型,称为“通信顺序过程”或CSP。Go使用n-to-m探查器,该探查器允许n个系统线程中发生m个并发执行。可以使用该语言的关键字(与该语言的名称相同)以非常基本的方式启动并发例程。例如,编码人员可以编写以下字符串:

go doMyWork()

。。。并且函数doMyWork()将同时开始执行。

进程之间的通信可以通过共享内存(不推荐)和通道来完成。它允许使用与我们使用GOMAXPROCS环境变量定义的进程一样多的核心的非常健壮和流畅的并行性。

默认情况下,进程数等于核心数。

Go提供了一种特殊模式来运行二进制文件并检查运行竞赛情况。这样,您可以测试并证明您的软件是并发安全的。

go run -race myapp.go

这将以运行竞赛检测模式运行该应用程序。

我非常感谢Go 提供了非常有用的基本功能,即开即用 (https://golang.org/dl/)。一个很好的例子是同步“ sync”  https://golang.org/pkg/sync/ 并发包。对于“一次”组类型单例实现,您可以编写:

package singleton


import (

    "sync"

)


type singleton struct {

}


var instance *singleton

var once sync.Once


func GetInstance() *singleton {

    once.Do(func() {

        instance = &singleton{}

    })

    return instance

}

包同步还为并发映射实现,互斥锁,条件变量和等待组提供了一种结构。软件包“ atomic”  https://golang.org/pkg/sync/atomic/ 还允许并发安全转换和数学运算-实质上是制作并发就绪代码所需的一切。

指针。使用指针,Go可以更好地控制如何分配内存,垃圾收集器有效负载以及其他Java无法实现的有趣的性能调整。与Java相比,Go感觉像是一种更底层的语言,并且支持更轻松,更快速的性能优化。

鸭打字。 “如果它走路像鸭子,却像鸭子一样嘎嘎叫,那它一定是鸭子。” 这句话在Go中是正确的:无需定义某种结构实现给定的接口。如果该结构在给定的接口中具有具有相同签名的方法,则它将实现它。这非常有帮助。作为库的客户端,您可以定义外部库结构所需的任何接口。在Java中,对象必须显式声明其实现了接口。

探查器。 Go的性能分析工具使分析性能问题变得方便,快捷和容易。Go中的事件探查器有助于揭示程序所有部分的内存分配和CPU使用情况,并可以在可视化图形中进行说明,从而使执行优化性能的操作非常容易。Java还具有许多从Java VisualVM开始的探查器,但是它们不像Go探查器那么简单。相反,它们的功效取决于JVM的工作,因此从它们获得的统计信息与垃圾收集器的工作相关。

CGO。Go允许对C进行非常简单而强大的集成,因此您可以在Go项目中编写带有C代码片段的平台相关应用程序。本质上,CGo使开发人员能够创建调用C代码的Go程序包。为了排除/包括给定平台的C代码段,有各种构建器选项,这些代码段允许应用程序的多平台实现。

用作参数。Go函数可以用作变量,传递给另一个函数或用作结构的字段。这种多功能性令人耳目一新。从Java的1.8版本开始,它结合了lambda的使用,它们不是真正的函数,而是单功能对象。尽管这有助于实现类似于在Go中使用函数的行为,但这种想法从一开始就存在于Go中。

明确的代码风格准则。 Go背后的社区充满了支持和热情。那里有大量关于使用示例和解释进行操作的最佳方法的信息, 网址为https://golang.org/doc/effective_go.html

函数可以返回许多参数。 这也是非常有用和不错的。

package main

import "fmt"


func returnMany() (int, string, error) {

return 1, "example", nil

}


func main() {

i, s, err := returnMany()

fmt.Printf("Returned %s %s %v", i, s, err)

}

使用Go的缺点

除接口外,没有多态性。 Go中没有即席多态性,这意味着如果在同一个程序包中有两个函数具有不同的参数但含义相同,则必须给它们指定不同的名称。例如,使用此代码。。。

func makeWorkInt(number int) {

   fmt.Printf(“Work done number %d”, number)

}


func makeWorkStr(title string) {

   fmt.Printf(“Work done title %s”, title)

}


…您最终会采用许多方法来做相同的事情,但所有方法都具有不同且丑陋的名称。

此外,通过继承没有多态性。如果嵌入结构,则嵌入式结构仅知道其自己的方法,而不会知道“宿主”结构的方法。对于像我这样的开发人员来说,这尤其具有挑战性,他们在主要使用OOP语言(最基本的概念之一是继承)后过渡到Go。

但是,随着时间的流逝,我开始意识到这种处理多态性的方法只是另一种思维方式,而且是有道理的,因为最终,这种组合更加可靠,明显,并且运行时间是可变的。

错误处理。 完全由您决定返回什么错误以及如何返回错误,因此作为开发人员,您需要每次都返回错误并相应地传递错误。毫不奇怪,错误可能被隐藏,这可能是真正的痛苦。记住要检查错误并把它们传递出去,这很烦人而且不安全。

当然,您可以使用短绒棉签来检查隐藏的错误,但这只是补丁而不是真正的解决方案。在Java中,异常要方便得多。如果它是RuntimeException,则甚至不必将其添加到函数的签名中。

public void causeNullPointerException() {

   throw new NullPointerException("demo");

}


……...


try {

   causeNullPointerException() ;

}

catch(NullPointerException e) {

   System.out.println("Caught inside fun().");

   throw e; // rethrowing the exception

没有泛型。虽然很方便,但泛型增加了复杂性,并且当Go类型的创建者键入系统和运行时时,它们被认为代价高昂。在Go中进行构建时,您实际上必须针对不同的类型重复使用自己或使用代码生成。

没有注释。 虽然可以用代码生成部分替换编译时注释,但是不幸的是,运行时注释根本不能替换。这是有道理的,因为Go不是声明性的,并且代码中不应包含任何魔术。

我喜欢在Java中使用注释,因为它们使代码更加优雅,简单和简约。

在为稍后提供大量文件的HTTP服务器端点提供某些方面或元数据时,它们将非常有用。在Go中,当前必须手动或直接制作swagger文件,或为端点功能提供特殊注释。每次更改API时,这都是很痛苦的事情。但是,Java中的注释是一种魔术,人们通常不关心它们的工作方式。

Go中的依赖管理。 我之前曾写过关于 使用vgo和dep在Go中进行依赖管理的文章。这是这些问题的重要摘要,描述了我在Go上遇到的最大问题,老实说,我并不是唯一一个有这种想法的人。Go依赖管理环境在相当长的一段时间里看起来很坎rock。最初,除了“ Gopgk”之外没有任何依赖项管理,但最终发布的“供应商”实验后来被“ vgo”取代,然后又被1.10版“ go mod”取代。如今,可以手动以及使用各种Go命令(例如“ go get”)更改go.mod文件描述符;不幸的是,这使依赖关系变得不稳定。

也没有开箱即用的依赖关系管理机制提供的源镜像。有点可惜,尤其是因为Java具有诸如Maven和Gradle之类的出色的声明式工具来进行依赖关系管理,它们也可用于构建,部署和处理其他CD / CI用途。我们实际上必须使用Makefile,docker-composes和bash脚本自定义构建所需的依赖性管理,这只会使CD / CI的过程和稳定性变得复杂。

Go微服务通常始于容器,并在本地,虚拟Linux机器或不同平台上同时终止。有时,它会使CD / CI在开发和生产周期中的工作比所需的更为复杂。

软件包的名称包括托管域名。 例如:

import “github.com/pkg/errors”

这真的很奇怪,尤其不方便,因为如果不更改整个项目代码库的导入,就无法用自己的实现替换某人的实现。

在Java中,导入通常以公司名称开头,例如:

import by.spirascout.public.examples.simple.Helper;

区别在于,在Go中,go get将转到by.spirascout.public并尝试获取资源。在Java中,程序包和域名不必关联。

我确实希望依赖管理的所有问题都是暂时的,将来会以最有效的方式解决。

最后的想法

Go最有趣的方面之一是它遵循的代码命名准则。它们基于代码可读性方法的心理学(请参阅本文)。

使用Go,您可以使用单独的方法编写非常清晰且可维护的代码,尽管它是由许多单词组成的语言,但仍然清晰可见。

在 Golang Web开发公司的 工作清楚地向我展示了Go快速,强大且易于理解,这使其非常适合小型服务和并发处理。对于大型,复杂的系统,功能更复杂的服务以及单服务器系统,Java将暂时保持其在世界顶级编程语言中的地位。

尽管 Java SE 已成为付费对象,但Go确实是编程社区的孩子。实际上,有很多不同品牌的JWM,但是Go的工具集是相同的。

技术开发 编程 技术框架 技术发展