GoLang&Java

Golang与Java

区别

对比项 Java Golang
实现跨平台 通过JVM支持 能直接在目标平台编译执行
内存管理 1. 运行期间依托JVM进行内存管理
2.配置繁杂
3.启动所需内存大,速度慢
1.配置简单
2.启动所需内存少,速度快
Java的GC更为成熟
并发 支持多线程级别,本质是更高效地利用系统线程. Java19开始支持虚拟线程
1.线程池
2.volatile、atomic
3.锁:sychronized、CAS
4.ThreadLocal
支持用户级线程:协程
1. channel实现协程通信
面向对象 1.原生支持继承、多态、封装
2.类是对象的表现形式
1.非面向对象,通过结构体的嵌套和“鸭子类型”来实现继承的思想
2.结构体是“对象”的表现形式
3.Optional模式可以实现Java重载的效果
异常处理 1.有全局异常处理机制,捕获Exception与Error
2.try_catch可用于内部消化异常或重包装异常
3.throw主动抛出异常,此时必须由catch捕获处理或throws抛到函数外面,但throw RuntimeException无需这么处理
1.recover可以全局部捕获panic
2.Errors.New()新建异常,不能内部消耗就只能将error作为函数返回值
3.iferr是常见的消化异常
因为Go没有全局异常处理机制,如果想扔到外部消化而调用链较长,代码就会出现非常多的iferr
数据结构 1.八大基本数据类型
2.每个数据类型都有对应的封装类,用于提供更多的数据操作
1.原生支持map,slice,list Java的数据类型更为丰富,有如各类HashMap、LinkedList、Stack
反射 1.可以在运行时动态加载实例的具体类
2.Class对象再内存空间是唯一的,通过ClassName便可以反射出一些Class基本信息
3.面对声明类型为父类的实例,通过反射可以了解到实际类型
4.Class.getField().set()操作属性
1.TypeOf()获取实际类型(Type)的基本信息,包括成员名,类型名
2.ValueOf()获取实际值(Value)
泛型 1.泛型类ClassName<[T,E,K,V]>,实例化时必须指明具体类型,但不能是基本数据类型
2.泛型方法 T function(T t),使用时传入的类型也就是返回的类型
1.泛型方法 fuction(t T) T, 使用时需要指明T类型function[T=int](t)
2.泛型结构type StructName[T int|float32|float64] struct,使用时选择类型StructName[int]{…}
特有语法 1.注解支持
2.继承,多态,重载覆写
3.static静态属性,静态块
4.异常处理
1.函数多值返回,也可以自由选择接受那些返回值
2.指针
3.函数也是一种类型,可以作为参数或返回值
4.defer延迟执行
5.协程与channel

如何实现继承

Java

java实现通过extends或implements实现继承,子类可以像父类一样使用其所有变量和方法,当继承一个接口时,编译器会强制检查继承者是否实现了所有抽象方法。

Golang

Golang以“鸭子类型”的方式实现“继承”,即我拥有你的所有变量和方法,我即是你。因而,Golang在结构体A中直接嵌入另一结构体类型B,在编译器看来A既可以是A也可以是B,实际类型依然是A。

1
2
3
4
5
6
7
8
9
type Person struct{
name string
}
type Student struct{
Person
StudentID int
}

var s Person = Student{}

当类型B是一个接口类型时,编译器理所当然的认为A也是B类型,A使用接口方法时编译器也不会检查A是否实现了接口方法,直到程序运行到该处才会报错

1
2
3
4
5
6
7
8
9
10
11
type Writer interface{
write() string
}
type Student struct{
Writer
StudentID int
}
var w Writer = &Student{}
w.write() //runtime error: invalid memory address or nil pointer dereference

func (s *Student) write(){} //只有Student实现该接口方法,运行时才不会报错

当然,A可以不嵌入B接口,如果实现了B的所有接口方法,编译器也会认为A是B类型

1
2
3
4
5
6
7
type Writer interface{
write() string
}
type Student struct{
StudentID int
}
func (s *Student) write(){}

但这也带来一个问题,编译器不会判断A是否实现了所有接口方法,写代码的时候就可能遗漏。为了让编译器强制检查A是否实现所有接口方法,可以通过类型强转

1
var _ Writer = (*Student)(nil)

控制范围

Java

Java控制范围以Class和Package为单元, private,default,protected,public严格控制了访问范围

类内部 本包 子类 外部包
public
protected ×
default × ×
private × × ×

Golang

Go的控制范围以Package为单元, 首字母大写则能在包外访问, 否则不能

并发控制

Java

Java在并发控制上能操控的更多,更细致。如线程池的核心参数,拒绝策略;volatile做信号量;wait/notify/join控制线程流程;ThreadLocal独享变量,各种锁。

