语法对比
功能 | Java | GO |
---|---|---|
封装 | class 关键字组织对象,所有方法和字段都定义在类内 | struct 关键字组织对象,struct内可以有字段和方法(方法组织形式不是字节在struct内声明),也允许方法和字段脱离方法之外,这些方法属于包| |
函数重载 | 允许函数重载 | Go上不允许函数重载,必须具有方法和函数的唯一名称 |
多态 | Java通过类继承或接口来实现多态 | Go 只能通过接口来实现多态 |
继承 | 支持 | 不支持。Go语言的继承通过匿名组合完成,基类以Struct的方式定义,子类只需要把基类作为成员放在子类的定义中 |
访问权限 | 支持public、protected、private、默认等几种权限 | 通过大小写控制包外可访问还是不可访问 |
值引用和地址引用 | 基础类型是值引用,其他对象是地址引用 | 所有对象默认是值应用,在对象前加&表示地址引用 |
接口 | 提倡先定义,后实现 | Duck typing方式,在使用时抽象出接口,有点先实现后抽象。Go 的 interface 写起来更自由,无需显式实现,只要实现了与 interface 所包含的所有函数签名相同的方法即可。 |
异常错误处理对比
错误指的是可能出现问题的地方出现了问题,比如打开一个文件时失败,这种情况在人们的意料之中 ;而异常指的是不应该出现问题的地方出现了问题,比如引用了空指针,这种情况在人们的意料之外。可见,错误是业务过程的一部分,而异常不是 。
错误和异常需要分类和管理,不能一概而论。错误和异常的分类可以以是否终止业务过程作为标准错误是业务过程的一部分,异常不是不要随便捕获异常,更不要随便捕获再重新抛出异常。
Java中,Throwable是所有错误(Error)和异常 (Exception) 的基类,整的来说,它们都是程序运行过程中可能出现的问题,区别在哪里呢? Exception是可预料的,而Error是不可预料的。Exception我理解为在程序运行中正常情况下意料之中发生的事,是可以被程序员处理,补救,有机会回到正常处理流程的,而Error在程序运行中非正常情况下发生后是无法被处理,恢复,比如内存溢出,栈溢出等。
Go中引入error接口类型作为错误处理的标准模式,如果函数要返回错误,则返回值类型列表中肯定包含error。error处理过程类似于C语言中的错误码,可逐层返回,直到被处理。
Golang中引入两个内置函数panic和recover来触发和终止异常处理流程,同时引入关键字defer来延迟执行defer后面的函数。
Recover之后还是被返回Error所处理,什么含义呢,就是在意料之外的panic发生时,在defer中通过recover捕获这个恐慌,转化为错误通过方法返回值告诉方法调用者。
其实从字面意思,Go中弱化了异常,一切皆错误,都被包装成类似Code,Message的形式返回方法调用者,直到有调用者去处理, 这也是Go的设计精髓,简化没必要存在的。
错误处理
在Go代码中 if err!=ni 随处可见,尤其是业务代码开发中。Go没有类似 try catch 这样的语句,Go对错误的价值观是可编程,我们看下面的代码:
go 错误处理
result1,err := func1()
if err != nil{
return nil,,err
}
result2,err := func2()
if err != nil{
return nil,err
}
result3,err := func3()
if err != nil{
return nil,err
}
return result1+result2+result3,nil
java错误处理
try{
result1 = method1();
reuslt2 = method2();
result3 = method3();
return result1+result2+result3;
}cache(Exception e){
log.error(e);
return null;
}
从顺序角度来看,Go更能被理解,每一个方法都有一个结果值和一个可能发生的错误值,Go中程序员对err有更多的操作空间,有更多的可编程性。Java需要更多的语法和更多理解,Java中相对可编程性弱化了许多。
异常处理
Java中通过 throw new 抛出一个异常,通过 tryca che捕获,而Go中通过 panic抛出一个恐慌,通过 defer和recover来处理,实现类似的功能。代码如下:
func test() (err error){
defer func(){
if e:=recover(); e != nil{
err = e.(error)
}
}()
panic("发生恐慌了")
return err
}
public Result test(){
try{
throw new RuntimeException("发生异常了")
}cache(Exception e){
//处理错误
1 打印错误,忽略
2 throw e,继续抛出
//转化为code,message
3 new Result(code,e.message)
}
}
对Java异常包装后是否和Go的错误设计有异曲同工之妙,同样的Go通过panic和defer,recover也可以为try,cache, throw这样的处理,但语言层面的设计的本质是不一样的。
Java中对异常和错误有一个比较清晰的边界,通过类继承体系进行隔离,错误并不在程序员的考虑范围之内,通过异常体系和控制流程来实现业务逻辑,往往也容易被滥用;而Go中并没有,且弱化了异常的概念,并提供了将异常转化为错误的方法。一切皆错误,拥有更好的可编程性,但同时也带来诸如 iferr!=nil的这样的代码到处都是,不同的编程语言对异常错误体系设计不一样,也代表不同开发者的思想,没有对与错。
并发包对比
功能 | java | go | 简单对比 |
---|---|---|---|
上下文Context | 无 | 上下文 context.Context 是用来设置截止日期、同步信号,传递请求相关值的结构体。与 Goroutine 密切配合,实现多并发Goroutine协同 |
java的Spring框架中含有ApplicationContext是最接近go的Context的概念。但是ApplicationContext不负责同步信号以及多线程之间的协同 |
互斥锁 | 隐式互斥锁实现关键字sychronized, 显式互斥锁实现ReentrantLock | 对应java ReentrantLock的实现 Mutex | java隐式锁实现关键字sychronized是java独有的,go中无对应实现。 Go认为如果需要重入锁,就说明代码可以优化,如果非要实现可重入锁,只需要在mutex基础上记录GorutineId进行区别即可。 |
读写锁 | ReadWriteLock | RWMutex | 实现原理基本一致。都是在锁的基础包装当前锁的读写状态实现读并发,写互斥控制 |
并发等待 | CountDownLatch | WaitGroup | 允许一个或多个线程等待其他线程完成操作。都是通过信号量方式实现多并发之间的协作 |
一次性并发执行控制 | 无 | Once | 这个可以理解为懒加载单例。java没有在原生并发包对这种事项场景进行封装 |
线程同步 | Condition接口 | Cond | 可以看成显式信号量机制,为开发者提供控制并发之间实现协同的方式 |
内存管理对比
堆内存管理
Go 语言的内存分配器就借鉴了 TCMalloc(Thread-Caching Malloc) 的设计实现高速的内存分配,它的核心理念是使用多级缓存根据将对象根据大小分类,并按照类别实施不同的分配策略。Go 语言的内存分配器会根据申请分配的内存大小选择不同的处理逻辑,运行时根据对象的大小将对象分成微对象((0, 16B))、小对象([16B, 32KB])和大对象((32KB, +∞))三种。内存分配器不仅会区别对待大小不同的对象,还会将内存分成不同的级别分别管理,TCMalloc 和 Go 运行时分配器都会引入线程缓存(Thread Cache)、中心缓存(Central Cache)和页堆(Page Heap)三个组件分级管理内存。这些都可以理解为基于基础字段类型、工程中对象大小现状等做的一些约定优化。
为了让内存回收更加高效,从Sun JDK 1.2开始对堆采用了分代管理方式。可以氛围年轻代和老年代。当年轻代需要回收时会触发Minor GC(也称作Young GC)。对象在被创建时,内存首先是在年轻代进行分配(注意,大对象可以直接在老年代分配)。年轻代由Eden Space和两块相同大小的Survivor Space(又称S0和S1)构成。老年代用于存放在年轻代中经多次垃圾回收仍然存活的对象,可以理解为比较老一点的对象。
垃圾回收
Java和go语言都有垃圾回收器,会自动清理堆内存中已经不再使用的dead内存对象。两者垃圾回收的算法基础都是标记清除(Mark-Sweep)算法。标记清除收集器是跟踪式垃圾收集器,其执行过程可以分成标记(Mark)和清除(Sweep)两个阶段:标记阶段 — 从根对象出发查找并标记堆中所有存活的对象;清除阶段 — 遍历堆中的全部对象,回收未被标记的垃圾对象并将回收的内存加入空闲链表。
go为了解决原始标记清除算法带来的长时间 STW(Stop The World),多数现代的追踪式垃圾收集器都会实现三色标记算法的变种以缩短 STW 的时间。详细参考go内存总结。该算法目前在java的GC中还未采用。
Java 中针对不同代的内存特定,提出了包含线性、并发、并行标记清除和 G1 四个垃圾收集器。可以在命令行中选择。因为他需要平衡其他各种因素,因此没有一个 GC 算法的目标能将暂停时间降低到 Go 水平。可以通过重新启动程序在 GC 之间切换,因为编译是在程序运行时完成(这里指 JIT 编译器),所以不同算法所需的不同内存屏障可以根据需要编译和优化到代码中。Go 的垃圾收集器是一个并发的,三色,标记扫描收集器,这是一个由 Dijkstra 在 1978 年首次提出的想法。
Java GC默认算法是吞吐量收集器(throughput collector),默认情况下没有任何暂停时间目标。这种默认选择也是人们认为 Java GC 有点吸引力的一个原因:开箱即用,它试图使您的应用程序尽可能快地运行,并尽可能少的内存开销,而暂停时间不是该算法首要考虑的。Go 将暂停时间优化作为首要目标,以至于它似乎愿意将程序减慢至任何数量级,以获得较短暂停。