Bỏ qua tới nội dungSkip to content

Java ⇄ Go

Bảng đối chiếu khái niệm giữa hai ngôn ngữ: cùng một ý tưởng được viết thế nào trong Java và Go. Nên đọc trước khi xem phần code của từng pattern.A concept reference across both languages: how the same idea is written in Java and in Go. Recommended before reading each pattern's code.

Cơ bảnBasics

Class ⇄ struct + methodClass ⇄ struct + methods

Go không có class. Dữ liệu nằm trong struct, hành vi là method gắn với struct qua receiver.Go has no classes. Data lives in a struct; behaviour is methods attached through a receiver.

Java
public class Order {
    private final String id;
    private long total;

    public Order(String id) { this.id = id; }

    public void add(long amount) { total += amount; }
}
Go
type Order struct {
	id    string
	total int64
}

func NewOrder(id string) *Order { return &Order{id: id} }

func (o *Order) Add(amount int64) { o.total += amount }
Receiver con trỏ (*Order) khi method cần sửa dữ liệu; receiver giá trị khi chỉ đọc.Use a pointer receiver (*Order) when the method mutates; a value receiver when it only reads.

public/private ⇄ Viết hoa/viết thườngpublic/private ⇄ Upper/lower case

Go không có từ khoá truy cập. Tên viết HOA chữ đầu được export ra ngoài package, viết thường là nội bộ package.Go has no access modifiers. A Capitalised name is exported from the package; lower-case stays package-private.

Java
public class PriceCalculator {
    public long total(Cart c) { ... }   // visible everywhere
    private long tax(long v) { ... }    // class only
}
Go
package pricing

func Total(c Cart) int64 { ... } // exported
func tax(v int64) int64 { ... }  // package-private
Phạm vi trong Go là theo package, không theo struct — các file cùng package thấy nhau hết.Go scopes visibility by package, not by struct — files in the same package see everything.

Constructor ⇄ hàm NewXConstructor ⇄ NewX function

Go không có constructor. Quy ước là viết hàm NewX trả về struct đã khởi tạo, có thể kèm error.Go has no constructors. The convention is a NewX function returning an initialised struct, maybe with an error.

Java
public Client(String baseUrl) {
    if (baseUrl.isBlank()) throw new IllegalArgumentException("baseUrl");
    this.baseUrl = baseUrl;
}
Go
func NewClient(baseURL string) (*Client, error) {
	if baseURL == "" {
		return nil, errors.New("baseURL is required")
	}
	return &Client{baseURL: baseURL}, nil
}
Thiết kế để zero value của struct vẫn dùng được khi có thể (như sync.Mutex, bytes.Buffer).Where possible, make the struct's zero value usable (like sync.Mutex, bytes.Buffer).

enum ⇄ const + iotaenum ⇄ const + iota

Go không có enum. Dùng kiểu riêng + hằng số với iota để đánh số tự động.Go has no enums. Use a named type + constants with iota for automatic numbering.

Java
public enum OrderStatus {
    PENDING, PAID, SHIPPED, CANCELLED
}
Go
type OrderStatus int

const (
	Pending OrderStatus = iota
	Paid
	Shipped
	Cancelled
)
Thêm method String() để in tên đẹp; dùng go generate stringer để tự sinh.Add a String() method for readable output; go generate stringer can produce it.

Hướng đối tượngObject orientation

implements ⇄ interface ngầm địnhimplements ⇄ implicit interfaces

Java bắt khai báo implements. Go: type nào có đủ method thì tự động thoả interface — interface thường do bên DÙNG định nghĩa.Java requires implements. In Go any type with the right methods satisfies the interface — interfaces are usually defined by the CONSUMER.

Java
public interface Notifier { void send(String msg); }

public class SmsNotifier implements Notifier {
    public void send(String msg) { ... }
}
Go
type Notifier interface{ Send(msg string) error }

type SmsNotifier struct{}

func (SmsNotifier) Send(msg string) error { return nil }
“Accept interfaces, return structs” — nhận interface nhỏ làm tham số, trả về kiểu cụ thể.“Accept interfaces, return structs” — take small interfaces as params, return concrete types.

extends ⇄ embedding (composition)extends ⇄ embedding (composition)

Go không có kế thừa. Nhúng (embed) một struct vào struct khác để 'mượn' field và method — đây là composition, không phải is-a.Go has no inheritance. Embed one struct in another to 'borrow' its fields and methods — composition, not is-a.

Java
public abstract class BaseEntity {
    protected Instant createdAt;
}

public class Product extends BaseEntity {
    private String name;
}
Go
type Audit struct {
	CreatedAt time.Time
}

type Product struct {
	Audit // embedded: p.CreatedAt works
	Name  string
}
Nhiều pattern GoF dùng kế thừa (Template Method) sẽ được viết lại bằng embedding hoặc truyền hàm trong Go.GoF patterns built on inheritance (Template Method) are rewritten with embedding or function params in Go.

