来Javaer,学学go吧(三)

简介: 在学go的过程中,也是一脸蒙蔽,语法和java有很大区别也很随意,直到学到goroutine,实现并发编程太方便简洁了,真香!

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())
}


相关文章
来Javaer,学学go吧(四)
在学go的过程中,也是一脸蒙蔽,语法和java有很大区别也很随意,直到学到goroutine,实现并发编程太方便简洁了,真香!
93 5
|
消息中间件 缓存 运维
|
架构师 Java 程序员
慢聊Go之Javaer转型|Go主题月
慢聊Go之Javaer转型|Go主题月
115 0
慢聊Go之Javaer转型|Go主题月
|
SQL Java 数据库连接
javaer to go之简单的ORM封装
身为一个做企业级开发的javaer,习惯使用hibernate、ibatis等ORM框架来操作数据库。虽然也发现golang也有ORM框架,像beego ORM等。 为了熟悉golang的一些特性,我还是觉得自己封装一个ORM。 <h1 id="1struct与interface
3467 0
|
网络协议 Java Go
javaer to go之TCP Socket与Goroutine
<div class="markdown_views"> <h1 id="1前言"> 1、前言</h1> <p>其实我前面一篇笔记的例子就是socket的一个例子,但是由于大部分的笔记说明都是在整理基础的东西,所以socket的笔记单独列在这里。</p> <p>server.go</p> <pre class="prettyprint"><code class=" h
2314 0
|
Go
javaer to go之byte类型转换
<div class="markdown_views"> <p>在Socket的Server和Client通信的过程中,传输的都是字节。而我们需要展示和使用的是字符串、整形等。这个时候,我们需要对字节进行处理,把byte类型的数据转成我们需要的类型。</p> <h2 id="1byte与16进制字符串">1、[]byte与16进制字符串</h2> <ul> <li>[]by
2162 0
|
SQL 关系型数据库 MySQL
javaer to go之mysql操作
<div class="markdown_views"> <p>经过度娘后,发现比较常用golang mysql驱动包有两种mymysql和go-sql-driver/mysql。个人觉得mymysql这个名字起得不够文雅。所以我选择了go-sql-driver/mysql。</p> <h2 id="1go-sql-drivermysql的安装">1、go-sql-drive
2051 0
|
Java Go 网络协议
javaer to go之基础
<div class="markdown_views"> <h2 id="1开始">1、开始</h2> <p>我是一个javaer,最近空闲时间在学习golang。</p> <p>度娘后,安装好Go环境和LiteIDE后,一开始我也没从基础开始看,而是想把现有的java项目改成是golang版本的。</p> <p>原项目内容:</p> <ol> <li>socket模块
1933 0
|
1天前
|
存储 安全 编译器
go语言中进行不安全的类型操作
【5月更文挑战第10天】Go语言中的`unsafe`包提供了一种不安全但强大的方式来处理类型转换和底层内存操作。包含两个文档用途的类型和八个函数,本文也比较了不同变量和结构体的大小与对齐系数,强调了字段顺序对内存分配的影响。
38 8
go语言中进行不安全的类型操作