I was once a Java developer. I did development works of JavaWeb and turned to Android later. I still couldn't do without Java until I turned to the frontend. I have been using JavaScript (JS) all the time. Now, due to personal development, I have learned the Go language on Alibaba Cloud because of the needs of the project. Many years of programming experience made me realize that a language is only a tool, and the important thing is its thought and logic. So, I just need to learn the syntax. I spent three days studying Go and compared it mainly with Java and JS. The difference in the syntax almost made me give up! It is not enough to learn the syntax, as many Go design concepts are also important. As the saying goes, a good memory is no better than a good habit of making notes. One way to show you understand something is to learn it and teach it to someone else. Therefore, I'd like to introduce what I have learned about the Go language.
Go was developed by Ken Thompson, Rob Pike, and Robert Griesemer. It originated in 2007 and was officially released to the public in 2009. It was originally designed to meet Google's needs. The main goal of Go is to "combine the development speed of dynamic languages (such as Python) with the performance and security of compilation languages (such as C/C++.)" It aims to reduce code complexity without sacrificing application performance with advantages, such as *"simple deployment, good concurrency, good language design, and high execution performance." The main focus is concurrency, which is based on goroutine. Goroutine is similar to thread, but it is not thread. Goroutine can be regarded as a virtual thread. The Go runtime schedules goroutine and allocates it to each CPU to maximize CPU performance.
When using Java, we need to download JDK. Similarly, when we use Go for development, we also need to download various develop-kit, libraries, and compilers provided by Go. We can download the .pkg file for macOS from the official website and install it directly. Then, verify the version using the Go version command:
Next, set these two environment variables. For macOS, they are in the .bash_profile file:
export GOROOT=/usr/local/go
export GOPATH=$HOME/go
GOROOT
: This indicates the installation path of Go language compilation, tools, standard libraries, etc., which is equivalent to configuring JAVA_HOME.GOPATH
: This is a little different from Java, and it does not need to be set in Java. This variable indicates the working directory of Go and is global. When the Go command is executed, it depends on this directory. It is equivalent to a global workspace. Generally, you can set $GOPATH/bin to
the PATH directory so the compiled code can be executed directly.You can write code and store the code in any place. For example, create a directory named helloworld
and create a file named hello.go
:
package main
import "fmt"
func main() {
fmt.Println("hello, world")
}
Run go build hello.go
to compile the hello file and run the code in ./hello
. The Go run hello.go
command can also be used together. This command does not require setting environment variables. It looks similar to C, but it is not like Java. It does not need a virtual machine when running. Early Go projects perform compilation using Makefile. Later, powerful commands like Go build and Go run were provided to identify directories and files directly.
Automatic import is super cool! The combination of the + /
command is no longer needed!
To run a project, you must set build config, which is similar to Android or Java projects. For example, create a hello-goland project:
You must select this option when importing a Go module project. Otherwise, you cannot download dependencies synchronously like in Maven and Gradle:
Search for the Go plug-in directly. The first one that is installed by most users is the best. I haven't used it yet, so I'm not sure about the performance.
When setting the GOPATH environment variable, this directory is divided into three subdirectories: bin, pkg, and src, which are used to store executable files, package files, and source code files, respectively. When we execute a Go command, if we specify a directory that is not for the current file or with an absolute path, the command will find the directory in the GOPATH directory. As such, after the xxx directory is created in the GOPATH directory, we can run the Go build xx command anywhere to build or run the code.
The .pkg directory stores the package files, including .a files, generated after the Go install command. It serves as an archive.
├── bin
│ ├── air
│ ├── govendor
│ ├── swag
│ └── wire
├── pkg
│ ├── darwin_amd64
│ ├── mod
│ └── sumdb
└── src
├── calc
├── gin-blog
├── github.com
├── golang.org
├── google.golang.org
├── gopkg.in
└── simplemath
This is not good for specific projects. There is no Workspace to isolate each project, so I think this GOPATH directory should store public projects, such as projects with open-source dependencies. During the development process, many dependencies are downloaded and mixed with the project files.
In addition, the project GOPATH can be set through IDE, which is equivalent to adding a directory variable to GOPATH when executing. In other words, we create a project. Then, there are bin, src, and pkg directories. This is the same as GOPATH. In essence, IDE sets GOPATH when running:
GOPATH=/Users/fuxing/develop/testgo/calc-outside:/Users/fuxing/develop/go #gosetup
The Go language will check the GOPATH system environment variable first while looking for variables, functions, class properties, and methods. Then, it searches for the corresponding directories in the src directory under the corresponding path based on the package name in sequence according to the path list configured with this variable. If the corresponding directory exists, it searches for variables, functions, class properties, and methods in the directory.
The Go Module's officially provided solution is better.
Starting from Go 1.11, Go Modules are provided to manage projects and dependencies. Starting from Go 1.13, Go Modules support is enabled by default. There are several advantages to using Go Modules. You no longer need to rely on GOPATH, and you can create a Go project anywhere. In China, you can configure the image source through GOPROXY to accelerate the download of dependency packages. In other words, a project is a mod, which is the case for Go open-source projects. It is similar to Maven and Gradle.
// Create a mod project. IDE can also be used to new the mod project:
go mod init calc-mod
// The general name of open-source project on GitHub is like this. Unlike Maven and Gradle, the project is not required to be published to the warehouse after development! It is enough to mark with tag after the code is submitted.
go mod init github.com/fuxing-repo/fuxing-module-name
// Create a module: The execution of this command generates a go.mod file, which contains only one line of content:
module calc-mod
// After being imported, run the command to download dependencies without editing the go.mod file. Dependencies are downloaded to the GOPATH/pkg/mod directory.
go list
If you use GoLand to open different projects, the external dependent libraries are different. If the project is created using GOPATH, you need to download the dependency package to GOPATH by running the commands below:
go get -u github.com/fuxing-repo/fuxing-module-name
The package name in Java is usually very long (corresponding to the folder name), and it serves as a namespace. When introducing it, you need to write a long string. Wildcards can also be used.
The general package name in Go is the current folder name. Same package names can exist in the same project. If packages with the same name need to be referenced at the same time, an alias can be used to distinguish them, which is similar to JS. Generally, a package is imported; while in Java, a specific class is imported. In the same package, files may be different, but the contents can be used and do not need to be imported. This is somewhat similar to the include
command in C. For multiple rows, use parentheses when wrapping.
In Go, the visibility of variables, functions, class properties, and methods is associated with the package. In Java, the visibility of class properties and methods is encapsulated in the corresponding classes and described by keywords, including private, protected, and public. The Go language does not have these keywords. Just like for variables and functions, for custom classes in Go, the visibility of properties and methods is determined by the initial case of the name. If capitalized, these properties and methods can be accessed directly in other packages. Otherwise, they can only be accessed in the package. Therefore, the visibility in the Go language is at the package level, not the class level.
In Java, only static types or objects can use dot operators, which are extremely common. In Go, package names, function calls, struct, and interfaces can be used in combination with the import
command. In addition, unlike in C, dot operators are also used in pointer addresses and object references, and there is no need to consider whether to use a pointer or an arrow!
The entry package must be main, or it cannot run even if it is compiled successfully:
Compiled binary cannot be executed.
This happens because no entry function is found. Just like C and Java, the main function is also needed.
:=
symbol is required.var v1 int = 10 // Method 1: Conventional initialization.
var v2 = 10 // Method 2: The variable type is automatically derived by the compiler.
v3 := 10 // Method 3: If var is omitted, the compiler can automatically derive the data type of V3.
//java
private HashMap<String, UGCUserDetail> mBlockInfo;
Multiple Assignment
i, j = j, i
Variable exchange can be implemented, which is a bit like JS object destruction, but it is different. A function can return multiple values with this ability!
Anonymous Variables
Anonymous variables are represented by _, and the purpose is to avoid creating and defining some meaningless variables. Memory will not be allocated for these variables.
Pointer Variable
Like in C, you can think back to the example of exchanging values. What is the difference between passing values and passing addresses to parameters?
The pointer type is introduced in Go based on two main considerations. One is to provide programmers with the ability to manipulate the memory data structure corresponding to variables. The other is to improve the performance of the program. The pointer can point directly to the memory address of a variable value, which can save memory space and improve operation efficiency. This cannot be ignored during system programming, operating systems, or network applications.
A Pointer has two usage scenarios in Go: type pointer and array slice.
When it is used as a type pointer, it is allowed to modify the data of this pointer type and point to other memory addresses. If you use the pointer when transferring data, you do not need to copy the data, thus saving memory space. In addition, unlike pointers in C, type pointers in Go cannot be offset or computed, so it is safer.
Variable Type
The Go language provides built-in support for these basic data types:
The Go language also supports these composite types:
As for the const constant, iota is used to pre-define this constant for enumerations. It can be considered a constant that can be modified by the compiler. The value is reset to 0 each time const appears and is added by 1 automatically each time iota appears before the next const appears.
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
numberOfDays
)
Forced Type Conversion
v1 := 99.99
v2 := int(v1) // v2 = 99
v1 := []byte{'h', 'e', 'l', 'l', 'o'}
v2 := string(v1) // v2 = hello
// strconv packages are generally used for character-related conversion.
v1 := "100"
v2, err := strconv.Atoi(v1) // Convert a string to an integer (v2 = 100)
v3 := 100
v4 := strconv.Itoa(v3) // Convert an integer to a string (v4 = "100")
// Structure type conversion
// Type assertion
// x.(T) is actually to determine whether T implements interface x. If so, the type of interface x is embodied as the type of T.
claims, ok := tokenClaims.Claims.(*jwt.StandardClaims)
Array and Slice
// Define an array
var a [8]byte // The length is 8, and each element is a byte.
var b [3][3]int // Two-dimensional array (sudoku-type)
var c [3][3][3]float64 // 3D array (3D sudoku-type)
var d = [3]int{1, 2, 3} // Initialize on declaration
var e = new([3]string) // Initialize through new
var f = make([]string, 3) // Initialize through make
// Initialize
a := [5]int{1,2,3,4,5}
b := [...]int{1, 2, 3}
// Slice
b := []int{} // Array slice is a variable-length array.
c := a[1:3] // Similar to subString or js.slice
d := make([]int, 5) // make is equivalent to new and alloc for allocating memory.
// Length of array
length := len(a)
// Add an element
b = append(b, 4)
Dictionary
This is the map in Java. The syntax is very different.
var testMap map[string]int
testMap = map[string]int{
"one": 1,
"two": 2,
"three": 3,
}
// It can also be initialized like this:
var testMap = make(map[string]int) //map[string]int{}
testMap["one"] = 1
testMap["two"] = 2
testMap["three"] = 3
make and new
// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make's return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
// Slice: The size specifies the length. The capacity of the slice is
// equal to its length. A second integer argument may be provided to
// specify a different capacity; it must be no smaller than the
// length. For example, make([]int, 0, 10) allocates an underlying array
// of size 10 and returns a slice of length 0 and capacity 10 that is
// backed by this underlying array.
// Map: An empty map is allocated with enough space to hold the
// specified number of elements. The size may be omitted, in which case
// a small starting size is allocated.
// Channel: The channel's buffer is initialized with the specified
// buffer capacity. If zero, or the size is omitted, the channel is
// unbuffered.
func make(t Type, size ...IntegerType) Type
// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type
The difference is that the return value and the parameter are different. One is the value, and the other is the pointer. slice, chan, and map can only use make, and they are pointers. Others can use make or new.
Magic nil
It is more comfortable to use null in Java, and it judges an empty value directly. In addition to the string type, it judges characters to be ""
. In Go, this is simpler, as only the ""
state instead of nil can be judged. However, nil in Go is different from null, but it is very similar to ==
and ===
in JS.
nil also has a type:
func Foo() error {
var err *os.PathError = nil
// …
return err // The actual returned result is [nil, *os.PathError].
//return nil // The correct way is to directly return nil. The actual returned result is [nil, nil].
}
func main() {
err := Foo()
fmt.Println(err) // <nil>
fmt.Println(err == nil) // false
fmt.Println(err == (*os.PathError)(nil)) //true
}
Root Object: Object
In Java, if you do not use polymorphism, interface, parent class, and superclass, use Object as the root object. In Go, if the type of a function parameter is unknown, usually, interface{}
is used. It is an empty interface and represents any type. Since weakly typed language, any type, strong object-oriented language, or Object exists, this empty interface emerges.
One of the major characteristics is that the brackets are not used.
Control the Process
The judgment conditions of the if statement are not enclosed in parentheses, and you can also write a variable initialization statement in the front. Similar to the for loop, the first brace {
must be in the same line as if or else there could be issues.
The switch statement has become more powerful with these changes:
score := 100
switch score {
case 90, 100:
fmt.Println("Grade: A")
case 80:
fmt.Println("Grade: B")
case 70:
fmt.Println("Grade: C")
case 60:
case 65:
fmt.Println("Grade: D")
default:
fmt.Println("Grade: F")
}
s := "hello"
switch {
case s == "hello":
fmt.Println("hello")
fallthrough
case s == "xxxx":
fmt.Println("xxxx")
case s != "world":
fmt.Println("world")
}
//output:hello xxxx
Recycle the Process
The while and repeat keywords have been removed, and only the for keyword is retained, which is similar in use. The break and continue keywords are still available.
// General usage
for i := 1; i <= 5; i++ {
fmt.Println(i)
}
// Similar to the use of while
a := 1
for a <= 5 {
fmt.Println(a)
a ++
}
// Infinite loop
for {
// Do something
}
for ;; {
// Do something
}
// Similar to for-each in Java in use
listArray := [...]string{"xiaobi", "xiaoda", "xiaoji"}
for index, item := range listArray {
fmt.Printf("hello, %d, %s\n", index, item)
}
//Java
for (String item : someList) {
System.out.println(item);
}
Redirect the Process
Go has retained the goto statement that has been abandoned all the time. I remember the Basic and Pascal languages are using this, but I don't know why.
i := 1
flag:
for i <= 10 {
if i%2 == 1 {
i++
goto flag
}
fmt.Println(i)
i++
}
The defer process is a bit like finally in Java, which ensures that it can be executed. I think the bottom layer is also the implementation of goto. A function call is placed behind to execute the call of the xxx function after the current function is completed.
This is the snapshot implementation of pushing variables.
func printName(name string) {
fmt.Println(name)
}
func main() {
name := "go"
defer printName(name) // Output: go
name = "python"
defer printName(name) // Output: python
name = "java"
printName(name) // Output: java
}
//Output:
java
python
go
// defer is executed after return
var name string = "go"
func myfunc() string {
defer func() {
name = "python"
}()
fmt.Printf("name in the myfunc function:%s\n", name)
return name
}
func main() {
myname := myfunc()
fmt.Printf("name in the main function: %s\n", name)
fmt.Println("myname in the main function: ", myname)
}
//Output:
name in the myfunc function: go
name in the main function: python
myname in the main function: go
// One return value
func GetEventHandleMsg(code int) string {
msg, ok := EventHandleMsgMaps[code]
if ok {
return msg
}
return ""
}
// Multiple return values
func GetEventHandleMsg(code int) (string, error) {
msg, ok := EventHandleMsgMaps[code]
if ok {
return msg, nil
}
return "", nil
}
// Do not explicitly declare the variable value of return
func GetEventHandleMsg(code int) (msg string, e error) {
var ok bool
msg, ok = EventHandleMsgMaps[code]
if ok {
//Do something
return
}
return
}
Anonymous Functions and Closures
The implementation in Java is generally an internal class and an anonymous object. It cannot pass a function as a parameter through a method, but only an object can be passed to implement an interface.
Go is as convenient as JS, where functions can be passed and anonymous functions can be defined.
// Pass the anonymous function
func main() {
i := 10
add := func (a, b int) {
fmt.Printf("Variable i from main func: %d\n", i)
fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)
}
callback(1, add);
}
func callback(x int, f func(int, int)) {
f(x, 2)
}
// Return the anonymous function
func main() {
f := addfunc(1)
fmt.Println(f(2))
}
func addfunc(a int) func(b int) int {
return func(b int) int {
return a + b
}
}
Indefinite Parameters
It is similar to Java, except that ...
is needed for identifying during calling.
// Define
func SkipHandler(c *gin.Context, skippers ...SkipperFunc) bool {
for _, skipper := range skippers {
if skipper(c) {
return true
}
}
return false
}
// Call
middlewares.SkipHandler(c, skippers...)
Aliases are often used in the C language. The type class can be used to create an alias, which is very common, especially in the source code:
type Integer int
Classes in Go are defined using struct.
type Student struct {
id uint
name string
male bool
score float64
}
// There is no constructor, but functions can be used to create instance objects, and field initialization can be specified, similar to the static factory method in Java.
func NewStudent(id uint, name string, male bool, score float64) *Student {
return &Student{id, name, male, score}
}
func NewStudent2(id uint, name string, male bool, score float64) Student {
return Student{id, name, male, score}
}
The definition of member methods of a class is implicit in a reverse direction. It is not to declare which member methods are in the class but to which class the function belongs. The declaration syntax is after the func keyword and before the function name. Be careful not to confuse the return value definition of Java!
// This declaration method is the same as that of C++. This is a member function instead of a common function.
// Note that one method declares addresses and the other declares struct. Both can be directly performed through dots.
func (s Student) GetName() string {
return s.name
}
func (s *Student) SetName(name string) {
s.name = name
}
// Use
func main() {
// a is the pointer type.
a := NewStudent(1, "aa", false, 45)
a.SetName("aaa")
fmt.Printf("a name:%s\n", a.GetName())
b := NewStudent2(2, "bb", false, 55)
b.SetName("bbb")
fmt.Printf("b name:%s\n", b.GetName())
}
// If the SetName method and GetName method belong to Student instead of *Student, the name modification fails.
// Essentially, declaring a member function is to pass or reference an object or a pointer in the place where the non-function parameter is used. In other words, this pointer is passed in disguised form.
//That is why sometimes name modification fails.
There would be no inheritance without the extend keyword, which can only be achieved through combination. Combination solves the problem of multi-inheritance, and the memory structure is different based on the multi-inheritance order.
type Animal struct {
name string
}
func (a Animal) FavorFood() string {
return "FavorFood..."
}
func (a Animal) Call() string {
return "Voice..."
}
type Dog struct {
Animal
}
func (d Dog) Call() string {
return "汪汪汪"
}
// Second method: The address needs to be specified during initialization, and the others have not changed.
type Dog2 struct {
*Animal
}
func test() {
d1 := Dog{}
d1.name = "mydog"
d2 := Dog2{}
d2.name = "mydog2"
// The struct is a value type. If the value variable is passed in, actually a copy of the struct value is passed, which consumes more memory.
// So, pointer is better.
a := Animal{"ddog"}
d3 := Dog{a}
d4 := Dog2{&a}
}
This syntax is not like the combination in Java that uses member variables. Animal is directly referenced instead of defining the variable name. Of course, it is also possible to define variable names, but not necessary. Then, all the properties and methods in Animal can be accessed. If two classes are not in the same package, only the public properties and methods with uppercase initial letters in the parent class can be accessed. Method overrides can also be achieved.
Java interfaces are intrusive, meaning that the implementation class must explicitly declare that it implements an interface. The problem is that if the interface is changed, the implementation class must be changed, so an abstract class always exists in the middle.
// Define the interface:
type Phone interface {
call()
}
// Implement the interface:
type IPhone struct {
name string
}
func (phone IPhone) call() {
fmt.Println("Iphone calling.")
}
Go interfaces are non-intrusive because the implementation relationship between the class and interface is not explicitly declared. The system determines the relationship based on the method set of both. A class must implement all the methods of an interface. Inheritance between interfaces is the same between classes. The implementation logic of polymorphism is the same through the combination. If the method list of interface A is a subset of the method list of interface B, B can assign values to A.
I haven't learned much about concurrent programming yet, so I took this classic example of a producer-consumer model from the Internet. I'll share my understanding after further studies.
// Data producer
func producer(header string, channel chan<- string) {
// Infinite loop that continuously produce data
for {
// Format random numbers and strings as strings and send to the channel
channel <- fmt.Sprintf("%s: %v", header, rand.Int31())
// Wait for 1 second
time.Sleep(time.Second)
}
}
// Data consumer
func customer(channel <-chan string) {
// Keep obtaining data
for {
// Retrieve data from the channel. The channel is blocked here until data is returned.
message := <-channel
// Print the data
fmt.Println(message)
}
}
func main() {
// Create a channel of the string type
channel := make(chan string)
// Create concurrent goroutine for the producer() function
go producer("cat", channel)
go producer("dog", channel)
// Data consumption function
customer(channel)
}
//Output:
dog: 1298498081
cat: 2019727887
cat: 1427131847
dog: 939984059
dog: 1474941318
cat: 911902081
cat: 140954425
dog: 336122540
This article is just a brief introduction. There are many things I haven't covered about the Go language, such as context, try-catch, and concurrency-related (like locks), Web development-related, and database-related topics. Starting with this article, I will continue to learn the Go language and share my understanding with everyone. You are welcome to discuss anything with me!
Alibaba Container Service - May 30, 2019
Alex - February 14, 2020
Alex - January 22, 2020
Alibaba Clouder - February 17, 2020
Alibaba Cloud Community - October 26, 2021
Alibaba Clouder - February 14, 2020
Explore Web Hosting solutions that can power your personal website or empower your online business.
Learn MoreA low-code development platform to make work easier
Learn MoreExplore how our Web Hosting solutions help small and medium sized companies power their websites and online businesses.
Learn MoreHelp enterprises build high-quality, stable mobile apps
Learn More