Java选手如何看待Golang

写在前面:翻了很多博客,一直没有Java选手转行golang的学习经验贴,思考很久,写下这篇Java选手怎么看待golang这个冉冉新星。

1.走完所有golang基础之后的感受

(1)最大的不适应有这么几点:

  • ---变量定义时,变量类型放在后面,如var temp string;但是Java是在前面,比如int a;
  • ---新建对象时,在go中常用下面这么几种,make()、new()、&引用传递

在新建map、chan、slice时,我们常用make,可以快速创建,并且赋空值,而new和&常用来新建结构体或者接口时使用,可以快速返回创建的对象的引用。

Go 复制代码
	//对象创建
	var temp1 chan string
	temp1 = make(chan string, 2) //带缓冲区
	var temp2 []int
	temp2 = make([]int, 10)
	var temp3 map[int]string
	temp3 = make(map[int]string)
	
	var temp4 *dto.Woman
	temp4 = &dto.Woman{
		Age1:  0,
		Name1: "",
	}
	temp4.Name("嘿嘿嘿")
	
	var temp5 *dto.Men
	temp5 = new(dto.Men)
	temp5.Name("嘎嘎嘎")

	temp1 <- "12"
	temp2[0] = 1
	temp3[12] = "12"
  • ---接口方面,没有严格的implements接口,只是可以新定义struct,然后实现这个接口的所有方法,相当于间接实现了这个接口。
Go 复制代码
	var temp4 dto.Man
	temp4 = &dto.Woman{
		Age1:  0,
		Name1: "",
	}

tips:

Go 的接口变量本身是一个 双字结构(two-word structure),包含:

  1. 动态类型信息(指向具体类型的元数据)
  2. 动态值(指向实际数据的指针或值)

声明接口类型时,无需声明为指针类型,事实上,接口类型可以指向任何类型,本身接口就可以是一个指针类型

(2)基本类型

golang相比Java,缩减了非常多的API,Java里的List,Set,HashMap全被砍掉,数组进行了保留。

Java基础数据类型有8种,int、long、short、float、double、byte、boolean、char

Golang基础数据类型有7种,整型(6种)byte、int、int8、int16、int32、int64、无符号整型(6种):uint、uint8、uint16、uint32、uint64、uintptr,浮点型(2种)float32、float64,复数类型(2种):complex64,complex128、字符:rune、字符串:string

并且Golang还有一些符合数据类型,如指针、数组,切片(类似Python),结构体(可以和C++的结构体类比,但是我把他理解成Java的类),还有并发编程的channel等

tips:go中的数组类型,在 Go 语言中,数组的大小是类型的一部分,因此不同大小的数组是不兼容的,也就是说 5int 和 10int 是不同的类型。

int在Go语言中,会根据机器情况是64位还是32位进行变化,建议显示声明int64还是int32

(3)输入输出流

golang的打印函数和c++、python的比较类似,使用printf打印可以格式化

(4)go的函数调用

**go中,函数不基于结构体而是基于包名调用,方法基于结构体调用。**在go中,没有类的概念,struct就类似于类的概念,方法归属于结构体,只有结构体被实例化时,才能调用这个结构体的方法,但是函数如果首字母大写,通过包名,是能够全局调用到的。

在Java中,是没有函数这个概念的,只有方法的概念,而方法都是归属于类的,同样,方法只有类被实例化后,才能被这个实例调用到。

(5)多线程

Java中线程可以继承Thread,实现Runnalbe接口,或者实现Callable接口,或者从线程池创建。Java 语言里解决并发问题靠的是多线程,但线程是个重量级的对象,不能频繁创建、销毁,而且线程切换的成本也很高,我们可以使用线程池维护一些线程的复用,ThreadPollExcutor,另外针对并发问题,Java有各种工具类,放在concurrent包下,例如countdownlatch,rw锁,concurrenthashmap,CopyOnWriteArrayList,ThreadLocalRandom,wait&notify,await&notifyALL,AtomicInteger在内的各种自增原子类,不得不说,Java的生态真的好,各种工具类用到你手软,而且是内置到JDK中!!!无需你自己去挑第三方包

而且从JDK21后开始全面支持虚拟线程,使用也非常简单,API方法完全一样,只是创建的时候加上ofVirtual,下面给出实例

