Java this 키워드 완전 정복 — 인스턴스 자기 참조

Java this 키워드의 세 가지 용법을 완전 정복한다. 필드/매개변수 구별, 현재 객체 참조 반환(메서드 체이닝), this()를 통한 생성자 위임까지 원리부터 활용 패턴까지 설명한다

· 7 min read · PALDYN Team

지난 글에서 생성자의 모든 것을 다뤘는데, 코드 곳곳에 this가 등장했다. 이번 글에서는 this가 정확히 무엇이며, 어떤 세 가지 상황에서 어떻게 활용하는지 원리부터 파헤친다.

this가 가리키는 것

this현재 인스턴스의 참조(주소) 를 담은 숨겨진 변수다. 컴파일러는 모든 인스턴스 메서드(생성자 포함)에 암묵적으로 this를 첫 번째 매개변수로 전달한다. 개발자 눈에는 보이지 않지만 바이트코드 레벨에서는 항상 존재한다.

class Counter {
    int count;

    void increment() {
        // 컴파일러가 변환하면: void increment(Counter this) { this.count++; }
        count++; // 암묵적으로 this.count++
    }
}

Counter c = new Counter();
c.increment(); // 내부에서 this = c (힙 주소 0x4A2F 등)

c.increment()를 호출하면 JVM은 c가 가리키는 객체의 주소를 this로 전달한다. this는 그 객체를 향하는 포인터라고 이해하면 된다.

용법 1 — 필드와 매개변수 이름 충돌 해결

생성자나 setter에서 매개변수 이름을 필드 이름과 동일하게 쓸 때 this.필드명으로 인스턴스 필드임을 명시한다.

class Member {
    String name;
    int    age;

    Member(String name, int age) {
        this.name = name; // this.name → 인스턴스 필드, name → 매개변수
        this.age  = age;
    }

    void setName(String name) {
        this.name = name; // setter에서도 동일 패턴
    }
}

this를 생략하면 양쪽 모두 매개변수를 참조하게 되어 필드가 초기화되지 않는다. IntelliJ 같은 IDE는 이 경우를 경고로 표시하지만, 컴파일 오류는 나지 않으므로 런타임 버그로 이어질 수 있다.

// 잘못된 예 — 필드가 초기화되지 않음
Member(String name) {
    name = name; // name(매개변수) = name(매개변수) — 필드 unchanged
}

this 키워드의 3가지 용법

용법 2 — 현재 객체 참조 반환 (메서드 체이닝)

메서드에서 return this를 반환하면 같은 객체에 대한 참조를 돌려준다. 이 덕분에 점(.)으로 메서드를 연속 호출하는 메서드 체이닝이 가능하다.

class QueryBuilder {
    private String table;
    private String condition;
    private int    limit;

    QueryBuilder from(String table) {
        this.table = table;
        return this; // 자기 자신을 반환 → 체이닝 가능
    }

    QueryBuilder where(String condition) {
        this.condition = condition;
        return this;
    }

    QueryBuilder limit(int limit) {
        this.limit = limit;
        return this;
    }

    String build() {
        return "SELECT * FROM " + table
             + " WHERE " + condition
             + " LIMIT " + limit;
    }
}

// 메서드 체이닝 사용
String sql = new QueryBuilder()
    .from("users")
    .where("age > 18")
    .limit(10)
    .build();

return this 패턴은 빌더 패턴(Builder Pattern) 의 핵심이기도 하다. Lombok@Builder, StringBuilder, Stream.Builder 등 Java 표준 API 곳곳에서 이 패턴을 볼 수 있다.

return this — 메서드 체이닝 패턴

용법 3 — this()로 생성자 위임

this(인수)같은 클래스의 다른 생성자를 호출한다. 생성자 오버로딩에서 중복 초기화 코드를 한 곳으로 모을 때 사용한다.

class Product {
    String name;
    int    price;
    String category;

    Product(String name) {
        this(name, 0); // ① name, price=0 생성자에 위임 — 반드시 첫 줄
    }

    Product(String name, int price) {
        this(name, price, "기타"); // ② 3-인수 생성자에 위임 — 반드시 첫 줄
    }

    Product(String name, int price, String category) { // ③ 실제 초기화
        this.name     = name;
        this.price    = price;
        this.category = category;
    }
}

this() 호출에는 두 가지 엄격한 제약이 있다.

  • 반드시 생성자 본문의 첫 번째 문장이어야 한다.
  • 한 생성자에서 한 번만 사용 가능하다.

두 규칙을 어기면 컴파일 오류가 발생한다.

static 맥락에서 this는 사용 불가

static 메서드나 static 초기화 블록에서는 this를 쓸 수 없다. 정적 멤버는 인스턴스 없이 클래스 레벨에서 호출되므로, 가리킬 “현재 인스턴스”가 존재하지 않는다.

class Util {
    static int count = 0;

    static void reset() {
        count = 0;    // OK — 정적 필드 직접 참조
        // this.count = 0; // 컴파일 에러: 'this' cannot be used in a static context
    }
}

람다 내부의 this — 익명 클래스와 다르다

람다 내부에서 this람다를 감싸는 클래스의 인스턴스를 참조한다. 이것이 익명 클래스와의 핵심 차이다.

class Printer {
    String name = "Printer";

    Runnable getLambda() {
        return () -> System.out.println(this.name); // this → Printer 인스턴스
    }

    Runnable getAnonymous() {
        return new Runnable() {
            String name = "Anonymous";

            @Override
            public void run() {
                System.out.println(this.name); // this → 익명 클래스 인스턴스
            }
        };
    }
}

Printer p = new Printer();
p.getLambda().run();    // "Printer"
p.getAnonymous().run(); // "Anonymous"

람다는 새로운 스코프를 만들지 않고 둘러싼 클래스의 this를 그대로 캡처한다.

주요 활용 패턴 정리

class FluentPerson {
    private String name;
    private int    age;

    // ① 필드 초기화 — this.필드 = 매개변수
    FluentPerson(String name, int age) {
        this.name = name;
        this.age  = age;
    }

    // ② 생성자 위임 — this()
    FluentPerson(String name) {
        this(name, 0); // age 기본값 0
    }

    // ③ 메서드 체이닝 — return this
    FluentPerson withAge(int age) {
        this.age = age;
        return this;
    }

    @Override
    public String toString() {
        return name + "(" + age + ")";
    }
}

// 세 가지 용법이 모두 조화
var p = new FluentPerson("Alice").withAge(30);
System.out.println(p); // Alice(30)
용법형식주목적
필드 구별this.필드명이름 충돌 해결
현재 객체 반환return this메서드 체이닝·빌더
생성자 위임this(인수)오버로딩 중복 제거

지난 글: Java 생성자 완전 정복 — 기본·매개변수·오버로딩·체이닝

다음 글: Java 메서드 오버로딩 — 같은 이름, 다른 매개변수


읽어주셔서 감사합니다. 😊