当前位置: 首页 > backend >正文

Java与Go差别在哪

基础语法

Golang:  编码风格及可见域规则严格且简单;Java:  来说层次接口清晰、规范。主要表现有: 1.1 变量

a、变量声明及使用

b、变量声明及初始化

1.2 作用域规则

Java:  对方法、变量及类的可见域规则是通过 private、protected、public 关键字来控制的,具体如下。

作用域 

当前类

同一package

子孙类 

public

√    

√    

√    

protected 

√    

√    

√    

default(无修饰词)

√    

√    

×  

private

√    

×  

×  

Golang:  控制可见域的方式只有一个,当字段首字母开头是大写时说明其是对外可见的、小写时只对包内成员可见。 

1.3 逗号 ok 模式

1.4 结构体、函数以及方法

a、结构体声明及使用

在 Golang 中区别与 Java 最显著的一点是,Golang 不存在“类”这个概念,组织数据实体的结构在 Golang 中被称为结构体。函数可以脱离“类”而存在,函数可以依赖于结构体来调用或者依赖于包名调用。Golang 中的结构体放弃了继承、实现等多态概念,结构体之间可使用组合来达到复用方法或者字段的效果。

b、函数和方法的区别

在Java中:所有的“函数”都是基于“类”这个概念构建的,也就是只有在“类”中才会包含所谓的“函数”,这里的“函数”被称为“方法”

在 Golang 中:“函数”和“方法”的最基本区别是:函数不基于结构体而是基于包名调用,方法基于结构体调用。

1.5 值类型、引用类型以及指针

Java:在Java中不存在显式的指针操作;8种基本数据类型是值类型,数组和对象属于引用类型。

Golang:而 Golang 中存在显式的指针操作,但是 Golang 的指针不像 C 那么复杂,不能进行指针运算;所有的基本类型都属于值类型,但是有几个类型比较特殊,表现出引用类型的特征,分别是 slice、map、channel、interface,除赋值以外它们都可以当做引用类型来使用,因此当我们这样做时,可以直接使用变量本身而不用指针。 注:slice 与数组的区别为是否有固定长度,slice 无固定长度,数组有固定长度。值得注意的是,在 Golang 中,只有同长度、同类型的数组才可视为“同一类型”,譬如 []int 和 [3]int 则会被视为不同的类型,这在参数传递的时候会造成编译错误。

a、数组对比

在 Java 中:当向方法中传递数组时,可以直接通过该传入的数组修改原数组内部值(浅拷贝)。 在 Golang 中:则有两种情况:在不限定数组长度(为 slice)时也直接改变原数组的值,当限定数组长度时会完全复制出一份副本来进行修改(深拷贝)

b、对象对比

在 Golang 中:传入函数参数的是原对象的一个全新的 copy(有自己的内存地址);go 对象之间赋值是把对象内存的 内容(字段值等) copy 过去

在 Java 中:传入函数参数的是原对象的引用的 copy(指向的是同样的内存地址); Java 对象之间的赋值是把对象的引用 copy 过去,因为引用指向的地址变了,所以对象的内容也变了。

c、指针的区别

在 Java 中:如果传递了引用类型(对象、数组等)会复制其指针进行传递。 在 Golang 中:必须要显式传递 Person 的指针,不然只是传递了该对象的一个副本。

面向对象

在 Golang 中:没有明确的 OOP 概念,Go 语言只提供了两个关键类型:struct,interface。 在 Java 中:面向对象语言的封装、继承、多态的特性以及“继承(extends)、实现(implements)”等关键字。

2.1 Java 的 OOP 与 Golang 的结构体组合

2.2 侵入式与非侵入式接口

在 Java 中:接口主要作为不同组件之间的契约存在。对契约的实现是强制的,你必须声明你的确实现了该接口。这类接口我们称为侵入式接口。“侵入式”的主要表现在于实现类需要明确声明自己实现了某个接口。 在 Golang 中:非侵入式接口不需要通过任何关键字声明类型与接口之间的实现关系,只要一个类型实现了接口的所有方法,那么这个类型就是这个接口的实现类型。

package entitytype Factory interface {Produce() boolConsume() bool
}type CarFactory struct {ProductName string
}func (c *CarFactory) Produce() bool {fmt.Printf("CarFactory生产%s成功", c.ProductName)return true
}func (c *CarFactory) Consume() bool {fmt.Printf("CarFactory消费%s成功", c.ProductName)return true
}// --------------
package mainfunc main() {factory := &entity.CarFactory{"Car"}doProduce(factory)doConsume(factory)
}func doProduce(factory entity.Factory) bool {return factory.Produce()
}func doConsume(factory entity.Factory) bool {return factory.Consume()
}