GenericsGenerics

Cả hai đều có generics. Go dùng type parameter trong ngoặc vuông và constraint là interface.Both have generics. Go uses square-bracket type parameters with interfaces as constraints.

Java
public static <T extends Comparable<T>> T max(List<T> xs) {
    return xs.stream().max(Comparator.naturalOrder()).orElseThrow();
}
Go
func Max[T cmp.Ordered](xs []T) T {
	m := xs[0]
	for _, x := range xs[1:] {
		m = max(m, x)
	}
	return m
}
Trong Go, chỉ dùng generics khi thật sự cần — interface hoặc code cụ thể thường đủ.In Go reach for generics only when needed — interfaces or concrete code are often enough.

Lambda/functional interface ⇄ func valueLambdas ⇄ function values

Hàm trong Go là giá trị hạng nhất: gán vào biến, truyền làm tham số, trả về — nhiều pattern (Strategy, Command) chỉ cần một kiểu hàm.Go functions are first-class values: assign, pass, return — many patterns (Strategy, Command) need just a function type.

Java
Function<Long, Long> vat = amount -> amount * 110 / 100;
long total = vat.apply(100_000L);
Go
type PriceFn func(int64) int64

var vat PriceFn = func(a int64) int64 { return a * 110 / 100 }
total := vat(100_000)
Closure trong Go bắt biến theo tham chiếu — cẩn thận khi dùng trong vòng lặp với goroutine (Go 1.22 đã sửa biến vòng lặp).Go closures capture variables by reference — careful in loops with goroutines (Go 1.22 fixed loop variables).

Lỗi & nullErrors & null

Exception ⇄ error là giá trịExceptions ⇄ errors as values

Java ném exception và bắt bằng try/catch. Go trả error như giá trị cuối cùng, caller kiểm tra ngay — luồng lỗi hiện rõ trong code.Java throws and catches exceptions. Go returns an error as the last value and the caller checks it — the error path is explicit.

Java
try {
    Order o = orderService.place(cmd);
} catch (OutOfStockException e) {
    return ResponseEntity.status(409).build();
}
Go
o, err := svc.Place(ctx, cmd)
if errors.Is(err, ErrOutOfStock) {
	return http.StatusConflict
}
if err != nil {
	return fmt.Errorf("place order: %w", err)
}
Bọc lỗi bằng %w để giữ chuỗi nguyên nhân; kiểm tra bằng errors.Is / errors.As.Wrap errors with %w to keep the cause chain; inspect with errors.Is / errors.As.

null/Optional ⇄ zero value, nil, comma-oknull/Optional ⇄ zero values, nil, comma-ok

Go không có Optional. Mọi biến có zero value; tra cứu dùng idiom comma-ok để biết có tồn tại hay không.Go has no Optional. Every variable has a zero value; lookups use the comma-ok idiom to tell presence.

Java
Optional<User> u = userRepository.findById(id);
String name = u.map(User::getName).orElse("guest");
Go
u, ok := users[id]
name := "guest"
if ok {
	name = u.Name
}
Hàm tìm kiếm trong Go thường trả (T, bool) hoặc (T, error) với lỗi sentinel như sql.ErrNoRows.Go lookup functions typically return (T, bool) or (T, error) with a sentinel like sql.ErrNoRows.

Spring ⇄ Go idiomSpring ⇄ Go idioms

DI container ⇄ wiring thủ côngDI container ⇄ manual wiring

Spring tự tạo và tiêm bean. Trong Go, bạn tự 'lắp ráp' dependency trong main() — dài hơn một chút nhưng nhìn là hiểu.Spring creates and injects beans. In Go you assemble dependencies in main() — a bit longer, but obvious.

Java
@Service
public class OrderService {
    private final OrderRepository repo;
    public OrderService(OrderRepository repo) { this.repo = repo; }
}
Go
func main() {
	db := mustOpenDB()
	repo := postgres.NewOrderRepo(db)
	svc := order.NewService(repo)
	http.ListenAndServe(":8080", api.NewRouter(svc))
}
Dự án Go lớn có thể dùng google/wire (sinh code lúc build) hoặc uber/fx (runtime).Large Go projects may use google/wire (compile-time codegen) or uber/fx (runtime).

Annotation ⇄ code tường minh / struct tagAnnotations ⇄ explicit code / struct tags

Spring dùng annotation cho routing, validate, transaction. Go không có annotation: routing là code, metadata JSON/DB là struct tag.Spring uses annotations for routing, validation, transactions. Go has none: routing is code, JSON/DB metadata are struct tags.

Java
@RestController
public class ProductController {
    @GetMapping("/products/{id}")
    public ProductDto get(@PathVariable long id) { ... }
}
Go
type ProductDTO struct {
	ID   int64  `json:"id"`
	Name string `json:"name"`
}

