Introduction

Not so long ago, I was given an opportunity to take a completely new step in my programming career. Indeed, this was one of the biggest and most satisfying challenges I have ever had as a developer. Almost five years have passed since I took my first steps in the IT world, but recently, I have felt like a rookie again, starting something completely new.

Without further ado, I was assigned to a new project, which had to be written in a language I had only heard of at the time – the Golang. Initially, I felt slightly overwhelmed by it because it is pretty specific compared to Java. But day after day, I got to know it better and felt more confident using it.

That is why I have decided to write the following article and share my thoughts about Go.

Yes, I am fully aware that I am at least five years late in bringing something new and sensational about this language. Probably everything that could have been written about Go was written already. Why do I care, then? Well, I want to present my point of view and show what it looks like to step into Go from a Java Developer perspective. Maybe some of you have had a similar experience or are considering choosing Golang for a project.

I will point out some of its features and compare them to Java. What will be distinctive about it is my personal bias. I don’t want to create another technical documentation. There are dozens of them within a grasp of the hand. Of course, I will discuss some technical aspects and present several examples, yet I will enhance them with my thoughts and observations.

How to classify Golang?

What Go actually has to offer? As a relatively new language, it is a mixture of concepts and ideas, well-known from other already-established programming languages. Some resemble their precursors (like pointers), while others are redesigned in a completely new way (for instance, error handling).

As a Java person, the greatest difficulty is describing what Go is all about. In the case of Java, it’s extremely easy to answer this one. It is an object-oriented language, which lately tries to be functional.

Is Go object-oriented? According to the answer from Official Golang’s FAQ – Yes and no. If you’re interested, you can read the whole answer here: Is Go an object-oriented language

There is no concept of class or inheritance. But, Go allows an OOP and makes the big advantage of simplifying this concept. Basically, if two types implement all methods defined by an interface, they can be used interchangeably wherever the type is defined as an interface. You don’t even have to explicitly specify which interface is implemented. Your struct just implements it, when the criteria are met.

Moreover, there is no need to specify any method in such an interface, which is very convenient. This is a clean and lightweight approach to object-oriented principles. It provides a simple yet flexible solution without the burden of more traditional inheritance, which can sometimes be complex and cumbersome to maintain.

Below there is a code snippet with an example of the Vehicle interface.

When running a program, you will get the following response:

As you can see, Golang is so flexible that even an apple became a vehicle, so you had better be careful with empty interfaces because all structures implement it implicitly! The best idea is to add and implement methods to an interface, but there is no guarantee that no other interface describes the same methods.

Let us upgrade our example as shown below:

If we add the startEngine() method to the interface and implement them for Car and Bike types, the Apple type no longer implements our interface. When running the code, the following error appears:

The Go compiler provides us with very specific information about which variable is wrong and which method is missing. After removing apple from our list, the following output is obtained:

As you can see in our implemented methods, we can access fields specific to our structs. That’s great, but how to do it when we do not know what type of object we have? Golang will not automatically do type conversion for us, so how can we deal with such a situation?

Fortunately, Go provides a couple of mechanisms: Type Assertion and Type Switch.

Below, there is a modified code snippet with an exemplary implementation of the Type switch:

The program outputs the following:

Java offers a similar feature as a Preview starting from JDK17. As shown above, Golang promotes a fresh approach to object-oriented programming and provides simple yet efficient mechanisms to complete the work. But it is not strictly OO language.

What is it, then? Maybe a functional programming language? Not really, but it does have some well-known mechanisms that make functional programming effortless and pleasant. First of all: Golang functions are treated as values. They can be assigned as variables, stored in a map or slice, passed as parameters to other functions, and even returned from functions. Moreover, Golang provides closure functionality, which allows an inner function to access and modify variables declared in the outer function.

These features might be useful for data manipulation, such as sorting or filtering slices, where necessary implementation can be passed as a parameter. Another application example is using closure as a handler or callback function. Such a solution is used in the Golang HTTP package, where endpoint handlers can be wrapped in functions that can provide additional (middleware) functionalities, i.e., authentication. Below is a simple example that presents how an HTTP request can be intercepted to check credentials from request headers. It is a very naive and unsafe way of authenticating a user, which should not be used in real applications.

Except for the closure example, it is worth noting how easy it is to write a web service in Go. Thanks to the net/HTTP package, which has a ready-made HTTP server, it takes 10 lines of code to spin up a web app. How does Java compare to Go in terms of functional programming?

The answer might be slightly surprising, but not so bad since it introduces lambda expressions. Let us compare both with examples of filtering even and odd numbers from arrays. Golang’s implementation goes first:

As you can see, there is a function called filter, which accepts an array and a function. Basically, all we do is iterate over an array and call an inner function, which checks if the filtering condition is met. We can pass any function if its signature matches the one described as a parameter. Let’s move on to the same implementation done in Java.

Well, I must admit that it does not look bad. One significant difference is the need to declare and use functional interfaces. Such an interface has to have one method signature. The good practice is to put the @FunctionalInterface annotation. Having that, Java reveals many features of functional programming in front of us. Functional Interface can be returned from the method, as well as a method parameter, variable or static constant. Moreover, we can reduce some of the code by using default Java functional interfaces if they satisfy our needs. For example, IntFilter could be replaced with IntPredicate.

Which approach is better, in my opinion? I will opt for Golang because it is a little bit cleaner solution, easier to understand due to more traditional syntax, and does not require additional code such as functional interface declaration. I do, however, reckon that Java did a great job with lambda expressions. It’s very flexible and has a neat arrow syntax.

So far, we have spoken about object-oriented and functional aspects of Golang, but so far, did not manage to categorize it. We can say that Golang is an imperative and, more specifically, procedural programming language like most programming languages are. And, to be honest, this piece of information is utterly useless. In fact, trying to classify it to a specific type is against the idea of Go and may lead to producing an unidiomatic code. I really like the way Golang is described in Jon Bodner’s “Learning Go”, namely: “Go is a practical programming language”. That is exactly how we should treat it, in a practical and efficient way, without trying to narrow it and beside ourselves to one concept.

What the heck are pointers?

This is the exact question I asked myself when I saw them for the first time. If you are programming in C or C++, you are more than familiar with this concept. But for people who come from Java or Python, this can be something new.

Basically, a pointer is a variable that stores a memory address to another variable. Usually, pointer stores address a structure of any type. It is worth noticing that pointers always allocate the same amount of memory, regardless of the type they are pointing at.

Go, as usual, does it in its own way, but it is hard to miss that the C-languages family inspires the solution. Unlike them, it does not allow direct manipulation of memory (unless we really, really want to do it) and provides a garbage collector mechanism, which takes away the necessity of memory management, which can often be problematic.

Initially, I felt slightly overwhelmed with pointers syntax and how to use it. Then I realized that they are equivalent to Java references and can be used to imitate the behavior of classes. And they are, actually, not as scary as they seem to be.

First of all, all types in Golang, similarly to functions, are treated as values. This also applies to structs and nested structures. So basically, all data types, simple and complex, behave like simple types in Java. The following example illustrates it: