【java工程师快速上手go】一.Go语言基础

目录

写在前面

一、开发环境与工具链

[1.1 Go安装与配置](#1.1 Go安装与配置)

[1.2 GOPATH vs Go Modules](#1.2 GOPATH vs Go Modules)

[1.3 快速体验:Hello World](#1.3 快速体验:Hello World)

二、基础语法对比

[2.1 变量声明](#2.1 变量声明)

[2.2 基本类型](#2.2 基本类型)

[2.3 常量与枚举](#2.3 常量与枚举)

[2.4 流程控制](#2.4 流程控制)

三、函数与方法

[3.1 函数定义](#3.1 函数定义)

[3.2 方法与接收者](#3.2 方法与接收者)

[3.3 指针接收者 vs 值接收者](#3.3 指针接收者 vs 值接收者)

四、数组与切片

[4.1 数组](#4.1 数组)

[4.2 切片](#4.2 切片)

[4.3 append机制](#4.3 append机制)

五、Map与集合

[5.1 Map的基本使用](#5.1 Map的基本使用)

[5.2 并发安全问题](#5.2 并发安全问题)

六、指针详解

[6.1 Go指针 vs Java引用](#6.1 Go指针 vs Java引用)

[6.2 指针的使用场景](#6.2 指针的使用场景)

[6.3 避免指针陷阱](#6.3 避免指针陷阱)

七、总结


写在前面

云原生时代,Go 语言已然成为容器编排、微服务、Service Mesh 等领域的事实标准。对 Java 工程师来说,掌握 Go 不只是多掌握一门语言,更是面向云原生架构的关键能力升级。

Go 的设计理念与 Java 差异显著:它崇尚极简、高效与工程友好,舍弃了 Java 中诸多繁复的语法与特性。为了让你用最短时间、最低成本从 Java 平滑过渡到 Go,本系列博客将全程以 Java 工程师的视角,通过大量对比、对标学习的方式,带你快速上手 Go。

作为系列第一篇,本文将从 Go 基础语法入手,帮你快速建立对 Go 的直观认知,轻松迈出从 Java 转向 Go 的第一步。

一、开发环境与工具链

1.1 Go安装与配置

Go的安装比JDK更简单。从官网下载对应平台的安装包,一路"下一步"即可完成。安装后需要配置两个环境变量:

  • GOROOT:Go的安装路径(类似JAVA_HOME)
  • GOPATH:Go的工作空间(类似Maven的本地仓库)

不过,现代Go开发已经不再强制要求配置这些环境变量,Go会使用默认值。

对比Java

  • Java需要安装JDK,配置JAVA_HOME、CLASSPATH
  • Go只需要安装Go,配置更简单
  • Go的编译速度远快于Java,这是Go的一大优势

1.2 GOPATH vs Go Modules

Go的依赖管理经历了两个阶段:

GOPATH时代

  • 所有项目共享一个工作空间
  • 依赖下载到$GOPATH/src
  • 没有版本管理,类似早期的Java项目

Go Modules时代(Go 1.11+)

  • 每个项目独立管理依赖
  • go.mod文件定义依赖(类似pom.xml)
  • go.sum记录依赖校验(类似Maven的依赖锁定)

对比Maven/Gradle

|------|------------------|----------------------------|---------------|
| 特性 | Go Modules | Maven | Gradle |
| 配置文件 | go.mod | pom.xml | build.gradle |
| 依赖格式 | module@version | groupId:artifactId:version | 同Maven |
| 仓库 | proxy.golang.org | Maven Central | Maven Central |
| 传递依赖 | 自动管理 | 自动管理 | 自动管理 |

Go Modules的配置更简洁,依赖格式也更直观。

1.3 快速体验:Hello World

Go 复制代码
package main

import "fmt"

func main() {
    fmt.Println("Hello, Go!")
}

对比Java

java 复制代码
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, Java!");
    }
}

差异一目了然:

  • Go不需要类,直接定义函数
  • Go的package main表示可执行程序
  • Go的import语法更简洁
  • Go不需要分号(编译器自动添加)

二、基础语法对比

2.1 变量声明

Go的变量声明有多种方式:

Go 复制代码
// 完整声明
var name string = "Go"

// 类型推断
var name = "Go"

// 短变量声明(最常用)
name := "Go"

// 多变量声明
var (
    name   string = "Go"
    age    int    = 10
    active bool   = true
)

对比Java

java 复制代码
String name = "Java";
var name = "Java";  // Java 10+

核心差异

  • Go的类型在变量名之后(var name string),Java在之前(String name
  • Go的:=语法糖可以省略var和类型
  • Go有var()批量声明语法

2.2 基本类型

Go的基本类型非常简洁:

Go 复制代码
bool
string
int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr
byte  // uint8的别名
rune  // int32的别名,表示Unicode码点
float32 float64
complex64 complex128

对比Java

|---------|---------|---------------------|
| Go类型 | Java类型 | 说明 |
| int | int | Go的int根据平台可能是32或64位 |
| int32 | int | Java的int固定32位 |
| float64 | double | Go默认float64 |
| string | String | Go的string是不可变的 |
| bool | boolean | 相同 |

关键差异

  • Go没有包装类型(Integer、Long等),只有基本类型
  • Go的int根据平台自适应,Java的int固定32位
  • Go有复数类型(complex),Java没有

2.3 常量与枚举

Go的常量:

Go 复制代码
const Pi = 3.14159
const (
    StatusOK    = 200
    StatusError = 500
)

Go没有enum关键字,但可以用iota实现枚举:

Go 复制代码
const (
    Sunday = iota  // 0
    Monday         // 1
    Tuesday        // 2
    Wednesday      // 3
    Thursday       // 4
    Friday         // 5
    Saturday       // 6
)

对比Java

java 复制代码
enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY, 
    THURSDAY, FRIDAY, SATURDAY
}

Go的iota更灵活,可以定义复杂的枚举表达式,但不如Java的enum类型安全。

2.4 流程控制

if语句

Go 复制代码
// Go
if age >= 18 {
    fmt.Println("成年")
}

// if语句可以包含初始化语句
if age := getAge(); age >= 18 {
    fmt.Println("成年")
}
java 复制代码
// Java
if (age >= 18) {
    System.out.println("成年");
}

for循环

Go只有for,没有while:

Go 复制代码
// 标准for循环
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

// 类似while
for i < 10 {
    fmt.Println(i)
    i++
}

// 无限循环
for {
    // break退出
}

// for-range遍历
for index, value := range slice {
    fmt.Println(index, value)
}

对比Java

java 复制代码
// 标准for循环
for (int i = 0; i < 10; i++) {
    System.out.println(i);
}

// while循环
while (i < 10) {
    System.out.println(i);
    i++;
}

// 增强for循环
for (String value : list) {
    System.out.println(value);
}

switch语句

Go 复制代码
// Go的switch默认break
switch day {
case "Monday":
    fmt.Println("周一")
case "Friday":
    fmt.Println("周五")
default:
    fmt.Println("其他")
}

// 需要穿透使用fallthrough
switch num {
case 1:
    fmt.Println("1")
    fallthrough
case 2:
    fmt.Println("2")
}

对比Java

java 复制代码
// Java的switch默认穿透
switch (day) {
    case "Monday":
        System.out.println("周一");
        break;
    case "Friday":
        System.out.println("周五");
        break;
    default:
        System.out.println("其他");
}

核心差异

  • Go的if不需要括号,但必须有花括号
  • Go只有for,没有while
  • Go的switch默认break,Java默认穿透
  • Go的switch可以不写表达式,当作if-else使用

三、函数与方法

3.1 函数定义

Go的函数定义:

Go 复制代码
// 单返回值
func add(a, b int) int {
    return a + b
}

// 多返回值(Go的特色)
func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("除数不能为0")
    }
    return a / b, nil
}

// 命名返回值
func calculate(a, b int) (sum, product int) {
    sum = a + b
    product = a * b
    return  // 自动返回sum和product
}

对比Java

java 复制代码
// 单返回值
public int add(int a, int b) {
    return a + b;
}

// 需要返回多个值时,Java需要定义类或使用数组
public class Result {
    public int sum;
    public int product;
}

public Result calculate(int a, int b) {
    Result result = new Result();
    result.sum = a + b;
    result.product = a * b;
    return result;
}

核心优势

  • Go的多返回值避免了定义额外的类
  • 命名返回值让代码更清晰
  • 错误处理更直观(返回error)

3.2 方法与接收者

Go没有类,但可以为类型定义方法:

Go 复制代码
type Person struct {
    Name string
    Age  int
}

// 值接收者
func (p Person) GetName() string {
    return p.Name
}

// 指针接收者
func (p *Person) SetAge(age int) {
    p.Age = age
}

对比Java

java 复制代码
public class Person {
    private String name;
    private int age;
    
    public String getName() {
        return name;
    }
    
    public void setAge(int age) {
        this.age = age;
    }
}

核心差异

  • Go的方法在结构体外部定义
  • Go有值接收者和指针接收者之分
  • 指针接收者可以修改结构体,类似Java的引用传递

3.3 指针接收者 vs 值接收者

Go 复制代码
// 值接收者:不修改原对象
func (p Person) ModifyAge(age int) {
    p.Age = age  // 修改的是副本
}

// 指针接收者:修改原对象
func (p *Person) SetAge(age int) {
    p.Age = age  // 修改的是原对象
}

对比Java

  • Java的方法默认是引用传递(对于对象)
  • Go需要显式选择值接收者或指针接收者
  • 指针接收者更接近Java的行为

四、数组与切片

4.1 数组

Go的数组是固定长度的:

Go 复制代码
// 声明数组
var arr [5]int

// 初始化
arr := [5]int{1, 2, 3, 4, 5}

// 数组长度是类型的一部分
var arr1 [5]int
var arr2 [10]int
// arr1和arr2是不同类型

对比Java

java 复制代码
int[] arr = new int[5];
int[] arr = {1, 2, 3, 4, 5};

核心差异

  • Go的数组长度是类型的一部分([5]int[10]int是不同类型)
  • Go的数组是值类型,传递时会复制
  • Java的数组是引用类型

4.2 切片

切片是Go中最常用的数据结构,是动态数组的实现:

Go 复制代码
// 创建切片
slice := []int{1, 2, 3, 4, 5}

// 使用make创建
slice := make([]int, 5, 10)  // 长度5,容量10

// 从数组创建
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4]  // [2, 3, 4]

// append追加元素
slice = append(slice, 6)
slice = append(slice, 7, 8, 9)

切片的内部结构

复制代码
┌─────────────────────────────────────────┐
│            切片结构                      │
├─────────────────────────────────────────┤
│                                         │
│   ptr ──────▶ 指向底层数组              │
│   len        切片长度                   │
│   cap        切片容量                   │
│                                         │
└─────────────────────────────────────────┘

对比Java的ArrayList

|------|-------|----------------|
| 特性 | Go切片 | Java ArrayList |
| 底层结构 | 数组 | 数组 |
| 扩容机制 | 2倍扩容 | 1.5倍扩容 |
| 类型安全 | 编译时检查 | 编译时检查 |
| 内存布局 | 连续内存 | 连续内存 |

核心差异

  • 切片是对底层数组的引用,多个切片可以共享底层数组
  • 切片的扩容会创建新数组,原切片不受影响
  • 切片更轻量,传递时只传递三个字段(ptr、len、cap)

4.3 append机制

Go 复制代码
slice := make([]int, 0, 5)

// 容量足够,不扩容
slice = append(slice, 1, 2, 3, 4, 5)

// 容量不足,扩容
slice = append(slice, 6)  // 创建新数组,容量翻倍

扩容策略

  • 容量小于1024:翻倍
  • 容量大于等于1024:增长25%

五、Map与集合

5.1 Map的基本使用

Go 复制代码
// 创建map
m := make(map[string]int)

// 初始化
m := map[string]int{
    "apple":  5,
    "banana": 3,
}

// 增删改查
m["orange"] = 10     // 增/改
delete(m, "apple")   // 删
value := m["banana"] // 查

// 检查key是否存在
value, exists := m["grape"]
if exists {
    fmt.Println(value)
}

对比Java

java 复制代码
Map<String, Integer> m = new HashMap<>();

m.put("apple", 5);      // 增/改
m.remove("apple");      // 删
Integer value = m.get("banana");  // 查

// 检查key是否存在
if (m.containsKey("grape")) {
    System.out.println(m.get("grape"));
}

核心差异

  • Go的map通过返回两个值来判断key是否存在
  • Java需要调用containsKey方法
  • Go的map是无序的,Java的HashMap也是无序的

5.2 并发安全问题

Go的map不是并发安全的:

java 复制代码
// 错误:并发读写map会panic
var m = make(map[int]int)

go func() {
    m[1] = 100  // 写
}()

go func() {
    _ = m[1]    // 读
}()

解决方案

  • 使用sync.RWMutex加锁
  • 使用sync.Map(Go 1.9+)

对比Java

  • Java的ConcurrentHashMap是线程安全的
  • Go需要手动处理并发安全

六、指针详解

6.1 Go指针 vs Java引用

Go有指针,但没有指针运算:

Go 复制代码
// 取地址
x := 10
p := &x  // p是指向x的指针

// 解引用
fmt.Println(*p)  // 10

// 通过指针修改
*p = 20
fmt.Println(x)   // 20

对比Java

java 复制代码
// Java只有引用,没有指针
Person p1 = new Person();
Person p2 = p1;  // p2和p1指向同一个对象

p2.setName("Go");
System.out.println(p1.getName());  // "Go"

核心差异

  • Go的指针可以获取变量的地址
  • Go的指针可以解引用获取值
  • Java的引用不能获取地址,也不能解引用
  • Go的指针不能进行算术运算(比C安全)

6.2 指针的使用场景

场景1:修改函数参数

Go 复制代码
func increment(num *int) {
    *num++
}

x := 10
increment(&x)
fmt.Println(x)  // 11

对比Java

java 复制代码
// Java无法直接修改基本类型参数
public void increment(int num) {
    num++;  // 只修改了副本
}

int x = 10;
increment(x);
System.out.println(x);  // 仍然是10

// 需要使用包装类或数组
public void increment(int[] num) {
    num[0]++;
}

场景2:避免大对象复制

Go 复制代码
type BigStruct struct {
    Data [1000000]int
}

func process(s *BigStruct) {
    // 传递指针,避免复制
}

对比Java

  • Java对象默认是引用传递,不存在复制问题
  • Go需要显式使用指针来避免复制

6.3 避免指针陷阱

陷阱1:循环变量地址

Go 复制代码
// 错误:所有指针指向同一个地址
var ptrs []*int
for i := 0; i < 3; i++ {
    ptrs = append(ptrs, &i)
}

// 正确:每次循环创建新变量
for i := 0; i < 3; i++ {
    i := i  // 创建新变量
    ptrs = append(ptrs, &i)
}

陷阱2:nil指针

Go 复制代码
var p *int
fmt.Println(*p)  // panic: nil pointer dereference

// 正确:检查nil
if p != nil {
    fmt.Println(*p)
}

七、总结

Go语言的基础语法相比Java更加简洁:

设计哲学差异

  • Go追求简洁,Java追求严谨
  • Go只有25个关键字,Java有50个
  • Go的编译速度远快于Java

核心语法差异

  • 变量声明:Go的类型在变量名之后
  • 函数:Go支持多返回值
  • 数组:Go的数组长度是类型的一部分
  • 切片:Go的动态数组实现
  • 指针:Go有指针,Java只有引用

思维转换

  • 从面向对象思维转向组合式设计
  • 从异常处理转向错误返回值
  • 从继承转向接口组合

掌握这些基础语法后,你已经可以编写简单的Go程序了。下一篇,我们将深入Go的高级特性,包括面向对象、并发编程、错误处理等,理解Go的设计哲学。

相关推荐
2601_950703942 小时前
Spring IoC入门实战:XML与注解双解
java
带刺的坐椅2 小时前
Snack JSONPath 项目架构分析
java·json·java8·jsonpath
飞Link2 小时前
【AI大模型实战】万字长文肝透大语言模型(LLM):从底层原理解析到企业级Python项目落地
开发语言·人工智能·python·语言模型·自然语言处理
妙蛙种子3112 小时前
【Java设计模式 | 创建者模式】 原型模式
java·开发语言·后端·设计模式·原型模式
LlNingyu2 小时前
Go 实现无锁环形队列:面向多生产者多消费者的高性能 MPMC 设计
开发语言·golang·队列·mpmc·数据通道
Lyyaoo.2 小时前
【JAVA基础面经】线程的状态
java·开发语言
Hello小赵2 小时前
C语言如何自定义链接库——编译与调用
android·java·c语言
John.Lewis3 小时前
C++进阶(8)智能指针
开发语言·c++·笔记
希望永不加班3 小时前
SpringBoot 配置绑定:@ConfigurationProperties
java·spring boot·后端·spring