Java新建线程可以继承Thread、实现Runnable,实现Callable,lambda实例化Thread类

Golang

Golang新建协程只需要调用go关键词,但并发控制比较受局限。如channel通信,mutex互斥锁,rwmutex读写锁,semaphore信号量机制,atomic的各种原子操作,WaitGroup控制协程流程,Context实现上下文共享

异常处理

Java

java除了try_catch直接捕获异常,消化异常外,Spring还提供了全局异常处理机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestControllerAdvice
@Component
public class GlobalExceptionHandler {
@ExceptionHandler(UserInfoException.class)
public ResponseVO handleUserInfoException(UserInfoException e) {
switch (e.getClass().getSimpleName()) {
case "PhoneFormatException":
log.error("[Phone Format Error]", e);
return new ResponseVO(Status.PHONE_FORMAT_ERR);
case "UserNotExistException":
log.error("[Uesr Not Exist Error]: ", e);
return new ResponseVO(Status.USER_NOT_EXIST_ERR);
case "PasswordWrongException":
log.error("[Password Wrong Error]: ", e);
return new ResponseVO(Status.PASSWD_WRONG_ERR);
default:
log.error("[UserInfoException] Can not handle this kind of exception", e);
return new ResponseVO(Status.SERVER_ERR);
}
}
}

该异常处理机制可以捕获Controller里任何地点没被消化的异常

用户继承Exception即可自定义异常,这些异常也可以被全局异常处理机制捕获

Golang

Golang的recover可以捕获全局Panic, 但无法捕获用户自定义的Error,因此用户只能通过iferr判断每个函数可能返回的error

Try_Resource

Java

Java在打开资源时通常要在finally中释放资源, Java1.7后新增try-with-resource语法糖允许不用手动释放资源.

Golang

Golang习惯在打开资源后紧跟着defer的资源释放语句, 确保资源一定会被释放

依赖管理

Java

Java使用Maven管理依赖包(jar), maven首先前往本地仓库拉去依赖,失败后才会去远程仓库(maven repository)拉取.

此外maven-plugin可以管理项目的生命周期

image-20230909140355281

maven clean package则可以顺序执行clean周期到clean阶段,执行default周期到package阶段

Golang

GOPATH

golang在1.11后正式推出GoMoudles模式,在此之前,Golang的依赖包需要放在GOPATH下,任何import都会从GOPATH下寻找。但逐渐出现许多问题:

  1. 依赖包的不同版本无法共存在GOPATH下,不同项目依赖不同版本时要么独立设置GOPATH,要么更名另一版本的依赖包
  2. 没有管理项目依赖项的工具,如果某些依赖被剔除,需要人为检查

Go Vendor

golang在1.5开始支持go vendor,开启时每个项目都会构建vendor目录,所有以来也都会下载到自己的vendor目录下。寻找依赖包时,vendor优先级高于GOPATH。

Go Moudles

GO111MODULE=on开启GoMoudle模式。GoMoudles模式对应的工具为go mod。新建GoMoudle项目时只需要执行go mod init ”github.com/levi/testmod“即可在本目录下生成go.mod文件, go.mod所在的根目录即为一个GoMoudle项目。

其他使用者通过go mod get ”github.com/levi/testmod“即可下载。

go moudles没有中心库和本地库的区别,下载依赖包就是直接拉取源码。而且如果处于内网开发,就无法下载依赖。此时go mod vendor就能下载到vendor目录并隔离外网开发。

replace

如果本地开发需要依赖一些本地模块或未发布模块,通过replace即可替换掉远程模块。但每个人replace为本地模块时填写的本地路径都不一样

golang在1.18后又新增了go work。go.work支持将多个Go Moudle的聚拢到当前go.work的项目中, 然后再通过replace替换为本地项目,以此达到原先在go.mod的replace效果。go.work不用上传,留在本地即可。

CICD

对比项 Java Go 影响程度
支持跨平台编译 支持 支持 可以减轻重复工作量;
但Go跨平台编译需要做额外工作:编写Makefile文件,通过Makefile文件标注各种平台编译需要的环境变量
支持跨平台运行 支持 支持,但需要在编译阶段下功夫 影响大,避免按平台区别编码,增加工作量;
java一次编码,到处运行,go如果需要c库,则会有影响,大部分情况没有影响
编译产物 当前项目jar+所有依赖的项目jar文件 一个单独的exe文件,没有依赖项 影响大,影响CD部署环境的维护工作量,这一点,Go的优势更大
编译产物大小 取决于项目的依赖包多少以及各依赖项的大小 相对小 影响磁盘空间;
影响镜像拉取速度,进而影响服务启动速度
运行环境 取需要安装JVM 无需安装任何依赖,直接运行exe文件 直接影响运维的工作量,GO对运维主机的维护更简单

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!