Golang for Java Developers

A quick look at a few important differences between Java and Go

Lars Gröber
Inheaden
5 min readApr 12, 2021

--

At Inheaden, we love learning more about new technologies and tools. Oftentimes, a new tool replaces the old one if it brings enough benefits. That’s what happened with Go and Java. After testing out Go as a backend language, we decided to completely switch to Go for any (new) microservice we would write. This article will shine some light on the differences you need to know when switching from Java to Go.

This article will only look at a few important differences between Java and Go and does not strive to be complete. You can try out Go yourself by running any piece of code in this article on the Go Playground.

Also, make sure to stay tuned for more articles in the future about how and why we switched technologies on the backend and what we learned on the way.

Variables

Of course, both Go and Java support the usage of variables, but Go has a few specifics here. Mostly syntax related, though.

func main() {
v1 := "some variable"
var v2 = "some other variable"
v1 = "now v1 has a different value"
v2 = " same with v2"
fmt.Print(v1, v2)
}

As you can see, new variables are defined using := while changing their value requires just =. Using var on the other hand requires only a single =.

Pointers

A large difference between Java and Go is the usage of Pointers directly in Go.

func main() {
v1 := "some variable"
// v2 is now a pointer to v1
var v2 *string = &v1
fmt.Print(v2) // prints the memory address of v1
}

The syntax is very similar to how C does it, although Go does not support pointer arithmetic. Luckily, Go has garbage collection, so we do not need to worry too much about memory leaks.

Pointers allow you to pass any variable by reference instead of by value (or at least make it work that way — Go does not have the concept of a “reference”). Most types in Go are passed by value if they are not pointers. Slices, maps and interfaces are always handled like pointers.

Slices and maps

Slices are the Lists of Go while maps are similar to HashMaps that essentially are key-value stores. Go also has fixed-sized arrays, but we don’t cover those here.

func main() {
v1 := []int{1, 2, 3}
v1 = append(v1, 4)

fmt.Println(len(v1)) // 4
fmt.Println(v1[1]) // 2
fmt.Println(v1[1:3]) // [2 3]

v2 := make(map[string]string)
v2["hello"] = "world"

v3, ok := v2["hello"]
fmt.Println(ok, v3) // true world
}

As you can see, slices are variable-sized lists that also support Python-like indexing (or “slicing”). You can convert a fixed-sized array to a slice by calling fixed[:]on the array.

Maps, on the other hand, are implemented as Hashmaps and allow you to quickly lookup a value by a key.

Error handling

Probably the biggest discussion point with Go is its error handling — or lack thereof. Go has no concept of “throwing an error” or the typical try/catch blocks you find in Java and many other languages.

In Go, errors are returned “normally” from a function and you as the caller are responsible for handling them.

func test(input int) error {
if input < 0 {
return errors.New("less than zero")
}
return nil
}
func main() {
err := test(-1)
if err != nil {
fmt.Print(err)
}
}

In this example, you see the typical way someone would handle errors on a program. You will see the if err != nil part in a lot of Go programs. While this becomes seemingly annoying at some point — and sometimes, it does — it forces you to always think about when errors could happen and how to handle them. In Java, you often simply rely on some other part of the code to handle the errors that might get thrown.

Go has a way to “throw” errors, though: panicking. But that should usually be reserved for nil dereferencing or type assertion errors.

Interfaces and structs

Lastly, let’s look at interfaces and structs in Go. For both, Go and Java, interfaces are used to describe the public API that some implementation should offer and they are especially useful when extending a library or writing tests. While you have to explicitly implement an interface in Java, in Go, interfaces are implemented implicitly — you just define all methods on a Go struct that the interface demands.

Structs, on the other hand, are the classes of Go. They can have members and methods like a class in Java.

type Animal interface {
Speak() string
}
type Dog struct {
Name string
}
// here we define Speak as a method on Dog
// it takes a pointer receiver, but you
// could also remove the *
func (d *Dog) Speak() string {
return fmt.Sprintf("I am %s", d.Name)
}
func main() {
var dog Animal = &Dog{
Name: "Cooper",
}

fmt.Println(dog.Speak())
}

Note here that dog is of type Animal not *Dog and that interfaces in Go have to be pointers. You sometimes see a line like var _ Animal = &Dog{} which asserts that Dog implements Animal.

Conclusion

Switching technologies is always a daunting task, but we hope that we were able to help anyone that might be thinking about changing from Java to Go or who is simply interested in how writing Go differs from whatever language you have used so far.

Thank You for Reading!

Found this post useful? Kindly hit the 👏 button below to show how much you liked this post!

Inheaden is a young IT and software startup based in Darmstadt, Germany. As an “Idea and Tech Factory”, we have set out to be a driving force of innovation, digitization, and automation with a focus on the areas of services, products, and research. Under the Inheaden brand, we work on individual “high performance” software solutions that bring a change. Modern designs, innovative technology approaches, and IT security for our partners and customers are important components of our work profile.

--

--