Go: Grundlagen für Java/Python-Entwickler in 2026

Go schnell erlernen durch vorhandene Java- oder Python-Erfahrung. Goroutines, Channels, Interfaces und wesentliche Patterns für einen reibungslosen Umstieg.

Go-Leitfaden für Java- und Python-Entwickler

Go (oder Golang) hat sich als Sprache der Wahl für Microservices, CLI-Tools und verteilte Systeme etabliert. 2009 von Google entwickelt, vereint Go die Einfachheit von Python mit der Performance von C. Für Entwickler mit Java- oder Python-Hintergrund verläuft der Umstieg auf Go überraschend reibungslos, sobald die Kernkonzepte verstanden sind.

Warum Go in 2026?

Go treibt Docker, Kubernetes, Terraform und unzählige Cloud-Infrastrukturen an. Die schnelle Kompilierung, native Concurrency-Unterstützung und das Deployment als einzelne Binary machen Go ideal für moderne Backend-Entwicklung.

Installation und Einrichtung von Go

Die Installation von Go ist unkompliziert und plattformübergreifend einheitlich. Das go-Tool verwaltet Kompilierung, Abhängigkeiten und Tests.

bash
# install.sh
# Installation on macOS with Homebrew
brew install go

# Installation on Linux (Ubuntu/Debian)
sudo apt update && sudo apt install golang-go

# Verify installation
go version
# go version go1.22.0 linux/amd64

Die Projektstruktur in Go folgt strikten, aber einfachen Konventionen. Die Datei go.mod definiert das Modul und seine Abhängigkeiten.

bash
# project-setup.sh
# Create a new project
mkdir my-project && cd my-project
go mod init github.com/user/my-project

# Generated structure:
# my-project/
# ├── go.mod    # Module manifest
# └── main.go   # Entry point

# Essential commands
go build          # Compile the project
go run main.go    # Compile and execute
go test ./...     # Run all tests
go fmt ./...      # Format code automatically

Erstes Go-Programm

Hier ist ein einfaches Programm, das die grundlegende Syntax von Go zeigt. Der Vergleich mit Java und Python hilft, die Unterschiede zu erkennen.

main.gogo
package main

import "fmt"

// Program entry point
func main() {
    // Declaration with type inference
    message := "Hello, Go!"
    fmt.Println(message)

    // Explicit declaration
    var count int = 42
    fmt.Printf("Count: %d\n", count)
}

Was sofort auffällt: keine Semikolons, keine Klammern um Bedingungen und Typinferenz mit :=. Go setzt auf Prägnanz, ohne die Lesbarkeit zu opfern.

Variablen und grundlegende Typen

Go ist statisch typisiert, bietet aber hervorragende Typinferenz. Die Basistypen decken die meisten Anwendungsfälle ab.

types.gogo
package main

import "fmt"

func main() {
    // Short declaration (inside functions only)
    name := "Alice"        // string
    age := 30              // int
    height := 1.75         // float64
    active := true         // bool

    // Explicit declaration
    var score int = 100
    var rate float64 = 3.14

    // Multiple declaration
    var (
        firstName string = "Bob"
        lastName  string = "Smith"
        points    int    = 0
    )

    // Zero values (default values)
    var count int      // 0
    var text string    // "" (empty string)
    var flag bool      // false
    var ptr *int       // nil

    fmt.Println(name, age, height, active)
}
Zero Values in Go

Anders als in Java oder Python initialisiert Go Variablen automatisch mit ihrem "Zero Value": 0 für Zahlen, "" für Strings, false für Bools, nil für Pointer und Slices.

Funktionen und mehrfache Rückgabewerte

Go ermöglicht die Rückgabe mehrerer Werte — eine Eigenschaft, die intensiv für die Fehlerbehandlung genutzt wird.

functions.gogo
package main

import (
    "errors"
    "fmt"
)

// Simple function with typed parameters
func add(a, b int) int {
    return a + b
}

// Multiple returns (idiomatic pattern for errors)
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// Named returns
func getUser(id int) (name string, age int, err error) {
    if id <= 0 {
        err = errors.New("invalid ID")
        return
    }
    name = "Alice"
    age = 30
    return
}

