Golang与Java各方面使用对比(上)

其实两年前就接触过Golang,但是当时对Golang的理解仅停留在“基本语法”这一块,没有去比较Golang相对于Java的其他差异,后续也因为使用Java作为主力语言而没有再使用过Golang了。现在从几个角度来和Java进行对比,以便更好地使用及理解Golang,希望本文能对刚入门Golang的朋友有所帮助。如有其他疏漏或错误也望大家不吝赐教。

Golang基本的使用可以参考我之前写的笔记:《Golang核心知识总结》

本文只对比基本情况、基本使用、结构体函数及指针三块内容,下一篇文章会对比面向对象、异常处理、并发编程及垃圾回收的差异。

第二篇文章传送门:Golang与Java各方面使用对比(下)

一、基本情况

1.1、Golang基本介绍

在这里插入图片描述

Go语言(或 Golang)起源于 2007 年,并在 2009 年正式对外发布。Go 是非常年轻的一门语言,它的主要目标是“兼具 Python 等动态语言的开发速度和 C/C++ 等编译型语言的性能与安全性”。

Go语言的推出,旨在不损失应用程序性能的情况下降低代码的复杂性,具有“部署简单、并发性好、语言设计良好、执行性能好”等优势,目前国内诸多 IT 公司均已采用Go语言开发项目。

Go语言有时候被描述为“C 类似语言”,或者是“21 世纪的C语言”。Go 从C语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等很多思想,还有C语言一直所看中的编译后机器码的运行效率以及和现有操作系统的无缝适配。

因为Go语言没有类和继承的概念,所以它和 Java 或 C++ 看起来并不相同。但是它通过接口(interface)的概念来实现多态性。Go语言有一个清晰易懂的轻量级类型系统,在类型之间也没有层级之说。因此可以说Go语言是一门混合型的语言。

此外,很多重要的开源项目都是使用Go语言开发的,其中包括 Docker、Go-Ethereum、Thrraform 和 Kubernetes。

据最新的2020年9月Tiobe编程语言排行榜,目前Golang的使用率排名为第11:

在这里插入图片描述

1.2、Golang使用场景
  1. 服务端开发(配合gin、gorm等库就能够完成高性能的后端服务)
  2. 容器开发(譬如Docker、K8s都是基于Golang开发的)
  3. 脚本开发(由于Golang自身部署简单,并且与操作系统API交互方便,所以还可替代Python作为脚本开发)
  4. 底层工具的开发(可代替C或者C++开发操作系统底层工具)

二、基本语法

2.1、编码规约

Golang是一门严格的工程语言,主要体现在编码风格及可见域规则上。在Java中,允许多种编码风格共存,譬如以下两种方法声明,对于Java来说都是允许的:

public String getString(Integer num) {
    return num.toString();
}

public String getString(Integer num) 
{
    return num.toString();
}

1.左右花括号需要符合上下换行风格

在Golang中,只允许出现一种换行风格:

func getString(num int) string {
	return strconv.Itoa(num)
}

如果出现以下换行风格则会报错,无法通过编译:

在这里插入图片描述

2.变量声明后必须使用,不使用需要使用_来代替

在Java中,变量可以声明了却不使用,而Golang中声明的变量必须被使用,否则需要使用_来替代掉变量名,表明该变量不会比使用到:

func getString(num int) string {
	temp := num // 没有使用者,无法编译
	_ := num	// 正常编译
	return strconv.Itoa(num)
}

3.可见域规则

Java对方法、变量及类的可见域规则是通过private、protected、public关键字来控制的,而Golang中控制可见域的方式只有一个,当字段首字母开头是大写时说明其是对外可见的、小写时只对包内成员可见。

  • entity
package entity

type Person struct {
	Name string
	Age int
	id string
}

type student struct {
	detail Person
}

func test() {
	// 本包内可见
	person := &student{detail: Person{
		Name: "ARong",
		Age:  21,
		id:   "211",
	}}
	fmt.Println(person)
}
  • main
package main

import (
	"fmt"
	entity "others/scope"
)

func main() {
	// id字段不可见
	person := &entity.Person{
		Name: "ARong",
		Age:  21,
	}
	fmt.Println(person)
}
2.2、变量声明及初始化

1.变量声明及初始化的文法
在Java中,通常声明变量及初始化的文法为:

    // Object:要声明的类型、v:变量名称、new Object()变量初始化
    Object v = new Object();

而Golang使用var关键字来声明变量:

    // var:变量定义、v1:变量名称、int:变量类型
    var v1 int
	var v2 string
	var v3 [10]int  // 数组
	var v4 []int // 数组切片
	var v5 struct {
		f int
	}
	var v6 *int // 指针
	var v7 map[string]int  // map,key为string类型,value为int类型
	var v8 func(a int) int
	var v9,v10 int //v9和v10都声明为int型

也可以采用:=自动推测变量类型:

	var v1 int = 10 // 正确的使用方式1
	var v2 = 10  // 正确的使用方式2,编译器可以自动推导出v2的类型
	v3 := 10  // 正确的使用方式3,编译器可以自动推导出v3的类型

2.对于基本类型,声明即初始化;对于引用类型,声明则初始化为nil

在Java中,如果在方法内部声明一个变量但不初始化,在使用时会出现编译错误:

public void solve() {
    int num;
    Object object;
    System.out.println(num); // 编译错误
    System.out.println(object); // 编译错误
}

而在Golang中,对于基本类型来讲,声明即初始化;对于引用类型,声明则初始化为nil。这样可以极大地避免NPE的发生。

func main() {
	var num int
	var hashMap *map[string]int
	fmt.Println(num) // num = 0
	fmt.Println(hashMap) //  &hashMap== nil
}
2.3、值类型及引用类型

Golang的类型系统与Java相差不大,但是需要注意的是Java中的数组是属于引用类型,而Golang中的数组属于值类型,当向方法中传递数组时,Java可以直接通过该传入的数组修改原数组内部值(浅拷贝),但Golang则会完全复制出一份副本来进行修改(深拷贝):

  • Java
    public static void main(String[] args) {
        int[] array = {1, 2, 3};
        change(array);
        System.out.println(Arrays.toString(array)); // -1,2,3
    }

    private static void change(int[] array) {
        array[0] = -1;
    }
  • Golang
func main() {
	array := [...]int{1, 2, 3}
	change(array)
	fmt.Println(array) // 1,2,3
}

func change(array [3]int) {
	array[0] = -1
}

并且值得注意的是,在Golang中,只有同长度、同类型的数组才可视为“同一类型”,譬如[2]int和[3]int则会被视为不同的类型,这在参数传递的时候会造成编译错误。

所以在Golang中数组很少被直接使用,更多的是使用切片(基于数组指针)来代替数组。

在Golang中,只有切片、指针、channel、map及func属于引用类型,也就是在传递参数的时候,实质上复制的都是他们的指针,内部的修改会直接影响到外部:

func main() {
	slice := []int{1, 2, 3}
	changeSlice(slice)
	fmt.Println(slice) // -1,2,3

	mapper := map[string]int {
		"num": 0,
	}
	changeMap(mapper)
	fmt.Println(mapper) // num = -1

	array := [...]int{1, 2, 3}
	changePointer(&array)
	fmt.Println(array) // -1,2,3

	intChan := make(chan int, 1)
	intChan <- 1
	changeChannel(intChan)
	fmt.Println(<- intChan) // -1
}

func changeChannel(intChan chan int) {
	<- intChan
	intChan <- -1
}

func changePointer(array *[3]int) {
	array[0] = -1
}

func changeMap(mapper map[string]int) {
	mapper["num"] = -1
}

