工程师,设计师,异想天开症患者

Java程序员转型Go语言随笔 - 基础篇

这是一篇关于go的随笔,旨在记录Java程序转型Go过程中的一些感悟,会记录Go语言一些与Java的差异,一些自己觉得优秀的设计思路,以及Go语言的坑点。

关于设计思想

编译速度快

Java通常来说在编译过程中需要遍历依赖链中所有依赖的库。Golang编译器只会关注那些直接被引用的库。 通常来说Go的编译速度不到1秒,在研发过程中更具有优势。

并发

原生的go语言就是为并发设计的,goroutine(类似线程)和channel(线程间的数据共享)的设计方式让程序员可以更轻松的写出处理高并发且线程安全的代码。但通常如果只是写业务代码的话,其实用到的不太多。如果开发框架可以认真看看

面向对象和类型系统

面向对象设计的三大特性封装,继承,多态在Java语言中都有体现。但在Go语言中,原生设计舍弃了继承而使用了组合这个思路。 通常我们在Java讲面向对象的教程中,如果我们设计一个车,那么就有如下的关系。但在Go语言中,由于组合的特性,Go语言更倾向于对行为进行建模。在设计过程中更加简单灵活,在使用领域驱动设计的方法时更贴合。

avatar

下面关于接口的说明会更加详细的说明这个设计方式

代码格式化

个人觉得这是Golang的一个亮点。 之前Java格式到底何时换行,是否空行等等每个人有每个人的看法。有闲心调格式的时候还行,一旦项目时间紧张,可能最多用IDE直接格式化一下就完了。如果有人不做,那么Java代码就是一团乱,看着闹心。

go fmt直接提供了代码格式化能力,强制大家用一样的代码风格,阅读性更强。

关于代码结构

GOPATH和GOROOT

文件组织

从以类为最小访问单元转向以包为最小访问单元

Java中通常以类为访问的最小单元,如XXXService.getXxx(), XXXController.updateXxx() 在Golang中,通常以包为访问的最小单元。如util里两个go文件

util
|--collections.go
|--strings.go
func main() {

	if !util.IsBlank("xxx") { // 用包名访问方法
		fmt.Println("字符串不为空")
	}

	if util.IsEmpty(map[string]string{}) { // 包名访问方法
		fmt.Println("映射为空") 
	}
}

语法特性

定义、赋值

类型放在变量后

go语言对定义和赋值语法做了较多的精简,通常来说把对象放在前面,类型放在后面,如:

var str string // var表示定义一个变量,类型是string

func hello(str string) {    //函数入参也可以这么定义
  fmt.Printf("%s str", str)
}

初始化变量

变量初始化通常有两种方式,第一种方式通常用来定义一个空值。这种方式产生的变量并不是像java一样是空指针

var str string  // 初始化为""
var isNow bool  // 初始化为false
var num int     // 初始化为0
var u user      // 对user结构体里面的每一个基本对象按照上述类型进行初始化

// 一次定义多个
var (
  hello string
  num   int
  isNow bool
)

第二种方式通常用来定义一个带初始化的值

hello := "hello"
num := 123
isNow := true
u := user{
  Name: "zhang",
  Age: 18,
}

一旦使用:=对对象赋值之后,就不能再次使用:=了,否则会编译失败。可以使用=对对象进行修改

条件/循环

if/for后面的bool表达式不再需要加括号

if err!=nil {
  fmt.Println(err)
}

if后面的bool表达式可以使用初始化语句

// 如果打开文件失败,那么打印错误
if file, err := os.OpenFile(file); err != nil {
  fmt.Println(err) 
}

for循环的range关键字

类似java中for循环的:用法

// java中
for(User user : users) {
  System.out.println(user)
}

// go中,增加了index表示当前下标
for index, user := range users {
  fmt.Printf("index:%d user:%v\n", index, user)
}

访问控制,开头第一个字母大写=加了public关键字,否则就是包内访问

  • 由于Golang中没有继承,所以就不存在protected这个概念
  • 如果是在同一个包中,所有的函数、方法和变量都是可以访问的。
  • 针对不同的包,如果函数、方法和变量的第一个字母是大写,就可以在其他包中访问。

关于基本类型

bool、int、float、string这四种类型跟java中基本类似,但没有java中的包装类型

集合

数组与Java中的数组基本没区别

特点:定长、访问速度快、连续存储。函数间传递是值传递 定义有三种类型

var nums [3]int // 定义

nums := [3]int{1,2,3} // 初始化

nums := [...]int{1,2,3} // 懒得填长度的初始化

切片,动态数组,与List类似

特点:不定长,动态扩容,容量小于1000时翻倍,大于1000增长因子1.25。函数间传递是引用传递 实例

var nums []int // 跟数组类似,只要不写[]中的数字即可
nums := make([]int, 3) // 另一种定义方式,make是一个关键字
nums := []int{1,2,3} // 初始化方法

nums = append(nums, 4) // 添加,没有删除

newnums = nums[1:3] // 序号从1-3的子切片

切片有长度容量两个不同的概念,可以用len()cap()分别获取

  • 长度:表示当前切片中又多少个元素
  • 容量:当前切片中最大能放多少个元素

有啥用?只有长度达到容量上限时,才会触发扩容。扩容会有性能损耗,所以在初始化时,可以预估一定范围内的容量,尽量少扩容。

切片是由三部分组成

  1. 数组指针
  2. 长度
  3. 容量

与数组不同的是在复制切片时,通常都是引用复制

映射,与map类似

特点:键值对,无序,函数间传递是引用传递 定义方式

// 定义
var dict map[string]string // Map<String,String>
dict := make(map[string]string)
// 初始化
dict := map[string]string {"name":"zhang","address":"12"}

dict["hello"]="world" //增加,修改

delete(dict, "hello") //删除

对象

在Java中除了基本类型,所有类型都是Object类的子类。我们之前提过,在Go中没有继承,而使用组合的方式,所以Go中所有对象都是由各种基本类型组成的结构体,以及结构体组成的结构体

// 结构定义
type user struct {
  name  string
  email string 
}

// 组合结构体
type admin struct {
  user
  string level
}

var lisa user  // 定义
var fred admin // 定义

// 初始化
lisa := user {
  name:  "lisa",
  email: "lisa@email.com",
}

// 初始化
fred := admin {
  user:  user {
    name:  "fred",
    email: "fred@email.com",
  },
  level: "super",
}

关于接口