java 复制代码
public class Main {
    public static void main(String[] args) throws InterruptedException {

        long start = System.currentTimeMillis();
        List<Thread> threads = IntStream.range(0, 1_000_000)
                .mapToObj(i -> Thread.ofVirtual()  // 替换为ofPlatform()测试传统线程
//                .mapToObj(i -> Thread.ofPlatform()  // 替换为ofPlatform()测试传统线程
                        .start(() -> {
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException ignored) {
                            }
                        }))
                .toList();

        for (Thread thread : threads) {
            thread.join();
        }
        long end = System.currentTimeMillis();
        System.out.println("Total time: " + (end - start) + "ms");
    }
}

但是,go的简化设计,虽然没有Java丰富,但是其小而美的设计依然可以满足大部分简单的场景,如下所示。

权限描述符

Java的权限描述符有private,public,protected,default

Golang中则是,如变量,结构体,方法等首字母大写,则其他包都可以访问,首字母小写,则不可访问

2.go几个比较重要的点

(1)select语句

  • 每个 case 都必须是一个通道
  • 所有 channel 表达式都会被求值
  • 所有被发送的表达式都会被求值
  • 如果任意某个通道可以进行,它就执行,其他被忽略。
  • 如果有多个 case 都可以运行,select 会随机公平地选出一个执行,其他不会执行。
    否则:
    1. 如果有 default 子句,则执行该语句。
    2. 如果没有 default 子句,select 将阻塞,直到某个通道可以运行;Go 不会重新对 channel 或值进行求值。

(2)高级特性

  • context.WithCancel、context.WithTimeout可以通过上下文通知其他线程统一进行取消或者进行超时释放。
  • sync.Mutex 提供互斥锁,还有RWMutex读写锁
  • 使用waitGroup+chan+context.WithCancel实现多线程的通信,模拟某一线程报错时,用可取消上下文来终止所有线程的运行
Go 复制代码
func MyWork(ctx context.Context, flag int, wg *sync.WaitGroup, erCh chan error) {

	defer wg.Done()
	for {
		select {
		case _ = <-ctx.Done():
			fmt.Printf("当前线程优雅退出%d\n", flag)
			return
		default:
			if time.Now().UnixNano()%5 == 0 {
				erCh <- errors.New("error happened")
				fmt.Printf("当前线程自行退出%d\n", flag)
				return
			}
			fmt.Println("当前线程正在执行任务{}", flag)
			time.Sleep(1 * time.Second)
		}

	}

}
func main(){
		var erCh chan error
		erCh = make(chan error, 3)
		var wg = &sync.WaitGroup{}
		ctx, cancel := context.WithCancel(context.Background())

		go func() {
			if err := <-erCh; err != nil {
				fmt.Println("发生错误")
				cancel()
			}
		}()
		for i := 0; i < 3; i++ {
			wg.Add(1)
			go sync2.MyWork(ctx, i, wg, erCh)
		}

		wg.Wait()
		close(erCh)
		fmt.Println("所有协程优雅退出")
}

(3)go语言中的参数传递(值传递和指针传递)

Go 复制代码
	var once sync.Once
	var wg *sync.WaitGroup
	wg = &sync.WaitGroup{}
	onceBody := func() {
		fmt.Println("Only execute once")
	}
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			once.Do(onceBody)
		}()
	}
	wg.Wait()
Go 复制代码
	var once sync.Once
	var wg sync.WaitGroup
	onceBody := func() {
		fmt.Println("Only execute once")
	}
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			once.Do(onceBody)
		}()
	}
	wg.Wait()

注意看,两段代码执行都能成功,一个使用了waitGroup的引用,一个是值

但是在多线程中,都能操作统一个waitGroup,这是为什么呢?按理说,值传递的话,wg进行扣除的时候,应该是使用的拷贝副本信息,但依然执行正确

事实上,匿名函数闭包直接引用外部的 wg,Go 会自动处理为指针操作,等效于

Go 复制代码
go func(wgPtr *sync.WaitGroup) {
    defer wgPtr.Done()
}(&wg)

go自动将值传递变成了引用传递

这样的情况也发生在显式参数传递上,例如下面的例子

Go 复制代码
type Person struct {
	age int
}

func (p Person) howOld() int {
	return p.age
}

func (p *Person) growUp() {
	p.age += 1
}