Golang 的非侵入式接口优点:简单、高效、按需实现 在 Go 中,类没有继承的概念,只需要知道这个类型实现了哪些方法,每个方法是啥行为。 实现类型的时候,只需要关心自己应该提供哪些方法,不用再纠结接口需要拆得多细才合理。接口由使用方按需定义,而不用事前规划 减少包的引入,因为多引用一个外部的包,就意味着更多的耦合。接口由使用方按自身需求来定义,使用方无需关心是否有其他模块定义过类似的接口 Java 的侵入式接口优点: 层次结构清晰,对类型的动作行为有严格的管理

异常处理

在 Java 中:通过 try..catch..finally 的方式进行异常处理,有可能出现异常的代码会被 try 块给包裹起来,在 catch 中捕获相关的异常并进行处理,最后通过 finally 块来统一执行最后的结束操作(释放资源)。 在 Golang 中:错误处理方式有两种方式:,ok 模式  与defer、panic 及 recover 的组合。

优点:这种比 Java 的简单很多,是 Golang 在异常处理方式上的一大特色。 缺点:代码冗余,所有的异常都需要通过 if err != nil {} 去做判断和处理,不能做到统一捕捉和处理,容易遗漏。

defer 是 Golang 错误处理中常用的关键字,pannic 及 recover 是 Golang 中的内置函数,通常与 defer 结合进行错误处理,它们各自的用途为:

defer 的作用是延迟执行某段代码,一般用于关闭资源或者执行必须执行的收尾操作,无论是否出现错误 defer 代码段都会执行,类似于 Java 中的 finally 代码块的作用;defer 也可以执行函数或者是匿名函数:

需要注意的是,defer 使用一个栈来维护需要执行的代码,所以 defer 函数所执行的顺序是和 defer 声明的顺序相反的。

panic 的作用是抛出错误,制造系统运行时恐慌,当在一个函数执行过程中调用 panic() 函数时,正常的函数执行流程将立即终止,但函数中之前使用 defer 关键字延迟执行的语句将正常展开执行,之后该函数将返回到调用函数,并导致逐层向上执行 panic 流程,直至所属的 goroutine 中所有正在执行的函数被终止,panic 和 Java 中的 throw 关键字类似,用于抛出错误,阻止程序执行。 recover 的作用是捕捉 panic 抛出的错误并进行处理,需要联合 defer 来使用,类似于 Java 中的 catch 代码块

func main() {fmt.Println("main begin")// 必须要先声明defer,否则不能捕获到panic异常defer func() { fmt.Println("defer begin")if err := recover(); err != nil {// 这里的err其实就是panic传入的内容fmt.Println(err) }fmt.Println("defer end")}()test()// test中出现错误,这里开始下面代码不会再执行fmt.Println("main end") 
}func test() {fmt.Println("test begin")panic("error")//这里开始下面代码不会再执行fmt.Println("test end") 
}//执行结果
main begin
test begin
defer begin
error
defer end

注:利用 recover 处理 panic 指令,defer 必须在 panic 之前声明,否则当 panic 时,recover 无法捕获到 panic。

并发编程

Java 中 CPU 资源分配对象是 Thread,Go 中 CPU 资源分配对象是 goroutine。Java Thread 与系统线程为一一对应关系,goroutine 是 Go 实现的用户级线程,与系统线程是 m:n 关系。

4.1 Java 和 Golang 的基本实现

在 Java 中,如要获得 CPU 资源并异步执行代码单元,需要将代码单元包装成 Runnable,并创建可以运行代码单元的 Thread,执行 start 方法启动线程。

Java 应用一般使用线程池集中处理任务,以避免线程反复创建回收带来的开销。

在 Golang 中,则需要将代码包装成函数。使用 go 关键字调用函数之后,便创建了一个可以运行代码单元的 goroutine。一旦 CPU 资源就绪,对应的代码单元便会在 goroutine 中执行。

4.2 Java 和 Golang 的区别 Golang 语言采用了 CSP(Communicating Sequential Processes)的模型,其中以 goroutine 和 channel 作为主要实现手段。Java 则采用了多线程模型,其中以 Thread 和 Synchronization 作为主要实现手段。 Golang 语言的 goroutine 是一种轻量级的线程,它们的创建和销毁速度比 Java 中的线程快得多。在 Java 中,创建和销毁线程都需要相当大的开销。 Golang 语言的 channel 是一种同步数据传递的机制,它可以方便地解决多道程序之间的通信问题。Java 中则需要使用同步工具(如 Semaphore、CountDownLatch 等)来解决多线程之间的通信问题。

Java 和 Go 官方库中同步方式的对应关系

Java

Golang

synchronized,ReentrantLock

sync.Mutex, one unit buffered channel

读写锁

ReentrantReadWriteLock, StampedLock

sync.RWMutex

条件变量

