Pointers, functions, and execution control in Go vs Java | Types - Language

Series: Go for Java Developers — analyzing pointer, closures, defer, panic/recover

In this article, we will analyze how Go manages the state and lifecycle of functions. A feature of Go is the ease of working with pointers without complex arithmetic, powerful closures, and built-in mechanisms for safe termination and error handling. For a Java developer, this allows understanding the differences with familiar references, exceptions, and lambdas.


Pointers and value vs reference semantics

Pointers in Go allow working with variable addresses, but without pointer arithmetic. This is safer than in C, and analogous to references in Java, but with explicit syntax.

Example of a function with a pointer

// Go
func update(x *int) {
    *x = 10
}

func main() {
    a := 5
    update(&a)
    fmt.Println(a) // 10
}

Java equivalent:

// Java
class Main {
    static void update(IntegerWrapper x) {
        x.value = 10;
    }

    public static void main(String[] args) {
        IntegerWrapper a = new IntegerWrapper(5);
        update(a);
        System.out.println(a.value); // 10
    }
}

class IntegerWrapper {
    int value;
    IntegerWrapper(int v) { value = v; }
}
Go makes references explicit through pointers. Value receiver works with a copy, pointer receiver works with the original.

Function literal / Closure — functions as objects

In Go, functions are full-fledged first-class objects. Closures allow maintaining state between calls.

Example of closure

// Go
func counter() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}

func main() {
    c := counter()
    fmt.Println(c()) // 1
    fmt.Println(c()) // 2
}

Java equivalent (via lambda with object)

// Java
import java.util.function.Supplier;

class Counter {
    int i = 0;
    Supplier<Integer> counter() {
        return () -> ++i;
    }

    public static void main(String[] args) {
        Counter obj = new Counter();
        Supplier<Integer> c = obj.counter();
        System.out.println(c.get()); // 1
        System.out.println(c.get()); // 2
    }
}
Closure allows Go to keep the internal state of a function without creating a separate class. This is convenient for counters, generators, callbacks.

Defer — deferred execution (analogous to try-with-resources)

Defer executes a function at the end of the current block. This is convenient for closing resources or freeing memory.

Example of defer

// Go
func main() {
    defer fmt.Println("world")
    fmt.Println("hello")
}
// Output:
// hello
// world

Java equivalent (try-with-resources / finally)

// Java
public class Main {
    public static void main(String[] args) {
        try {
            System.out.println("hello");
        } finally {
            System.out.println("world");
        }
    }
}
// Output:
// hello
// world
Defer is convenient for clean code, especially with files, sockets, and other resources.

Panic / Recover — handling critical errors

In Go, panic is analogous to unchecked exceptions in Java, but more controllable. Recover allows intercepting the panic and continuing execution.

Example panic/recover

// Go
func safeDivide(a, b int) (result int) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered:", r)
            result = 0
        }
    }()
    if b == 0 {
        panic("division by zero")
    }
    return a / b
}

func main() {
    fmt.Println(safeDivide(10, 0)) // Recovered: division by zero \n 0
}

Java equivalent

// Java
public class Main {
    static int safeDivide(int a, int b) {
        try {
            return a / b;
        } catch (ArithmeticException e) {
            System.out.println("Recovered: " + e.getMessage());
            return 0;
        }
    }

    public static void main(String[] args) {
        System.out.println(safeDivide(10, 0));
    }
}
Panic and recover allow for graceful handling of critical errors without breaking the entire flow of execution.

Comparison of key concepts Go ↔ Java

Concept Go Java Comment
Pointers explicit pointer, no arithmetic object references Go makes the reference explicit
Value vs reference semantics value receiver / pointer receiver primitive vs object reference Pointer receiver modifies the original
Closure function literal, captures vars lambda / anonymous class Can store function state
Defer deferred execution try-with-resources / finally Convenient for releasing resources
Panic / Recover runtime error / catch unchecked exception / try-catch Catching critical errors

Practical Output

For a Java developer learning Go, it is important to understand:

  • Pointers in Go are safe and explicit, without arithmetic
  • Value vs pointer receiver allows controlling data changes
  • Functions are first-class objects, closures maintain state
  • Defer replaces finally or try-with-resources
  • Panic / recover – built-in handling of critical errors

These mechanisms make Go code compact, safe, and predictable, while maintaining control over state and resources without the bulky class structure of Java.


🌐 На русском
Total Likes:0

Оставить комментарий

My social media channel
By sending an email, you agree to the terms of the privacy policy

Useful Articles:

Go ↔ Java: Complete Guide to Runtime, Memory, and Allocator - Part 3
This article is a comprehensive guide to the key aspects of memory and runtime work in Go and Java. We will discuss fundamental concepts: execution scheduler, memory barriers, memory alignment, stack ...
Loops in Java: for, while, do while, continue and break statements
Hello! This is Vitaly Lesnykh. Today we will continue the course “Java Basics for Beginners” and discuss one of the most important topics in programming — loops. A loop is the repetition of code exe...
Channel direction and select patterns in Go vs Java | Patterns, idioms, and best practices in Go
← Related articles: Context, propagation and cancellation patterns in Go vs Java | Patterns, idioms and best practices in Go 1. Channel direction — channel directions In Go, channels can be one-w...

New Articles:

Zero Allocation in Java: what it is and why it matters
Zero Allocation — is an approach to writing code in which no unnecessary objects are created in heap memory during runtime. The main idea: fewer objects → less GC → higher stability and performance. ...
Stream vs For in Java: how to write the fastest code possible
In Java, performance is often determined not by the "beauty of the code," but by how it interacts with memory, the JIT compiler, and CPU cache. Let s analyze why the usual for is often faster than Str...
Compiler, Build, and Tooling in Go and Java: how assembly, initialization, analysis, and diagnostics are organized in two ecosystems
This article is dedicated to a general overview of how the compiler, build, and tooling practices are arranged in Go, and how to better understand them through comparison with Java. We will not delve ...
Fullscreen image