func changeSlice(array []int) {
	array[0] = -1
}

三、结构体、函数及指针

3.1、结构体声明及使用

在Golang中区别与Java最显著的一点是,Golang不存在“类”这个概念,组织数据实体的结构在Golang中被称为结构体。函数可以脱离“类”而存在,函数可以依赖于结构体来调用或者依赖于包名调用。

Golang中的结构体放弃了继承、实现等多态概念,结构体之间可使用组合来达到复用方法或者字段的效果。

要声明一个结构体只需使用type + struct关键字即可:

type Person struct {
	Name string
	Age  int
	id   string
}

要使用一个结构体也很简单,一般有以下几种方式去创建结构体:

    personPoint := new(entity.Person) // 通过new方法创建结构体指针
	person1 := entity.Person{} // 通过Person{}创建默认字段的结构体
	person2 := entity.Person{ // 通过Person{Name:x,Age:x}创建结构体并初始化特定字段
		Name: "ARong",
		Age:  21,
	}
	fmt.Println(personPoint) // &{ 0 }
	fmt.Println(person1)     // { 0 }
	fmt.Println(person2)	 // {ARong 21 }
3.2、函数和方法的区别

使用Java的朋友应该很少使用“函数”这个词,因为对于Java来说,所有的“函数”都是基于“类”这个概念构建的,也就是只有在“类”中才会包含所谓的“函数”,这里的“函数”被称为“方法”。

而“函数”这个词源于面向过程的语言,所以在Golang中,“函数”和“方法”的最基本区别是:

函数不基于结构体而是基于包名调用,方法基于结构体调用。

下面是一个例子,可以直观地看出方法和函数的区别:

  • entity
package entity

import "fmt"

type Person struct {
	Name string
	Age  int
	id   string
}

// Person结构体/指针可调用的"方法",属于Person结构体
func (p *Person) Solve() {
	fmt.Println(p)
}

// 任何地方都可调用的"函数",不属于任何结构体,可通过entity.Solve调用
func Solve(p *Person) {
	fmt.Println(p)
}
  • main
func main() {
	personPoint := new(entity.Person) // 通过new方法创建结构体指针

	entity.Solve(personPoint) // 函数调用
	
	personPoint.Solve() 	  // 方法调用
}
3.3、指针的使用

在Java中不存在显式的指针操作,而Golang中存在显式的指针操作,但是Golang的指针不像C那么复杂,不能进行指针运算。

下面从一个例子来看Java的隐式指针转化和Golang的显式指针转换:Java和Golang方法传参时传递的都是值类型,在Java中如果传递了引用类型(对象、数组等)会复制其指针进行传递, 而在Golang中必须要显式传递Person的指针,不然只是传递了该对象的一个副本。

Golang使用 * 来定义和声明指针,通过&来取得对象的指针。

func main() {
	p1 := entity.Person{
		Name: "ARong1",
		Age:  21,
	}
	changePerson(p1)
	fmt.Println(p1.Name) // ARong1
	changePersonByPointer(&p1)
	fmt.Println(p1.Name) // ARong2
}

func changePersonByPointer(person *entity.Person) {
	person.Name = "ARong2"
}

func changePerson(person entity.Person) {
	person.Name = "ARong2"
}

注意,如果结构体中需要组合其他结构体,那么建议采用指针的方式去声明,否则会出现更新丢失问题。

以下是Golang方法的一个隐式指针转换,结构体调用方法时,如果传递的是对象,那么会被自动转化为指针调用:


type Person struct {
	Name string
	Age  int
}

// Person结构体/指针可调用的"方法",属于Person结构体
func (p *Person) Solve() {
	fmt.Println(p)
}


func main() {
	p := entity.Person{
		Name: "ARong",
		Age:  21,
	}
	
	pp := &p
	pp.Solve() // 显式
	
	p.Solve    // 隐式,自动将p转化为&p
}
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BoringError

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值