Golang与Java比较

首先,我想声明一个免责声明。 我不是Go方面的专家。 几周前我开始研究它,因此这里的陈述是第一印象。 在本文的某些主观方面,我可能是错的。 也许以后我会写一些评论。 但是直到那时,如果您是Java程序员,这里就是欢迎您,看看我的感受和经验,同时,如果某些陈述中我有错,也欢迎您评论和纠​​正我。

Golang令人印象深刻

与Java相反,Go被编译为机器代码并直接执行。 与C非常相似。由于这不是VM机器,因此与Java有很大不同。 它是面向对象的,并且在某种程度上同时起作用,因此,它不仅是带有自动垃圾回收的新C语言。 如果我们认为编程语言的世界在一条直线上,那不是C和C ++之间的某个地方。 用Java程序员的眼光来看,有些事物有很大不同,以至于学习它们具有挑战性,并且可能使人们对编程语言结构以及对象,类和所有这些事物的方式有更深的了解……甚至在Java中。

我的意思是,如果您了解Go如何在OO中实现,那么您可能还会理解Java与众不同的一些原因。

简而言之,如果您不耐烦:不要让自己对这种看似奇怪的语言结构感到惊讶。 学习它,即使您没有要在Go中开发的项目,它也会增加您的知识和理解。

GC而非GC

内存管理是编程语言中的关键点。 汇编使您可以做所有事情。 或更确切地说,这需要您完成所有操作。 对于C语言,标准库中有一些支持功能,但是仍然需要您在调用malloc之前释放已分配的所有内存。 自动化内存管理始于C ++,Python,Swift和Java。 Golang也在此类别中。

Python和Swift使用引用计数。 当存在对一个对象的引用时,该对象本身拥有一个计数器,该计数器对指向该对象的引用数进行计数。 没有向后的指针或引用,但是当新引用获取该值并开始引用一个对象时,计数器将增加,而当引用变为null / nil或引用另一个对象时,计数器将下降。 因此,当计数器为零时,就没有对该对象的引用,可以将其丢弃。 这种方法的问题在于,当计数器为正时,对象仍然可能无法到达。 可能存在对象圆相互引用,并且当该圆中的最后一个对象从静态,局部和其他可访问的引用中释放时,该圆开始像漂浮在水中的气泡一样漂浮在内存中:计数器均为正,但对象为无法到达。 Swift教程很好地解释了这种行为以及如何避免这种行为。 但是重点仍然存在:您必须在某种程度上关心内存管理。

如果是Java,则其他JVM语言(包括Python的JVM实现)由JVM管理。 有一个完整的垃圾收集,它不时在一个或多个线程中运行,与工作线程并行,或者有时停止那些(标记为停止运行)标记无法访问的对象,对其进行清除并压缩可能分散的内存。 您只需要担心性能。

Golang也在这一类别中,有一个很小的,很小的例外。 它没有参考。 它具有指针。 差异至关重要。 它可以与外部C代码集成,并且出于性能原因,在运行时没有像引用注册表这样的功能。 执行系统不知道实际的指针。 仍可以分析分配的内存以收集可达性信息,并且仍可以标记和清除未使用的“对象”,但是无法四处移动内存以进行压缩。 从文档中看,这对我来说不是很明显,而且当我了解指针处理时,我正在寻找Golang向导实现压缩的魔力。 我很抱歉学习,他们根本没有。 没有魔术。

Golang有一个垃圾回收器,但这不是Java中的完整GC,没有内存压缩。 不一定是坏事。 它可以很长时间地运行服务器,并且仍然不会使内存碎片化。 一些JVM垃圾收集器还跳过了压缩步骤,以减少清理旧版本时的GC暂停,并且仅作为最后的手段进行压缩。 Go中的最后一个步骤丢失了,在极少数情况下可能会引起一些问题。 学习语言时,您不太可能会遇到问题。

局部变量

局部变量(有时是新版本中的对象)以Java语言存储在堆栈中。 在C,C ++和其他实现了调用堆栈的语言中也是如此。 与本地变量相关的Golang也不例外,除了…

除了可以简单地从函数返回指向局部变量的指针。 这是C语言中的一个致命错误。在Go语言中,编译器认识到分配的“对象”(我将在后面解释为什么使用引号)正在转义该方法,并相应地对其进行分配,以便在函数和指针返回时得以幸存不会指向没有可靠数据的已经废弃的内存位置。

所以这绝对合法:

package main

import (
	"fmt"
)

type Record struct {
	i int
}

func returnLocalVariableAddress() *Record {
	return ℜcord{1}
}

func main() {
	r := returnLocalVariableAddress()
	fmt.Printf("%d", r.i)
}