// Variadic function
func sum(numbers ...int) int {
    total := 0
    for _, n := range numbers {
        total += n
    }
    return total
}

func main() {
    // Simple call
    result := add(5, 3)
    fmt.Println("5 + 3 =", result)

    // Explicit error handling
    quotient, err := divide(10, 3)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Printf("10 / 3 = %.2f\n", quotient)

    // Ignore a returned value with _
    name, _, _ := getUser(1)
    fmt.Println("User:", name)

    // Variadic call
    total := sum(1, 2, 3, 4, 5)
    fmt.Println("Sum:", total)
}

Structs und Methoden

Structs sind die Bausteine für benutzerdefinierte Typen in Go. Methoden werden über Receiver an Typen gebunden.

structs.gogo
package main

import "fmt"

// Struct definition
type User struct {
    ID       int
    Username string
    Email    string
    Active   bool
}

// Constructor (convention: NewTypeName)
func NewUser(id int, username, email string) *User {
    return &User{
        ID:       id,
        Username: username,
        Email:    email,
        Active:   true,
    }
}

// Method with value receiver (copy)
func (u User) FullInfo() string {
    status := "inactive"
    if u.Active {
        status = "active"
    }
    return fmt.Sprintf("%s <%s> (%s)", u.Username, u.Email, status)
}

// Method with pointer receiver (modification possible)
func (u *User) Deactivate() {
    u.Active = false
}

// Method with pointer receiver for modification
func (u *User) UpdateEmail(newEmail string) {
    u.Email = newEmail
}

func main() {
    // Create with constructor
    user := NewUser(1, "alice", "alice@example.com")
    fmt.Println(user.FullInfo())

    // Modify via method
    user.Deactivate()
    fmt.Println(user.FullInfo())

    // Direct creation
    user2 := User{
        ID:       2,
        Username: "bob",
        Email:    "bob@example.com",
    }
    fmt.Println(user2.FullInfo())
}
Value vs Pointer Receiver

Ein Pointer-Receiver (*User) wird verwendet, wenn die Methode den Zustand ändert oder das Struct groß ist. Ein Value-Receiver (User) eignet sich für schreibgeschützte Methoden bei leichtgewichtigen Structs.

Interfaces: impliziter Polymorphismus

Go-Interfaces werden implizit implementiert. Ein Typ erfüllt ein Interface, wenn er alle Methoden implementiert — ohne explizite Deklaration.

interfaces.gogo
package main

import (
    "fmt"
    "math"
)

// Interface definition
type Shape interface {
    Area() float64
    Perimeter() float64
}

// Rectangle implements Shape implicitly
type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

// Circle also implements Shape
type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

// Function accepting the interface
func PrintShapeInfo(s Shape) {
    fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}

func main() {
    rect := Rectangle{Width: 10, Height: 5}
    circle := Circle{Radius: 7}

    // Polymorphism via interface
    PrintShapeInfo(rect)
    PrintShapeInfo(circle)

    // Slice of interfaces
    shapes := []Shape{rect, circle}
    for _, shape := range shapes {
        PrintShapeInfo(shape)
    }
}

Dieser Ansatz unterscheidet sich grundlegend von Java, wo implements obligatorisch ist. In Go ist die Konformität strukturell, nicht nominal.

Bereit für deine Go-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Slices und Maps: dynamische Collections

Slices sind dynamische Sichten auf Arrays und Maps sind native Hash-Tabellen.

collections.gogo
package main

import "fmt"

func main() {
    // Slice: dynamic array
    numbers := []int{1, 2, 3, 4, 5}

    // Append elements
    numbers = append(numbers, 6, 7)

    // Slicing (similar to Python)
    subset := numbers[1:4]  // [2, 3, 4]
    fmt.Println("Subset:", subset)

    // Create slice with make
    scores := make([]int, 0, 10)  // len=0, cap=10
    scores = append(scores, 100, 95, 88)

    // Iteration with range
    for index, value := range numbers {
        fmt.Printf("numbers[%d] = %d\n", index, value)
    }

    // Map: hash table
    users := map[string]int{
        "alice": 30,
        "bob":   25,
    }

    // Add/Update
    users["charlie"] = 35

    // Check existence
    age, exists := users["alice"]
    if exists {
        fmt.Println("Alice's age:", age)
    }

    // Delete
    delete(users, "bob")

    // Map iteration
    for name, age := range users {
        fmt.Printf("%s is %d years old\n", name, age)
    }
}