func main() {
	// qcrao 是值类型
	qcrao := Person{age: 18}

	// 值类型 调用接收者也是值类型的方法
	fmt.Println(qcrao.howOld())

	// 值类型 调用接收者是指针类型的方法
	qcrao.growUp()
	fmt.Println(qcrao.howOld())

	// ----------------------

	// stefno 是指针类型
	stefno := &Person{age: 100}

	// 指针类型 调用接收者是值类型的方法
	fmt.Println(stefno.howOld())

	// 指针类型 调用接收者也是指针类型的方法
	stefno.growUp()
	fmt.Println(stefno.howOld())
}

总结就是下面,对于可引用的类型,在转化时可升级成指针

- 值接收者 指针接收者
值类型调用者 方法会使用调用者的一个副本,类似于"传值" 使用值的引用来调用方法,上例中,qcrao.growUp() 实际上是 (&qcrao).growUp()
指针类型调用者 指针被解引用为值,上例中,stefno.howOld() 实际上是 (*stefno).howOld() 实际上也是"传值",方法里的操作会影响到调用者,类似于指针传参,拷贝了一份指针

(4)slice切片

golang里使用slice可变数组来模拟List的效果,底层原理依然是使用数组来实现,slice底层数据结构维护三个字段:引用的数组、slice长度、slice容量,可以通过len()和cap()查切片的长度和容量,可以参考下面示例来理解slice的工作原理

Go 复制代码
	i1 := [5]int{1, 2}
	slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	s1 := slice[2:5]
	s2 := s1[2:6:7]

	s2 = append(s2, 100)
	s2 = append(s2, 200)
	s1[2] = 20

	fmt.Println(i1)
	fmt.Println(s1)
	fmt.Println(s2)
	fmt.Println(slice)

-对于数组i1的打印,直接打印1 2 0 0 0,对于未赋初值的index,默认是0

-对于slice切片,初始化为0 1 2 3 4 5 6 7 8 9,len=10,cap=10

-s1切片,从slice的下标2开始截取,截取长度为5-2=3,s1容量未明确说明,默认使用指向的数组的长度10,所以s1的容量cap为10-2=8,此时如果print的话,应该为2,3,4,但其底层指向的数组实际上是2,3,4,5,6,7,8,9,但并不会被打印出来

-s2切片,从s1的下标2开始截取,截取长度为6-2=4,容量为7-2=5,此时如果print的话,应该为4,5,6,7,实际底层指向的数组为4,5,6,7,8,但并不会被打印出来

-对切片s2进行append100后,容量够用,变为4,5,6,7,100,len=5,cap=5,由于s1,s2,slice都指向同一个底层数组的指针,所以会同步修改slice=0,1,2,3,4,5,6,7,100,9 s1=2,3,4,5,6,7,100,9

-对切片s2再进行append200后,容量不够用,需要扩容,会将原数组复制一份,指向新的数组s2=4,5,6,7,100,200,-,-,-,-,len=6,cap=10,此时,s2不再与s1和slice的底层数组指向同一处。

-s12=20,会同步修改s1和slice,所以s1=2,3,20,5,6,7,100,9,slice=0,1,2,3,20,5,6,7,100,9

运行结果如下,证明猜想正确

(5)for+range遍历

go语言提供了更加全面的遍历方法,使用for+range来遍历数组、slice、map、chan,来获取不同的值,对于map,返回key和value;对于数组和slice,返回index和value;对于chan,返回value 。当然,其实Java借助Lamda表达式,可以更简单的实现所有对象类型的遍历。

Go 复制代码
	var map1 map[string]string
	map1 = make(map[string]string)
	map1["mellowChen"] = "mellowChen"
	map1["yiguangshen"] = "yiguangshen"

	var chan1 chan string
	chan1 = make(chan string, 1)
	chan1 <- "mellowchen"
	close(chan1)

	for index, value := range i1 {
		print(index, value)
	}

	for index, value := range slice {
		print(index, value)
	}

	for key, value := range map1 {
		print(key, value)
	}

	for value := range chan1 {
		print(value)
	}
相关推荐
Scott9999HH5 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔5 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社6 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海6 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖7 小时前
JDK 26 新特性详解
java·开发语言
马优晨7 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区9 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大10 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai10 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说10 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json