关闭

此外,您可以在函数内部编写函数,还可以像使用函数语言(Go是一种函数语言)一样返回函数,并且函数周围的局部变量用作闭包中的变量。

package main

import (
	"fmt"
)

func CounterFactory(j int) func() int {
	i := j
	return func() int {
		i++
		return i
	}
}

func main() {
	r := CounterFactory(13)
	fmt.Printf("%d\n", r())
	fmt.Printf("%d\n", r())
	fmt.Printf("%d\n", r())
}

函数返回值

函数不仅可以返回一个值,而且可以返回多个值。 如果使用不当,这似乎是一个不好的做法。 Python做到了。 Perl做到了。 它可能很好用。 它主要用于返回值和“ nil”或错误代码。 这样,将错误编码为返回类型的旧习惯(通常返回-1作为错误代码,并且在C std库调用中存在一些有意义的返回值的情况下返回一些非负值)被更具可读性的东西所代替。

分配侧的多个值不仅是功能。 要交换两个值,您可以编写:

a,b = b,a

面向对象

由于闭包和函数是一等公民,所以Go至少是面向对象JavaScript。 但实际上不仅仅如此。 Go lang具有接口和结构。 但是它们并不是真正的课程。 它们是值类型 。 它们按值传递,无论它们存储在内存中的什么位置,数据都只有纯数据,没有对象标头或类似的东西。 Go中的struct非常类似于C中的它们。它们可以包含字段,但不能彼此扩展,也不能包含方法。 面向对象的方法略有不同。

您可以在定义方法本身时指定方法,而不是将方法填充到类定义中。 结构还可以包含其他结构,如果没有该字段的名称,则可以按其类型引用它,该类型隐式地成为其名称。 或者,您可以仅引用字段或方法,因为它们属于顶级结构。

例如

package main

import (
	"fmt"
)

type A struct {
	a int
}

func (a *A) Printa() {
	fmt.Printf("%d\n", a.a)
}

type B struct {
	A
	n string
}

func main() {
	b := B{}
	b.Printa()
	b.A.a = 5
	fmt.Printf("%d\n", b.a)
}

这几乎是一种继承。

当指定可以在其上调用方法的结构时,可以指定结构本身或指向该结构的指针。 如果将方法应用于结构,则该方法将访问调用方结构的副本(此结构按值传递)。 如果将该方法应用于指向该结构的指针,则该指针将被传递(通过引用类型传递)。 在后一种情况下,该方法还可以修改结构(在这种意义上,结构不是值类型,因为值类型是不可变的)。 任何一种都可以用来满足接口的要求。 在上面的示例中, Printa应用于指向结构A的指针。 Go说A是该方法的接收者。

Go语法对于结构和指向它的指针也有点宽大。 在C中,您可以拥有一个结构,并且可以编写ba来访问该结构的字段。 如果指向C中的结构的指针,则必须编写b->a才能访问同一字段。 在指针ba情况下是语法错误。 Go表示写b->a是没有意义的(您可以从字面上解释)。 当点运算符可以重载时,为什么用->运算符填充代码。 如果是结构化,则通过指针进行字段访问。 很合逻辑。

因为指针在某种程度上和结构本身一样好,所以可以这样

package main

import (
	"fmt"
)

type A struct {
	a int
}

func (a *A) Printa() {
	if a == nil {
		fmt.Println("a is nil")
	} else {
		fmt.Printf("%d\n", a.a)
	}
}

func main() {
	var a *A = nil
	a.Printa()
}

是的,这是真正的Java程序员的重点,您不应该害怕。 我们确实在nil指针上调用了一个方法! 怎么会这样

输入变量而不是对象

这就是为什么我使用引号写“对象”的原因。 当Go存储一个结构时,它就是一块内存。 它没有对象标头(尽管可以,因为它是实现的问题,而不是语言定义的问题,但实际上没有)。 它是保存值类型的变量。 如果变量类型是结构,则在编译时就已经知道。 如果这是一个接口,则该变量将指向该值,同时它还将引用其具有该值的实际类型。

如果变量a是接口而不是结构体的指针,则您不能执行相同操作 :出现运行时错误。

实施接口

接口在Go中非常简单,同时又非常复杂,或者至少与Java中的接口不同。 接口声明了一系列结构要实现与接口兼容的功能。 继承的完成方式与结构相同。 奇怪的是,如果某个结构实现了接口,则无需指定该结构。 毕竟,实现接口的实际上不是结构,而是使用该结构或指向该结构的指针作为接收器的一组函数。 如果实现了所有功能,则该结构确实实现该接口。 如果缺少其中一些,则说明实施不完整。

为什么在Java中而不是Go中需要'implements'关键字? Go不需要它,因为它已完全编译,没有什么比类加载器更能在运行时加载单独编译的代码了。 如果一个结构应该实现一个接口,但不是,那么它将在编译时发现,而无需明确分类该结构确实实现了该接口。 如果使用反射(Go拥有的反射),则可以克服此问题并导致运行时错误,但是'implements'声明仍然无济于事。

Go紧凑

Go代码紧凑且不容忍。 在其他语言中,有些字符根本没用。 自从C语言问世以来,我们已经习惯了这40年来,所有其他语言都遵循该语法,但这并不一定意味着它是最好的遵循方式。 毕竟,自C以来我们都知道,最好在'if'语句中使用代码分支周围的{}解决'trailing else'问题。 (可能是Perl是第一个要求此格式的主流C语言语法语言。)但是,如果必须使用花括号,则没有必要将条件括在括号之间。 如您在上面的代码中看到的:

...
	if a == nil {
		fmt.Println("a is nil")
	} else {
		fmt.Printf("%d\n", a.a)
	}
...

不需要,Go甚至不允许。 您可能还会注意到没有分号。 您可以使用它们,但不需要。 插入它们是对源代码的预处理步骤,并且非常有效。 无论如何,大多数时候它们都很混乱。

您可以使用':='来声明一个新变量并为其赋值。 表达式通常在右侧定义类型,因此无需编写“ var x typeOfX = expression ”。 另一方面,如果导入软件包,请分配一个以后不使用的变量:这是一个错误。 由于可以在编译时检测到它是代码错误,因此编译失败。 很聪明。 (有时,当我导入要使用的程序包时很烦,在引用它之前,我保存了代码,而IntelliJ智能地删除了导入,只是为了帮助我。)

线程和队列

线程和队列内置在该语言中。 它们称为goroutines和通道。 要启动goroutine,您只需编写go functioncall() ,该函数将在其他线程中启动。 尽管标准Go库中有锁定“对象”的方法/功能,但本机多线程编程仍在使用通道。 通道是Go中的内置类型,它是任何其他类型的固定大小FIFO通道。 您可以将值推入通道,goroutine可以将其拉出。 如果通道已满,则推块处于阻塞状态,如果通道为空,则拉动将阻塞。

恐慌!

Go确实具有异常处理,但是不应像Java中那样使用它。 异常称为“紧急情况”,当代码中存在真正的紧急情况时,可以使用此异常。 在Java术语中,它类似于以'... Error'结尾的某些throwable。 如果有特殊情况,系统调用将返回该状态可以处理的一些错误,并且应用程序功能应遵循类似的模式。 举个例子

package main

import (
	"log"
	"os"
)

func main() {
	f, err := os.Open("filename.ext")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
}

函数“ Open”返回文件处理程序和nil,或nil和错误代码。 如果在Go Playground上执行它(单击上面的链接),则会显示错误。

这与我们使用Java进行编程时所​​习惯的做法并不完全匹配。 很容易错过一些错误条件并编写

package main

import (
	"os"
)

func main() {
	f := os.Open("filename.ext")
	defer f.Close()
}

只是忽略了错误。 当我们对更长的命令链感兴趣(如果其中任何一个产生了错误,并且我们并不在乎哪个命令)时,检查每个系统或应用程序调用中可能返回错误的错误的可能性也很麻烦。

最终没有,请推迟

Java与try / catch / finally功能一起实现的功能与异常处理紧密相关。 在Java中,无论哪种方式,您都可以使用最终代码执行的代码。 Go提供了关键字“ defer”,使您可以指定一个函数调用,即使在发生恐慌的情况下,该方法也可以在方法返回之前被调用。 这是解决该问题的一种方法,可以减少滥用的可能性。 您不能编写仅延迟执行函数调用才能执行的任意代码。 在Java中,您甚至可以在finally块中包含return语句,或者当在finally块中执行的代码也可能引发异常时,看到一团糟试图处理这种情况。 去很容易。 我喜欢。

其他事情…

乍一看似乎也很奇怪

  • 公共函数和变量都大写,没有关键字“ public”,“ private”
  • 库的源代码将被导入到项目的源中(我不确定我是否理解正确)
  • 缺乏仿制药
  • 语言中以注释指令形式内置的代码生成支持(这实际上是wtf)

通常,Go是一种有趣的语言。 即使在语言级别上,它也不能替代Java。 它们不应执行相同类型的任务。 Java是企业开发语言,Go是系统编程语言。 与Java一样,Go也在不断发展,因此我们可能会在将来看到一些变化。

翻译自: https://www.javacodegeeks.com/2016/04/comparing-golang-java.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值