Go VS Java:一位资深程序员对两种语言的解读

易娃
• 阅读 1967

导读:对于软件开发的编程语言,其实没有万能灵药。

本文作者详细介绍了他使用Java和Go这两种编程语言,一个是传统语言,一个是新兴语言的工作方式。

Go VS Java:一位资深程序员对两种语言的解读

实话说,我很喜欢Java这门语言。近几年来,我在公司里积累了大量关于EJB2,DB2与Oracle等后端开发的专业知识。

现在我转向到基于自然语言处理的开发方向,如Spring Boot、Redis、RabbitMQ、Open NLP还有UIMA等技术。

因此说来,我选择的语言是Java,这个语言一直有旺盛的生命力,写起程序来也很有意思。

开始测试Go!

在2017年初,我接到了一个很有意思的项目。这个项目围绕着自动化系统来监控农作物和花花草草等植物们。

原先的开发团队源代码部署在三个不一样的系统中:Windows,MacOS和ARM,他们使用Go语言做为网关。

坦白讲,当时我对Go语言一点儿也不熟悉,随着项目的发展,我的工作内容包括对新技术的学习以及产品实施。项目现有代码库结构复杂,我面临的挑战比较大。使用Go写的针对于物联网平台支持程序,要在三个异构系统上部署、测试与运维。这个系统使用单例模式编写,系统耦合严重,模块之间相互杂揉,可以说是一处错误,多处崩溃。我用Java的开发风格重新构建了网关的新版本,但后让我弄的比原来更丑陋、混乱了。

我后来升职为一家公司任技术总监。

我试着不再使用Java的方式来开发Go了,尽量更多的使用Go的语言特性来做,全面拥抱该语言。

如此下来我才真正发现,Go的确是一个创新且全面的编程语言,我的团队开始将它用它来开发各种各样的项目。

不容置疑的是,与任何编程语言一样,Go有优点也有不少缺点,我这个人不太会撒谎,有时候我还蛮想念Java的。

依照以往我的编程经验,软件开发方面没有灵丹妙药,编程语言就是典型。以下我来讲一下心得体会,使用一门传统语言和创新语言新手是怎么干活的。

Go与Java的相似之处

Go语言与Java都从属于C语言家族,既然是远房亲戚,它们有着相似的语法。所以,Java开发者阅读Go写的代码并不会太难,反之亦然。

Go语句在语句末尾并不使用;分号结束。不用担心,除了极少数情况,我阅读Go写的代码反倒感觉更清晰易读。

Go与Java都使用我最喜欢的功能之一,那就是垃圾收集器(GC),以防止内存泄漏。

C/C++程序员都知道,自己在写程序时要注意避免内存泄漏,垃圾回收机制让内存管理自动化,从而简化程序员在代码中处理类似的功能。

Go语言的GC工具的表现很优秀,它的处理时间在1.8版本中不到1ms即可完成。

在Go语言中,其GC的设置参数并不多,有一个唯一的GC变量用来设置初始时垃圾回收目标的百分比。在Java中,有4个不同的垃圾收集器,每一个都要有大量的设置工作。

Java和Go都被视为跨平台语言,但是Java需要Java虚拟机(JVM)来解析编译后的代码,而Go可以简单地将代码编译为给定平台的二进制文件。

Java与Go相比,Go比Java依赖度更低,因为Go每次为一个平台编译代码时都要创建一个二进制文件。从测试和DevOps角度来看,分别编译不同平台的二进制文件非常的费时,有时候使用GO组件时,跨平台的Go编译在一些情况下并不起作用。但是,使用Java则可以在有JVM的任何地言使用一个Jar。

Go占用内存很小,而且并不需要安装和管理虚拟机的关联依赖,以及复杂的注意事项。

接下来谈一下反射功能。Java的反射功能很强大,而Go的反射更偏复杂。Java是一种纯面向对象语言,所有内容都被视为对象。

在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语言结构具有字段的功能,而不是方法。

再来说一下两个语言的辅助功能。

Java具有Private、Protected以及Public这3个修饰符,用来对数据、方法和对象提供不同范围的存取。

Go语言中,有着和Java类似的语句,它们是exported/unexported修饰符。在Go中如果没有修饰符,以大写字母开头的所有内容都能够导出,并且在其它软件包中可见。unexported,以小写变量或函数仅在当前包中可见。

Go与Java大方面的差异

Go语言并不是一种面向对象(OOP)语言。它没有像Java中的继承,没有通过继承这一特性实现系统的多态性。

Go语言没有对象,只有结构体。但它可以通过提供接口实现一些面向对象的模式。同样道理,可以将将结构彼此嵌入,但是嵌入式结构无法访问宿主结构的数据和方法。

Go中使用组合,而不使用继承来组合一些所需的行为和数据。

Go是一种命令式语言,而Java则是一种声明式语言。

Go中没有像依赖式注入那样的东西,在Go中必须将所有内容明确的包装在一块。因此,在Go中编程末尽量少用一些魔术方法,一切都是可见的。

Go程序员要了解Go代码如何使用内存、文件系统以及其它关联资源的全部机制。

从另一方面,Java需要开发人员更多的关注自定义开发的业务逻辑部分,诸如如何创建、过滤、更改和存储数据。

从系统基础架构和数据库管理系统而言,所有这些都是通过配置和通过Spring Boot等通用框架来完成。

人们总是矛盾心理,我们对基础架构的部分感到枯燥乏味,因此这部分功能交给框架。这会给我们带来方便,但对于程序员来说,控制权在框架,也限制人们对整个流程优化的能力。

再讨论一下两个语言的变量定义,以及顺序。在Java中定义变量类似于以下:

String name;

在Go语言,这样来写:

name string

我在第一次使用Go时,没有;号的代码让我有些困惑,就像写文章没写句号一样,感觉没有写完。

使用Go语言的优势

Go有着简单且且优雅的并发能力。Go语言有着强大的并发模型,称之为“通信顺序过程”或CSP。在Go中使用n-to-m嗅探器,该嗅探器允许n个系统线程中发生m个并发执行。

Go VS Java:一位资深程序员对两种语言的解读

可以利用Go语言的关键字:go(与该语言的名字相同)启动例程。比如下面的字符串:

go doMyWork()

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

在Go中进程之间的通信可以通过共享内存(并不推荐)和共享通道来完成。它允许开发者使用GoMaxProcs环境变量定义一个多内核的健壮且流畅有并发系统。

Go提供了一种特殊模式-race(竞赛)来运行二进制文件,并同时检查运行情况。通过此机制来证明软件是并发安全的

run-race myapp.go

以上命令格式,即以竞赛检测模式运行一个应用程序。

我个人很喜欢Go提供的即开即用功能(golang.org/dl),它提供一个很好的例子,如sync同步功能(golang.org/pkg/sync)包。

比如针对实现Singleton模式实现,可以如下代码表示:

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(golang.org/pkg/sync/atomic)提供了并发安全转换与数学运算。

实际上,Go提供了并发代码所需要的全部。

Duck typing

“如果你说话像鸭子,走路像鸭子,那它一定就是只鸭子”。这句话在Go的世界里是正确的。

在Go语言中,不需要定义某种接口,直接实现结构即可。在Java中,对象比较显式声明并实现了接口。

探查器。Go提供的性能分析工具,使用非常方便、快捷和容易。Go中的事件探查器有助于显示程序中的内存分配和CPU使用情况,还可以在可视化图形中加以说明,这对优化应用程序特别有用。

Java可以从Java VisualVM这些探查器中来开始,但它们不像Go的探查器那样简单,而且这些工具的功效取决于JVM,其获取的统计信息和垃圾收集与JVM的具体工作有关。

C 与Go

Go中允许对C语言进行简单且强大的集成。开发者可以在Go项目中编写有C语言代码的平台相关应用。从底层来讲,CGo可以让开发人员创始调用C代码的Go程序包。为了排除/包含指定平台的C代码段,如各种构建器选项,这些代码段可以实现应用程序的多平台化。

函数作为参数使用

