20.Go的内置函数和Java的默认导入包java.lang.*
为了在Java中快速开发,Java语言的创造者把一些常用的类和接口都放到到java.lang包下,lang包下的特点就是不用写import语句导入包就可以用里面的程序代码。
Go中也有类似的功能,叫做Go的内置函数,Go的内置函数是指不用导入任何包,直接就可以通过函数名进行调用的函数。
Go中的内置函数有:
close 关闭channel len 求长度 make 创建slice,map,chan对象 append 追加元素到切片(slice)中 panic 抛出异常,终止程序 recover 尝试恢复异常,必须写在defer相关的代码块中
参考示例代码1:
package main import "fmt" func main() { array := [5]int{1,2,3,4,5} str := "myName" //获取字符串长度 fmt.Println(len(str)) //获取数组长度 fmt.Println(len(array)) //获取切片长度 fmt.Println(len(array[1:])) //make创建channel示例 intChan := make(chan int,1) //make创建map示例 myMap := make(map[string]interface{}) //make创建切片 mySlice := make([]int,5,10) fmt.Println(intChan) fmt.Println(myMap) fmt.Println(mySlice) //关闭管道 close(intChan) //为切片添加元素 array2 := append(array[:],6) //输出 fmt.Println(array2) //new案例 num := new(int) fmt.Println(num) }
参考示例代码2:panic和recover的使用
他们用于抛出异常和尝试捕获恢复异常
func func1() { fmt.Println("1") } func func2() { // 刚刚打开某资源 defer func() { err := recover() fmt.Println(err) fmt.Println("释放资源..") }() panic("抛出异常") fmt.Println(2") } func func3() { fmt.Println("3") } func main() { func1() func2() func3() }
Java中的java.lang包下具体有什么在这里就不赘述了,请参考JavaAPI文档:
JavaAPI文档导航:https://www.oracle.com/cn/java/technologies/java-se-api-doc.html
21.Go的标准格式化输出库fmt和java的输出打印库对比
Java的标准输出流工具类是java.lang包下的System类,具体是其静态成员变量PrintStream类。
他有静态三个成员变量:
分别是PrintStream类型的out,in,err
我们常见System.out.println(),实际上调用的就是PrintStream类对象的println方法。
Go中的格式化输出输入库是fmt模块。
fmt在Go中提供了输入和输出的功能,类型Java中的Scanner和PrintStream(println)。
它的使用方法如下:
Print: 原样输出到控制台,不做格式控制。 Println: 输出到控制台并换行 Printf : 格式化输出(按特定标识符指定格式替换) Sprintf:格式化字符串并把字符串返回,不输出,有点类似于Java中的拼接字符串然后返回。 Fprintf:来格式化并输出到 io.Writers 而不是 os.Stdout
详细占位符号如下:
代码示例如下:
22.Go的面向对象相关知识
1.封装属性(结构体)
Go中有一个数据类型是Struct,它在面向对象的概念中相当于Java的类,可以封装属性和封装方法,首先看封装属性如下示例:
package main import "fmt" //示例 type People struct { name string age int sex bool } func main(){ //示例1: var l1 People l1.name = "li_ming" l1.age = 22 l1.sex = false //li_ming fmt.Println(l1.name) //示例2 var l2 *People = new(People) l2.name = "xiao_hong" l2.age = 33 l2.sex = true //xiao_hong xiao_hong fmt.Println(l2.name,(*l2).name) //示例3: var l3 *People = &People{ name:"li_Ming",age:25,sex:true} //li_Ming li_Ming fmt.Println(l3.name,(*l3).name) }
2.封装方法(方法接收器)
如果想为某个Struct类型添加一个方法,参考如下说明和代码:
go的方法和Java中的方法对比,go的函数和go方法的不同
Go中的函数是不需要用结构体的对象来调用的,可以直接调用
Go中的方法是必须用一个具体的结构体对象来调用的,有点像Java的某个类的对象调用其方法
我们可以把指定的函数绑定到对应的结构体上,使该函数成为这个结构体的方法,然后这个结构体的对象就可以通过.来调用这个方法了
绑定的形式是:在func和方法名之间写一个(当前对象变量名 当前结构体类型),这个叫方法的接受器,其中当前对象的变量名就是当前结构体调用该方法的对象的引用,相当于java中的this对象。
参考如下示例为Student学生添加一个learn学习的方法
package main import "fmt" type Student struct { num int //学号 name string //姓名 class int //班级 sex bool //性别 } //给Student添加一个方法 //这里的(stu Student)中的stu相当于java方法中的this对象 //stu是一个方法的接收器,接收是哪个对象调用了当方法 func (stu Student) learn() { fmt.Printf("%s学生正在学习",stu.name) } func main() { stu := Student{1,"li_ming",10,true} stu.learn() }
方法的接收器也可以是指针类型的
参考如下案例:
package main import "fmt" type Student struct { num int //学号 name string //姓名 class int //班级 sex bool //性别 } //这里方法的接收器也可以是指针类型 func (stu *Student) learn() { fmt.Printf("%s学生正在学习",stu.name) } func main() { //指针类型 stu := &Student{1,"li_ming",10,true} stu.learn() }
注意有一种情况,当一个对象为nil空时,它调用方法时,接收器接受的对于自身的引用也是nil,需要我们做一些健壮性的不为nil才做的判断处理。
3.Go的继承(结构体嵌入)
Go中可以用嵌入结构体实现类似于继承的功能:
参考如下代码示例:
package main import "fmt" //电脑 type Computer struct { screen string //电脑屏幕 keyboard string //键盘 } //计算方法 func (cp Computer) compute(num1,num2 int) int{ return num1+num2; } //笔记本电脑 type NoteBookComputer struct{ Computer wireless_network_adapter string //无线网卡 } func main() { var cp1 NoteBookComputer = NoteBookComputer{} cp1.screen = "高清屏" cp1.keyboard = "防水键盘" cp1.wireless_network_adapter = "新一代无线网卡" fmt.Println(cp1) fmt.Println(cp1.compute(1,2)) }
需要注意的是,Go中可以嵌入多个结构体,但是多个结构体不能有相同的方法,如果有参数和方法名完全相同的方法,在编译的时候就会报错。所以Go不存在嵌入多个结构体后,被嵌入的几个结构体有相同的方法,最后不知道选择执行哪个方法的情况,多个结构体方法相同时,直接编译就会报错。
参考如下示例:
package main import "fmt" func main() { man := Man{} fmt.Println(man) //下面的代码编译会报错 //man.doEat() } type Man struct { FatherA FatherB } func (p FatherA) doEat() { fmt.Printf("FatherA eat") } func (t FatherB) doEat() { fmt.Printf("FatherB eat") } type FatherB struct { } type FatherA struct { }
4.Go的多态(接口)
接下来我们讲Go中如何通过父类接口指向具体实现类对象,实现多态:
go语言中的接口是非侵入式接口。
java语言中的接口是侵入式接口。
侵入式接口是指需要显示的在类中写明实现哪些接口。
非侵入式接口是指不要显示的在类中写明要实现哪些接口,只需要方法名同名,参数一致即可。
参考如下代码示例:接口与多态
package main import "fmt" //动物接口 type Animal interface{ eat() //吃饭接口方法 sleep() //睡觉接口方法 } //小猫 type Cat struct { } //小狗 type Dog struct { } //小猫吃方法 func (cat Cat) eat() { fmt.Println("小猫在吃饭") } //小猫睡方法 func (cat Cat) sleep(){ fmt.Println("小猫在睡觉") } //小狗在吃饭 func (dog Dog) eat(){ fmt.Println("小狗在吃饭") } //小狗在睡觉 func (dog Dog) sleep(){ fmt.Println("小狗在睡觉") } func main() { var cat Animal = Cat{} var dog Animal = Dog{} cat.eat() cat.sleep() dog.eat() dog.sleep() }
接口可以内嵌接口
参考如下代码示例:
package main //内嵌接口 //学习接口,内嵌听和看学习接口 type Learn interface { LearnByHear LearnByLook } //通过听学习接口 type LearnByHear interface { hear() } //通过看学习 type LearnByLook interface { look() }
23.Go语言中线程的实现和Java语言中线程的实现
go中的线程相关的概念是Goroutines(并发),是使用go关键字开启。
Java中的线程是通过Thread类开启的。
在go语言中,一个线程就是一个Goroutines,主函数就是(主) main Goroutines。
使用go语句来开启一个新的Goroutines
比如:
普通方法执行
myFunction()
开启一个Goroutines来执行方法
go myFunction()
java中是
new Thread(()->{ //新线程逻辑代码 }).start();
参考下面的代码示例:
package main import ( "fmt" ) //并发开启新线程goroutine测试 //我的方法 func myFunction() { fmt.Println("Hello!!!") } //并发执行方法 func goroutineTestFunc() { fmt.Println("Hello!!! Start Goroutine!!!") } func main() { /* myFunction() //go goroutineTestFunc() //此时因为主线程有时候结束的快,goroutineTestFunc方法得不到输出,由此可以看出是开启了新的线程。 */ //打开第二段执行 /* go goroutineTestFunc() time.Sleep(10*time.Second)//睡一段时间 10秒 myFunction() */ }
线程间的通信:
java线程间通信有很多种方式:
比如最原始的 wait/notify
到使用juc下高并发线程同步容器,同步队列
到CountDownLatch等一系列工具类
......
甚至是分布式系统不同机器之间的消息中间件,单机的disruptor等等。
Go语言不同,线程间主要的通信方式是Channel。
Channel是实现go语言多个线程(goroutines)之间通信的一个机制。
Channel是一个线程间传输数据的管道,创建Channel必须声明管道内的数据类型是什么
下面我们创建一个传输int类型数据的Channel
代码示例:
package main import "fmt" func main() { ch := make(chan int) fmt.Println(ch) }
channel是引用类型,函数传参数时是引用传递而不是值拷贝的传递。
channel的空值和别的应用类型一样是nil。
==可以比较两个Channel之间传输的数据类型是否相等。
channel是一个管道,他可以收数据和发数据。
具体参照下面代码示例:
package main import ( "fmt" "time" ) //channel发送数据和接受数据用 <-表示,是发送还是接受取决于chan在 <-左边还是右边 //创建一个传输字符串数据类型的管道 var chanStr = make(chan string) func main() { fmt.Println("main goroutine print Hello ") //默认channel是没有缓存的,阻塞的,也就是说,发送端发送后直到接受端接受到才会施放阻塞往下面走。 //同样接收端如果先开启,直到接收到数据才会停止阻塞往下走 //开启新线程发送数据 go startNewGoroutineOne() //从管道中接收读取数据 go startNewGoroutineTwo() //主线程等待,要不直接结束了 time.Sleep(100*time.Second) } func startNewGoroutineOne() { fmt.Println("send channel print Hello ") //管道发送数据 chanStr <- "Hello!!!" } func startNewGoroutineTwo(){ fmt.Println("receive channel print Hello ") strVar := <-chanStr fmt.Println(strVar) }
无缓存的channel可以起到一个多线程间线程数据同步锁安全的作用。
缓存的channel创建方式是
make(chan string,缓存个数)
缓存个数是指直到多个数据没有消费或者接受后才进行阻塞。
类似于java中的synchronized和lock
可以保证多线程并发下的数据一致性问题。
首先我们看一个线程不安全的代码示例:
package main import ( "fmt" "time" ) //多线程并发下的不安全问题 //金额 var moneyA int =1000 //添加金额 func subtractMoney(subMoney int) { time.Sleep(3*time.Second) moneyA-=subMoney } //查询金额 func getMoney() int { return moneyA; } func main() { //添加查询金额 go func() { if(getMoney()>200) { subtractMoney(200) fmt.Printf("200元扣款成功,剩下:%d元\n",getMoney()) } }() //添加查询金额 go func() { if(getMoney()>900) { subtractMoney(900) fmt.Printf("900元扣款成功,剩下:%d元\n",getMoney()) } }() //正常逻辑,只够扣款一单,可以多线程环境下结果钱扣多了 time.Sleep(5*time.Second) fmt.Println(getMoney()) }
缓存为1的channel可以作为锁使用:
示例代码如下:
package main import ( "fmt" "time" ) //多线程并发下使用channel改造 //金额 var moneyA = 1000 //减少金额管道 var synchLock = make(chan int,1) //添加金额 func subtractMoney(subMoney int) { time.Sleep(3*time.Second) moneyA-=subMoney } //查询金额 func getMoney() int { return moneyA; } func main() { //添加查询金额 go func() { synchLock<-10 if(getMoney()>200) { subtractMoney(200) fmt.Printf("200元扣款成功,剩下:%d元\n",getMoney()) } <-synchLock }() //添加查询金额 go func() { synchLock<-10 if(getMoney()>900) { subtractMoney(900) fmt.Printf("900元扣款成功,剩下:%d元\n",getMoney()) } synchLock<-10 }() //这样类似于java中的Lock锁,不会扣多 time.Sleep(5*time.Second) fmt.Println(getMoney()) }
go也有互斥锁
类似于java中的Lock接口
参考如下示例代码:
package main import ( "fmt" "sync" "time" ) //多线程并发下使用channel改造 //金额 var moneyA = 1000 var lock sync.Mutex; //添加金额 func subtractMoney(subMoney int) { lock.Lock() time.Sleep(3*time.Second) moneyA-=subMoney lock.Unlock() } //查询金额 func getMoney() int { lock.Lock() result := moneyA lock.Unlock() return result; } func main() { //添加查询金额 go func() { if(getMoney()>200) { subtractMoney(200) fmt.Printf("200元扣款成功,剩下:%d元\n",getMoney()) }else { fmt.Println("余额不足,无法扣款") } }() //添加查询金额 go func() { if(getMoney()>900) { subtractMoney(900) fmt.Printf("900元扣款成功,剩下:%d元\n",getMoney()) }else { fmt.Println("余额不足,无法扣款") } }() //正常 time.Sleep(5*time.Second) fmt.Println(getMoney()) }