condition

sync.Cond

CAS/Atomic

Varhandle、volatile,Atomic 类

atomic.Value,atomic 包

once

单例模式

sync.Once

a、Java synchronized 与 Golang Mutex

Go Mutex:Go 并未像 Java 一样提供 volatile 这样基础的关键字,但其 Mutex 相关内存模型和 synchronized 或 Java 官方库 Lock 实现有十分接近语义。若 goroutine A 在 t1 时刻释放 sync.Mutex 或 sync.RWMutex 后,在随后的 t2 时刻,若任意 goroutine B 获取到锁,则 goroutine A 在 t1 时刻之前发生的所有写入均对 B 可见。

type count struct {lock  sync.Mutexvalue int
}// 结构体对应的结构方法
func (receiver *count) countOne() {receiver.lock.Lock()defer receiver.lock.Unlock()receiver.value++
}func main() {c := count{lock:  sync.Mutex{},value: 0,}group := sync.WaitGroup{}for i := 0; i < 10; i++ {group.Add(1)go func(count2 *count) {defer group.Done()for i := 0; i < 100; i++ {count2.countOne()}}(&c)}group.Wait()fmt.Printf("The count value is %d", c.value)    // The count value is 1000
}

b、条件变量

Java 和 Golang 相似点:一般来说,条件变量衍生于锁,不同条件变量只是同一锁空间下的不同等待队列。Java 可以使用 synchronized 代码块保护特定代码路径,兼而可以在 synchronized 代码块中使用 Object wait 和 notify、notifyall 方法实现单一条件等待。如果需要多个条件,可以使用官方库提供的 Lock 实现和 Condition 实现。 Java 和 Golang 区别点:Java 创建条件变量的方式是调用 Lock 接口 newCondition 方法。Go sync.Cond 结构体需设置 sync.Mutex 字段才能工作,挂起方法为 Wait,唤醒方法为 Braodcast。Go 语言里面条件变量的通知 Signal() 和 Broadcast(),并没有在锁的保护下执行,而是在 Unlock() 之后执行。

c、CAS/Atomic 原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性,称为原子性(atomicity)。CAS是乐观锁技术,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。Java 和 Go 均支持 CAS 及原子操作。 在Java中:  CAS 操作由 volatile 关键字和 VarHandle(9 之前是 UnSafe)支持,在此基础上有了 Atomic 类和并发包中的大量无锁实现(如 ConcurrentHashMap, AQS 队列等)。 在 Golang 中:atomic.Value 提供了 CAS 操作基础,它保证任意类型(interface {}) 的 Load 和 Store 为原子操作,在此基础上有 atomic 包。 d、Once 与单例模式 sync.Once 是 Golang 标准库提供的使函数只执行一次的实现,常应用于单例模式,例如初始化配置、保持数据库连接等。它有 2 个特性: 保证程序运行期间某段代码只会执行一次。 如果多个 goroutine 同时执行 Once 守护代码,只有 1 个 goroutine 会获得执行机会,其他 goroutine 会阻塞直至代码执行完毕。

java 中单例模式的写法有好几种,主要是懒汉式单例、饿汉式单例。 懒汉式单例:懒汉式单例的实现没有考虑线程安全问题,需要结合 synchronized,保证线程安全

饿汉式单例:饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

垃圾回收

GC(Garbage Collection)垃圾回收是一种自动管理内存的方式,支持 GC 的语言无需手动管理内存,程序后台自动判断对象是否存活并回收其内存空间,使开发人员从内存管理上解脱出来。 因为支持更多的特性和更灵活多样的 GC 策略,比如分代,对象可移动,各种参数调节等等。而 Go 只做了一种 GC 方案,不分代,不可移动,没什么参数能调节,而且更注重暂停时间的优化,执行 GC 的时机更频繁,所以 Go 通常更占更少的内存,但代价就是 GC 性能比 JVM 差了不少。

5.1 Java 的垃圾回收体系 Java 基于 JVM 完成了垃圾收集的功能,其体系很庞大,包括了垃圾回收器(G1、CMS、Serial、ParNew 等)、垃圾回收算法(标记-清除、标记-整理、复制、分代收集)、可达性算法(可达性分析、引用计数法)、引用类型、JVM 内存模型等内容。经过多代发展,Java 的垃圾回收机制较为完善,Java 划分新生代、老年代来存储对象。对象通常会在新生代分配内存,多次存活的对象会被移到老年代,由于新生代存活率低,产生空间碎片的可能性高,通常选用“标记-复制”作为回收算法,而老年代存活率高,通常选用“标记-清除”或“标记-整理”作为回收算法,压缩整理空间。

5.2 Golang GC 特征

三色标记、并发标记和清扫、非分代、非紧缩、写屏障

