从Java到Golang入门杂谈

简介: 作为一名Java老兵,入门Golang的一些体验和思考记录,本篇只是一些零碎的记录,不打算写成一个Golang入门指南,如果需要入门指南请参考其他文档或者书籍。

一句话总结:Java是一门很好的语言,Golang是一门很有意思的语言。

理念上的区别

Java诞生于企业信息化年代,那个时候软件工程师还是一份很严肃的职业,所以Java的设计从骨子里透着严谨和认真,并且力求完备周全,无论是虚拟机的规范,还是API文档都可以看出来在Gasling的带领下,Java团队构建了一个非常严谨而完备的技术平台,让Java Developer能够在不需要了解太多的底层细节就能写出安全可靠的代码,相比之前的C/C++大幅度降低了技术风险,符合普通人直觉的OOP设计以及Design Pattern的总结,也让很多初级程序员能够快速上手,把业务需求转换为代码实现,从而带来了整个Java世界的繁荣。
但是到了互联网时代之后,Java的很多设计让很多代码变得冗长而无趣,对于高水平程序员来说亟需一种表达和控制能力更强,也更简洁的语言,从而更好地提高开发的效率。所以Google结合了C和脚本语言和现代计算机语言理论发展,带来了Golang,由于有了前人的经验和踩过的坑,所以Golang从一开始就秉承着不一样的理念。从Golang Proverbs我们就可以看出来,其与Java有着一些有趣的区别。

Don't communicate by sharing memory, share memory by communicating.

首先Golang强调了通讯而非共享,在Java里面我们遵从的是OOP封装数据暴露方法的原则,但是对象是可以传递的,而对象(状态)实质上就是一片内存里的数据,而对象传递和引用被滥用之后,一个对象可能会被很多地方引用,并且都有可能改变状态,也就很容易带来系统内的逻辑耦合。而Golang从设计之初就希望通过通讯的方式最小化共享的状态,不论是通过channel来传递一个小的消息,还是最小化interface的实现,都透着让程序员减少关联和引用的画外音。

Gofmt's style is no one's favorite, yet gofmt is everyone's favorite.

其次为了避免大家在Code Convention上浪费时间争论括号应该放在哪儿,用空格还是用tab,Golang直接规定了标准格式,看起来有点霸道,但是保证统一的风格之后,确实提升了代码的可读性,也简化了代码检查工具的负担,因为只要用官方工具即可。从这一条我们可以看出来Golang的设计者是实用主义者,不愿意在一些意义不大的地方浪费时间,而是希望程序员能够专注于解决问题。

A little copying is better than a little dependency.

这一条有点反直觉,特别是对于一些动辄复用的Java程序员来说应该是很难受的。Golang里面并不鼓励为了复用而复用,潜台词是:“如果只是简单的几行代码,那么直接复制过来用即可,不要想那么多”。从过去的Java从业经历来看,这一条太赞了,有太多的xxx.utils和代码规约为了一两行代码的复用带来额外学习和理解成本,引入的依赖导致为了一根绳子牵出一头牛,导致产品越来越臃肿而难以改变。

简单直接

还有一条Golang proverbs没有提,但是我觉得也是Golang的一个设计理念,就是简单直接,能够直接给你的,就不要包一层再给你,程序员自己需要对代码负责。比如struct的exported fields可以直接访问,而不需要getter/setter包一层。而由调用者决定接口,而非提供者定义接口的做法,让我们可以避免搞出一堆IXxx,直接写实现即可。

实用主义而非完美主义

实用主义在Golang的设计中随处可见,不再追求完美之后就给大家带来了一些有争议但是也非常有意思的设计,当然可能也会害死强迫症患者。
比如Golang的命名规约中,大写字母开头是表示exported,大致相当于Java里面的public,但是问题是内置函数全部都是小写,为啥内置函数就可以例外呢?还有slice的append()方法,必须写成s = append(s, ...),为啥不能给个类似s.append(...)的语法糖呢?

一些Java程序员容易踩的坑

nil是有类型的

最开始的时候,把nil当成了Java的null,但是很快就被打脸,比如下面的代码:

// custom error type
type myErr struct{
   
  // fields omitted
}

func err() *myErr {
   
  return nil
}

func checkErr(err error) bool {
   
  return err == nil
}

这里checkErr(err())会返回false,原因在于err()返回的是*myErr类型的nil,而后面checkErr()判断的是error类型的nil,显然是对不上的。

slice和map

slicemap的实际存储是一个引用,但不是一个指针,这样也很容易让初学者迷糊。比如s = append(s, ...),这个调用过后,可能s还是指向原来的存储,也可能不是,取决于原来的存储容量是否能承载新加入的元素。这样就有了类似下面的代码片段(来自Go 101):

a := [...]int{
   0, 1, 2, 3}
x := a[:1]
y := a[2:]
x = append(x, y...)
x = append(x, y...)
fmt.Println(a, x)

初学者肯定想不到最后打印的是什么内容,顺便说一下Go 101是一个非常好的学习Golang的资源,值得一读。

还有就是对于未初始化的map,读是没有问题的,写就会报错,比如:

var m map[string]string // nil map
fmt.Println(m["foo"]) // no error
m["foo"] = "bar" // panic

而在Java里面,类似的代码:

Map m = null;
m.get("foo"); // NullPointerException

channel不能被close两次

一般而言,我们会认为一个资源可以被close多次,除了第一次以外其他都相当于无操作(no op),但是在golang里面,最基本的语言元素channel竟然只能close一次,之后再次close就会panic。

comparable不能比大小

Golang内置的comparable接口,只能用== !=来比较相等或者不等,如果要用> <来比较大小的话,需要的是ordered接口,这个让对于习惯Java的Comparable的人有点迷糊,同时也更加深刻地理解了Golang最小化接口定义的决心。

pprof给出来的数据看不懂

一开始我把pprof获得的heap信息当成了Java heap dump来看,但是发现完全看不懂,后来发现pprof给出来的是内存分配的调用链路,而不是当前的object关系图,所以从Java过来的同学需要好好适应一下。

type embedding不是继承

type embedding的机制,在用起来的时候有点像是继承,但是千万不要被表象所迷惑,因为type embedding不是继承,而且差异非常大。比如如下代码:

type Base struct {
   
}

func (b *Base) m1() {
   }
func (b *Base) m2() {
   
  b.m1()
}


type Child struct {
   
  *Base
}

func (c *Child) m1() {
   }

从外面调用的角度来看,Childm1()能够直接被使用,但是调用m2()的时候,里面实际调用的是m1(),这个对于从Golang入手的人来说非常好理解,但对于习惯了OOP的人来说非常反直觉。实际上所有围绕着Base类型的方法,当出现引用方法定义中的类this指针的那个变量时,引用的都是那个具体的类型,不会有重载。外部使用只不过是编译器给加了一个语法糖而已。也就是说:

var cc Child
cc.m2()
cc.Base.m2()

上面两个方法调用是完全等价的,前面那个只不过是编译器帮你省了一个变量名而已。

文档在哪里?

一开始上手的时候,我发现一个非常麻烦的问题,找不到文档,或者找到了也语焉不详,看不太明白。因为Golang的文档相比Java过于简洁了,官方库还好,很多非常流行的第三方库也是寥寥数语,而且缺乏概括性的介绍,如果单看文档很难了解如何使用,所以开始的时候还是挺怀念java doc的。
熟悉了之后发现还好,要了解代码的逻辑,最好的方法还是直接看源代码,因为golang的所有库都是提供源代码的,所以直接进去看就好了,很多细节直接读源代码很快就明白了,如果看文档或者搜索都是很难找到正确答案的。

Golang里舒服的设计

Golang有很多让程序员舒服的设计,这里主要讲一下Java里很难看到的。

简单的赋值表达

有点类似JavaScript,在Golang里面可以直接对结构进行赋值,比如:

type Foo struct{
   
  field1 string
  field2 int
}

foo := Foo{
   
  field1: "bar",
  field2: 1234,
}

相比在Java里面需要一堆的setter/getter给大的数据结构赋值或者复制很多字段,来得轻松了很多。
另外map的读写也非常简洁,不需要大量的get() set()让代码变得赏心悦目。

类型别名

Golang不允许跨包给别的类型添加方法,但是提供了type alias这种办法允许程序员在别的数据类型基础上重新定义方法,这个设计大幅度避免了为了扩展一个已有结构的功能,而不得不加入一个子类的冗余代码。即使是内置的数据类型,依然可以做type alias,golang自带的库里就有很多直接给基本类型设置type alias来实现不同逻辑功能的例子。比如我们需要一个64 bits的timestamp,可以这么设计:

type Timestamp int64
func (t Timestamp) String() string {
   
  return fmt.Sprintf("%d", t)
}
func (t *Timestamp) AddSeconds(secs int) {
   
  *t = (*t) + int64(secs)
}

因为type alias也是一个新的类型,编译器不会把这个类型当作原来的类型(除非强转),所以样调用方可以很方便地使用这个新的Timestamp类型,并且不用担心引入一些因为忽略了实际类型导致的bug。

包管理

Golang是一门互联网时代的语言,所以任何一个Golang的包,都是直接从官方repository里面下到的,不需要额外引入类似maven这种仓库,这样好处是非常方便。但是也需要注意golang的非官方包也出现在准官方repository里面,但是并不意味着有官方的质量保证和承诺,我们需要自行研究才能知道这些引入的第三方库是否合适。

辅助工具

除了语言以外,Golang提供了很多开箱即用的工具,比如golint, gofmt,pprof等,这些工具大大简化了新手上路的负担,识别了很多基础错误和问题。

打包成单个执行文件

这个功能相比于Java的CLASSPATH方便太多了,不会出现在这个环境里能正确执行,换个环境就不行的问题,原因在于CLASSPATH可以随意配置,而单个可执行文件已经包含了所有依赖前不会变更,避免了很多环境配置带来的问题。相比python的依赖管理更是强出100条街。

一点感想

Golang设计之初就定位为非OOP的语言,所以所有的OOP相关的习惯在Golang都不适用,甚至会带来bug和问题,所以从Java到Golang首先要提醒自己的,就是忘记OOP相关的概念和代码模式,切换成我要直接操作一个数据结构,该怎么写代码这个思维模式。其次就是需要忘记各种接口定义和封装,特别是各种IXxx类型的接口定义,也不要写一堆的utils,因为Golang其实没有办法阻止我们写出类似Java般冗余的代码,加上一堆的额外接口和复杂的结构之后,只会让代码变得非常难看。还有一点,package的命名非常重要,既要简单易懂,又不能占用用户常用的命名,非常考验程序员(特别是中国程序员)的词汇量和对英语的理解。

未完待续

目录
相关文章
|
8月前
|
存储 Oracle Java
java零基础学习者入门课程
本课程为Java零基础入门教程,涵盖环境搭建、变量、运算符、条件循环、数组及面向对象基础,每讲配示例代码与实践建议,助你循序渐进掌握核心知识,轻松迈入Java编程世界。
684 0
|
10月前
|
安全 Java 数据库连接
2025 年最新 Java 学习路线图含实操指南助你高效入门 Java 编程掌握核心技能
2025年最新Java学习路线图,涵盖基础环境搭建、核心特性(如密封类、虚拟线程)、模块化开发、响应式编程、主流框架(Spring Boot 3、Spring Security 6)、数据库操作(JPA + Hibernate 6)及微服务实战,助你掌握企业级开发技能。
1184 3
|
9月前
|
Java
java入门代码示例
本文介绍Java入门基础,包含Hello World、变量类型、条件判断、循环及方法定义等核心语法示例,帮助初学者快速掌握Java编程基本结构与逻辑。
675 0
|
Java API 微服务
2025 年 Java 从入门到精通学习笔记全新版
《Java学习笔记:从入门到精通(2025更新版)》是一本全面覆盖Java开发核心技能的指南,适合零基础到高级开发者。内容包括Java基础(如开发环境配置、核心语法增强)、面向对象编程(密封类、接口增强)、进阶技术(虚拟线程、结构化并发、向量API)、实用类库与框架(HTTP客户端、Spring Boot)、微服务与云原生(容器化、Kubernetes)、响应式编程(Reactor、WebFlux)、函数式编程(Stream API)、测试技术(JUnit 5、Mockito)、数据持久化(JPA、R2DBC)以及实战项目(Todo应用)。
614 5
|
9月前
|
前端开发 Java 数据库连接
帮助新手快速上手的 JAVA 学习路线最详细版涵盖从入门到进阶的 JAVA 学习路线
本Java学习路线涵盖从基础语法、面向对象、异常处理到高级框架、微服务、JVM调优等内容,适合新手入门到进阶,助力掌握企业级开发技能,快速成为合格Java开发者。
1315 3
|
10月前
|
NoSQL Java 关系型数据库
Java 从入门到进阶完整学习路线图规划与实战开发最佳实践指南
本文为Java开发者提供从入门到进阶的完整学习路线图,涵盖基础语法、面向对象、数据结构与算法、并发编程、JVM调优、主流框架(如Spring Boot)、数据库操作(MySQL、Redis)、微服务架构及云原生开发等内容,并结合实战案例与最佳实践,助力高效掌握Java核心技术。
1051 2
|
10月前
|
Java 测试技术 API
Java IO流(二):文件操作与NIO入门
本文详解Java NIO与传统IO的区别与优势,涵盖Path、Files类、Channel、Buffer、Selector等核心概念,深入讲解文件操作、目录遍历、NIO实战及性能优化技巧,适合处理大文件与高并发场景,助力高效IO编程与面试准备。
|
10月前
|
Java 编译器 API
Java Lambda表达式与函数式编程入门
Lambda表达式是Java 8引入的重要特性,简化了函数式编程的实现方式。它通过简洁的语法替代传统的匿名内部类,使代码更清晰、易读。本文深入讲解Lambda表达式的基本语法、函数式接口、方法引用等核心概念,并结合集合操作、线程处理、事件回调等实战案例,帮助开发者掌握现代Java编程技巧。同时,还解析了面试中高频出现的相关问题,助你深入理解其原理与应用场景。
|
10月前
|
前端开发 Java 数据库
Java 项目实战从入门到精通 :Java Web 在线商城项目开发指南
本文介绍了一个基于Java Web的在线商城项目,涵盖技术方案与应用实例。项目采用Spring、Spring MVC和MyBatis框架,结合MySQL数据库,实现商品展示、购物车、用户注册登录等核心功能。通过Spring Boot快速搭建项目结构,使用JPA进行数据持久化,并通过Thymeleaf模板展示页面。项目结构清晰,适合Java Web初学者学习与拓展。
610 1
|
9月前
|
Java API 数据库
2025 年最新 Java 实操学习路线,从入门到高级应用详细指南
2025年Java最新实操学习路线,涵盖从环境搭建到微服务、容器化部署的全流程实战内容,助你掌握Java 21核心特性、Spring Boot 3.2开发、云原生与微服务架构,提升企业级项目开发能力,适合从入门到高级应用的学习需求。
2750 0

推荐镜像

更多