mux.HandleFunc("GET /products/{id}", h.Get) // Go 1.22 patterns
@Transactional trong Go thường là một hàm WithTx(ctx, func(tx) error) bạn tự viết.Go's @Transactional is usually a WithTx(ctx, func(tx) error) helper you write.

Filter/AOP ⇄ middlewareFilters/AOP ⇄ middleware

Logic cắt ngang (log, auth, metrics) trong Spring là Filter/Interceptor/AOP; trong Go là hàm bọc http.Handler.Cross-cutting logic (logging, auth, metrics) is a Filter/Interceptor/AOP in Spring; in Go it's a function wrapping http.Handler.

Java
@Component
public class TimingFilter extends OncePerRequestFilter {
    protected void doFilterInternal(HttpServletRequest rq, HttpServletResponse rs,
                                    FilterChain chain) throws ServletException, IOException {
        long t = System.nanoTime();
        chain.doFilter(rq, rs);
        log.info("took {}ms", (System.nanoTime() - t) / 1_000_000);
    }
}
Go
func Timing(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t := time.Now()
		next.ServeHTTP(w, r)
		slog.Info("took", "ms", time.Since(t).Milliseconds())
	})
}
Đây chính là Decorator + Chain of Responsibility mà bạn sẽ học trong lộ trình.This is exactly Decorator + Chain of Responsibility, which you'll learn on the path.

Đồng thờiConcurrency

Thread ⇄ goroutineThreads ⇄ goroutines

Goroutine rất nhẹ (vài KB stack), chạy hàng trăm nghìn cái dễ dàng. Java 21 có virtual thread với ý tưởng tương tự.Goroutines are tiny (a few KB of stack); hundreds of thousands are fine. Java 21 virtual threads share the idea.

Java
try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
    for (var id : orderIds) {
        exec.submit(() -> notifier.send(id));
    }
} // waits for all tasks
Go
var wg sync.WaitGroup
for _, id := range orderIDs {
	wg.Add(1)
	go func() {
		defer wg.Done()
		notifier.Send(id)
	}()
}
wg.Wait()
Luôn biết goroutine của bạn kết thúc khi nào — goroutine bị treo là rò rỉ bộ nhớ.Always know when your goroutine ends — a stuck goroutine is a memory leak.

BlockingQueue ⇄ channelBlockingQueue ⇄ channel

Channel là hàng đợi có kiểu dữ liệu, dùng để goroutine trao đổi dữ liệu thay vì chia sẻ bộ nhớ.A channel is a typed queue goroutines use to exchange data instead of sharing memory.

Java
BlockingQueue<Job> q = new ArrayBlockingQueue<>(100);
q.put(job);            // blocks when full
Job next = q.take();   // blocks when empty
Go
q := make(chan Job, 100)
q <- job       // blocks when full
next := <-q    // blocks when empty
close(q)       // only the sender closes
“Don't communicate by sharing memory; share memory by communicating.” — Go proverb.“Don't communicate by sharing memory; share memory by communicating.” — Go proverb.

synchronized ⇄ sync.Mutexsynchronized ⇄ sync.Mutex

Khi thật sự phải chia sẻ trạng thái, Go dùng Mutex tường minh, thường kèm defer Unlock.When state must be shared, Go uses an explicit Mutex, usually with defer Unlock.

Java
public class Counter {
    private long n;
    public synchronized void inc() { n++; }
}
Go
type Counter struct {
	mu sync.Mutex
	n  int64
}

func (c *Counter) Inc() {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.n++
}
Chạy test với go test -race để phát hiện data race sớm.Run tests with go test -race to catch data races early.

CompletableFuture ⇄ goroutine + channel / errgroupCompletableFuture ⇄ goroutine + channel / errgroup

Gọi song song nhiều service rồi gộp kết quả: Java dùng CompletableFuture, Go dùng errgroup (huỷ tất cả khi một cái lỗi).Call services in parallel and combine: Java uses CompletableFuture, Go uses errgroup (cancels all on first error).

Java
var user  = CompletableFuture.supplyAsync(() -> users.get(id));
var order = CompletableFuture.supplyAsync(() -> orders.last(id));
Profile p = user.thenCombine(order, Profile::new).join();
Go
g, ctx := errgroup.WithContext(ctx)
var u User
var o Order
g.Go(func() (err error) { u, err = users.Get(ctx, id); return })
g.Go(func() (err error) { o, err = orders.Last(ctx, id); return })
if err := g.Wait(); err != nil {
	return Profile{}, err
}
context.Context là cách chuẩn trong Go để truyền deadline và tín hiệu huỷ xuyên suốt các lời gọi.context.Context is Go's standard way to carry deadlines and cancellation through calls.