Idiomatische Fehlerbehandlung

Go kennt keine Exceptions. Fehler sind explizit zurückgegebene Werte, was eine rigorose Behandlung erzwingt.

errors.gogo
package main

import (
    "errors"
    "fmt"
    "os"
)

// Sentinel error (for comparison)
var ErrNotFound = errors.New("resource not found")
var ErrInvalidInput = errors.New("invalid input")

// Custom error with context
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}

// Function returning different error types
func GetUser(id int) (string, error) {
    if id <= 0 {
        return "", &ValidationError{
            Field:   "id",
            Message: "must be positive",
        }
    }
    if id > 1000 {
        return "", ErrNotFound
    }
    return "Alice", nil
}

// Error wrapping (Go 1.13+)
func ReadConfig(path string) ([]byte, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("reading config %s: %w", path, err)
    }
    return data, nil
}

func main() {
    // Basic pattern
    user, err := GetUser(-1)
    if err != nil {
        fmt.Println("Error:", err)

        // Type assertion for custom error
        var valErr *ValidationError
        if errors.As(err, &valErr) {
            fmt.Printf("Field: %s\n", valErr.Field)
        }

        // Comparison with sentinel error
        if errors.Is(err, ErrNotFound) {
            fmt.Println("User not found")
        }
    } else {
        fmt.Println("User:", user)
    }
}
errors.Is und errors.As

Seit Go 1.13 wird errors.Is() zum Vergleich mit Sentinel-Fehlern und errors.As() zum Extrahieren eines bestimmten Fehlertyps aus einer Fehler-Kette verwendet.

Goroutines: leichtgewichtige Concurrency

Goroutines sind leichtgewichtige Threads, die von der Go-Runtime verwaltet werden. Das Starten einer Goroutine kostet nur wenige KB Speicher.

goroutines.gogo
package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()  // Decrement counter when done

    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    // Launch 5 goroutines
    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i, &wg)  // 'go' prefix launches the goroutine
    }

    // Wait for all goroutines to complete
    wg.Wait()
    fmt.Println("All workers completed")
}

Die sync.WaitGroup ermöglicht das Warten auf den Abschluss mehrerer Goroutines. Dies ist das grundlegende Pattern für Parallelismus in Go.

Channels: Kommunikation zwischen Goroutines

Channels sind typisierte Kanäle für die Kommunikation zwischen Goroutines. Sie ermöglichen sichere Synchronisation und Datenaustausch.

channels.gogo
package main

import (
    "fmt"
    "time"
)

func producer(ch chan<- int) {
    for i := 1; i <= 5; i++ {
        fmt.Println("Producing:", i)
        ch <- i  // Send on channel
        time.Sleep(100 * time.Millisecond)
    }
    close(ch)  // Close channel when done
}

func consumer(ch <-chan int, done chan<- bool) {
    for value := range ch {  // Iterate until closed
        fmt.Println("Consuming:", value)
    }
    done <- true
}

func main() {
    ch := make(chan int)     // Unbuffered channel
    done := make(chan bool)

    go producer(ch)
    go consumer(ch, done)

    <-done  // Wait for consumer to finish
    fmt.Println("All done")
}

Gepufferte Channels und Select

channels_advanced.gogo
package main

import (
    "fmt"
    "time"
)