Go函数可以用作变量,传递给另一个函数或用作结构的字段。Go语言的这种多功能性的确让人耳目一新。

而Java是从1.8开始结合了lambda的使用,但它并不是真正的函数,而是一个单功能的对象。它的这一特性就是想实现Go中使用函数的行为,而Go在一开始就

存在于语言特性中。

明晰的代码风格。在Go语言社区中,有着众多热情和能力强大的支持者,社区中有大量对Go的使用示例和操作的最佳实践和方法。

其地址为https://golang.org/doc/effective_go.html

在Go的函数中可以返回多个参数,这是一个非常有用和优秀的方式。如下代码:

package main
importt "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除了接口外,没有多态特性。Go中没有多态性,这表示如果在同一个程序包中,有两个函数具有不同的参数,但是含义相同,这时必须给它们修改为不同的名称。请看如下之代码:

func makeWorkIntt(number int){
  fmt.Printf("Work done number %d",number)
}
func makeWorkStr(title string){
  fmt.Printf("Work done title %s",title)
}

其实这些方法都做同样的事情,只是方法名称不同,而且代码比较丑陋。

此外,在Go中没有继承的多态性。如果嵌入到结构,则嵌入式结构只知道自己的方法,而不会知道宿主结构的方法。这对于一些有其它语言经验开发者有一定的挑战性,他们在之前使用的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语言创建时,必须针对不同的类型重复使用自己的或生成代码。

没有注释

在Go中可以用代码生成部分注释,但是不幸的是,在运行时注释根本不能替换,这是有原因的——Go不是声明性的,且代码中不应包含任何魔术。

我喜欢在Java中使用注释,它们让代码更加优雅、简单、简约。这些注释可以生成大量文件,在做HTTP结点提供某些元数据时非常有用。在GO语言中,必须手动或直接制作Swagger文件,或作为结点功能提供特殊注释。Java中的注释是一种魔术方式,人们通常不必关心它们的工作方式。

再来谈一下Go中的依赖管理。在本文之前我曾写过一篇GO依赖项管理的文章。Go依赖管理环境在相当长一段时间中存在一些麻烦。最初,除了“Gopgk”外没有任何依赖项管理,后来又发布“Vendor”后被“vgo”取代。现在可以手动和使用go命令(如go get命令)来修改go.mod文件描述符来处理。

不幸的是,这种修改会引发依赖关系不稳定。

Go没有开箱即用的源镜像依赖关系管理机制,而Java有着诸如Maven和Gradle之类著名的依赖管理声明类工具。它们也可能用来构建,部署和处理其它集成用途。

我们在实际开发中使用Makefile、Docker-composer和bash脚本自定义构建所需的依赖关系管理,会让持续交付和集成的过程、稳定性变复杂。

在Go中,引用软件包的名称要包含托管的GitHub域名。例如:

import “github.com/pkg/errors”

这块实在有点奇怪,而且非常不方便,如果不更改整个项目代码库的导入,就无法用自己的实现替换其它人的代码。

而在Java中,导入通常以公司名字开头。像这样:

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

当然,我想在GO中的这些依赖管理问题都只是暂时的,将来会以最有效的方式解决。

总结

Go语言中最有趣的一块是,它遵循一种代码命名准则,称为代码可读性方法的心理学。使用Go语言,可以使用单独的方法来构建清晰且可维护的代码,虽然看起来像是一些单词组合在一起,但实际上清晰可读。

一些使用Go进行Web开发的公司,它们将项目展示给我,代码体现了Go的快速,强大且易于理解,非常适合小型服务和并发处理。而对大型,复杂的系统,比如功能更复杂的服务以及微服务系统,Java暂时保持在世界顶级编程语言的地位。

尽管Java SE已经成为Oracle的收费商品,而Go则是技术社区的孩子。已经产生多个品牌的JVM,而Go的工具集则是相同的。

可以确定的是,不同的任务需要不同的工具,语言也是如此。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
00_设计模式之语言选择
设计模式之语言选择设计模式简介背景设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。设计模式(Designpattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这