a、三色标记

  1. 程序开始时有黑白灰三个集合,初始时所有对象都是白色;
  2. 从 root 对象开始标记,将所有可达对象标记为灰色;
  3. 从灰色对象集合取出对象,将其引用对象标记为灰色,放入灰色集合,并将自己标记为黑色;
  4. 重复第三步,直到灰色集合为空,即所有可达对象全部都被标记;
  5. 标记结束后,不可达白色对象即为垃圾,对内存进行迭代清扫,回收白色对象;
  6. 重置 GC 状态;

b、非分代 Java 采用分代回收(按照对象生命周期长短划分不同的代空间,生命周期长的放入老年代,短的放入新生代,不同代有不同的回收算法和回收频率),Golang 没有分代,一视同仁; c、非紧缩 在垃圾回收之后不会进行内存整理以清除内存碎片; d、写屏障 在并发标记的过程中,如果应用程序修改了对象图,就可能出现标记遗漏的可能,写屏障是为了处理标记遗漏的问题。

资源消耗对比

6.1 Java 的 JIT 策略比 Golang 的 AOT 策略

Java 在运行时相比 Golang 多占用了一些内存。原因在于: Java 运行态中包含了一个完整的解释器、一个 JIT 编译期以及一个垃圾回收器,这会显著地增加内存。 Golang 语言直接编译到机器码,运行态只包含机器码和一个垃圾回收器。 因此 Golang 的运行态相对消耗内存较少。

6.2 内存分配和垃圾回收器

Java 确实在起步占用上偏多,毕竟 jvm 需要更多内存做 jit,默认的 gc 算法对内存要求偏高,但这不能代表后续占用仍然线性增长。如果目标是启动成百上千个内存需求较少的进程,那 Java 确实不擅长。

6.3 并发 协程模型比线程模型更加节省内存。

6.4 反射

Golang 的反射更加简单,导致框架的内存消耗 Golang 程序比 Java 程序优秀。主要是因为: Java 的框架实现中大量使用反射,并使用 hashmap 缓存信息,这2个都是极度消耗内存的行为。 Golang 的框架中也使用 reflect、map。但是 Golang 是面向 interface 和值类型的,这导致 Golang 的反射模型要比 Java 的反射模型简单非常多,反射过程要产生的对象数量也少非常多。

生态

Java 在生态这方面简直是无敌的存在,这主要得益于 Spring 全家桶,Spring 让 Java 走上了神座。Golang 语言知名的框架也很多,但是远远没有 Spring 影响那么大。

 

优点

缺点

Golang

代码简洁性 静态类型可编译成机器码直接运行 天生多核并行 垃圾收集 跨平台且不依赖运行时环境 简洁的泛型

有限的库支持 泛型不够完善 灵活度没Java高(这个可算优点也可算缺点)

java

优秀的生态 优秀的三方库 多线程 灵活性高 平台独立性 完善的语言特性 代码结构层次清晰

大量冗余的陈旧实现导致性能不佳 生态的复杂性 复杂的继承机制

http://www.xdnf.cn/news/8462.html

相关文章:

  • **代换积分法**或**变量替换法**)
  • 【论文阅读】Stop Overthinking:高效大模型推理技术综述
  • 26考研|高等代数:λ-矩阵
  • 07_分类器不确定评估
  • 京东外卖分润系统部署实操!0门槛入驻+全平台接入+自定义比例...这些人,赚翻了!
  • Terraform本地windows部署
  • 安全生态与职业跃迁
  • 相机--基础
  • [Datagear] 实现按月颗粒度选择日期的方案
  • 精益数据分析(81/126):从Timehop案例看病毒性增长的黑客式策略
  • 数据的获取与读取篇---获取数据
  • 客服中心大模型应用演进路线:从传统服务到超级智能助手的转型
  • leetcode513. 找树左下角的值:层序遍历中的深度与顺序控制之道
  • Maven 项目介绍
  • 什么是HTTP
  • FFTW图像处理入门
  • 支持电子病历四级的云HIS系统,云HIS系统源码,医院管理信息系统
  • 5月23日day34打卡
  • 日拱一卒【6】
  • IDEA 编程语言 MoonBit:为 AI 与大型系统而生,无缝调用 Python
  • 2025最好的Next.js面试题
  • 霍尼韦尔HMR2300-D00-485数字模块
  • LTSPICE仿真电路:(二十九)T型反馈比例器
  • TCP实现双向通信练习题
  • 网络的协议和标准
  • Gradle快速入门
  • 【普及+/提高】洛谷P2613 【模板】有理数取余——快读+快速幂
  • 用户获取规模提升45%,NetMarvel助力金融APP精准推广!
  • 基于民锋价格通道模型的波动分析策略研究
  • Docker安装Nginx(最完整的安装方式)