func main() {
    // Buffered channel (capacity 3)
    buffered := make(chan int, 3)
    buffered <- 1
    buffered <- 2
    buffered <- 3
    // buffered <- 4  // Would block since buffer is full

    fmt.Println(<-buffered)  // 1

    // Select: channel multiplexing
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(100 * time.Millisecond)
        ch1 <- "from ch1"
    }()

    go func() {
        time.Sleep(200 * time.Millisecond)
        ch2 <- "from ch2"
    }()

    // Wait for first available message
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println("Received:", msg1)
        case msg2 := <-ch2:
            fmt.Println("Received:", msg2)
        case <-time.After(500 * time.Millisecond):
            fmt.Println("Timeout!")
        }
    }
}
Go-Philosophie: CSP

Go folgt dem CSP-Modell (Communicating Sequential Processes): "Kommuniziere nicht durch gemeinsamen Speicher; teile Speicher durch Kommunikation." Channels verhindern Race Conditions.

Testing in Go

Go enthält ein minimalistisches, aber effektives Test-Framework. Testdateien enden mit _test.go.

calculator.gogo
package calculator

func Add(a, b int) int {
    return a + b
}

func Divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}
calculator_test.gogo
package calculator

import (
    "testing"
)

// Basic test
func TestAdd(t *testing.T) {
    result := Add(2, 3)
    expected := 5

    if result != expected {
        t.Errorf("Add(2, 3) = %d; want %d", result, expected)
    }
}

// Table-driven tests (recommended pattern)
func TestAddTableDriven(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive numbers", 2, 3, 5},
        {"negative numbers", -2, -3, -5},
        {"zero", 0, 0, 0},
        {"mixed", -5, 10, 5},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d",
                    tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

// Error test
func TestDivideByZero(t *testing.T) {
    _, err := Divide(10, 0)
    if err == nil {
        t.Error("Expected error for division by zero")
    }
}

// Benchmark
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(100, 200)
    }
}

Tests werden mit go test ./... ausgeführt und Benchmarks mit go test -bench=..

HTTP: minimalistischer Webserver

Go glänzt bei der Erstellung hochperformanter HTTP-Server mit seiner Standardbibliothek.

server.gogo
package main

import (
    "encoding/json"
    "log"
    "net/http"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func main() {
    // Simple route
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, Go!"))
    })

    // JSON route
    http.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
        users := []User{
            {ID: 1, Name: "Alice"},
            {ID: 2, Name: "Bob"},
        }

        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(users)
    })

    // Route with method
    http.HandleFunc("/api/user", func(w http.ResponseWriter, r *http.Request) {
        switch r.Method {
        case "GET":
            w.Write([]byte("Get user"))
        case "POST":
            w.Write([]byte("Create user"))
        default:
            http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        }
    })

    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Dieser Server verarbeitet dank Goroutines Tausende gleichzeitiger Verbindungen. Jede Anfrage wird automatisch in ihrer eigenen Goroutine verarbeitet.

Fazit

Go bietet einen pragmatischen Ansatz für die Backend-Entwicklung: einfache Syntax, schnelle Kompilierung, native Concurrency und ausgezeichnete Tools. Für Java- oder Python-Entwickler erfordert der Umstieg die Akzeptanz einiger anderer Konventionen (explizite Fehlerbehandlung, eingeschränkte Generics vor Go 1.18), doch die Vorteile bei Performance und Wartbarkeit zeigen sich sofort.

Checkliste für den Einstieg

  • ✅ Go über die offizielle Website oder den Paketmanager installieren
  • ✅ Die Befehle go build, go run, go test, go fmt beherrschen
  • ✅ Den Unterschied zwischen Slices und Arrays verstehen
  • ✅ Das if err != nil-Pattern für Fehlerbehandlung übernehmen
  • ✅ Goroutines und Channels für Concurrency einsetzen
  • ✅ Table-driven Tests mit dem testing-Paket schreiben

Fang an zu üben!

Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.

Das Go-Ökosystem ist ausgereift, mit populären Frameworks wie Gin, Echo und Fiber für Web-Entwicklung sowie Tools wie Cobra für CLIs. Mit diesen soliden Grundlagen wird die Auseinandersetzung mit fortgeschrittenen Themen wie Generics (Go 1.18+), dem Context-Paket und Concurrency-Patterns gut zugänglich.

Tags

#go
#golang
#concurrency
#goroutines
#backend

Teilen

Verwandte Artikel