Skip to content

1차 세미나 | 키워드 과제 정리

Minjae Lee edited this page Apr 8, 2024 · 32 revisions

1. this와 this() 키워드

this : 인스턴스 자기자신을 가리킬 때 사용하는 참조(reference) 변수

1차 세미나의 예시를 살펴볼까요?

Person이라는 클래스가 만들어져있고, 이 Person 클래스의 객체(=인스턴스)를 찍어낼 때 호출되는 생성자가 구현되어있는 것을 확인할 수 있죠.
생성자에서는 Person 클래스 내부 변수인 name, age, sex에 각각 값을 할당하기 위해 사용자로부터 파라미터로 각각 name, age, sex 값을 받아오고 있습니다.

이때 생성자 메서드에서 인수로 받은 name과 Person 클래스 안에 있는 name을 구분해주기 위해 this라는 키워드를 사용할 수 있습니다!
앞의 this.name은 Person 클래스 자기자신이 갖고 있는 private String name 변수에 해당,
뒤의 name은 생성자 파라미터로 받은 String name에 해당한다고 생각하면 구분이 쉬울 것 같네요!

생성자의 매개변수 이름과 인스턴스 내부 변수의 이름이 같은 경우, 이를 구분해주기 위해 인스턴스 변수 앞에 this를 붙이게 됩니다.
추가로, static 메서드에서는 this 키워드를 사용할 수 없습니다! -> static 메서드는 특정 객체(인스턴스)에 속한 것이 아니므로 인스턴스 내부 변수란 개념이 따로 없겠죠!

public class Person {
    private String name;
    private int age;
    private String sex;

    // 생성자 : 인스턴스가 생성될 때 호출되는, 인스턴스 초기화 메서드
    public Person(String name, int age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
}

만약, 매개변수 이름과 인스턴스 내부 변수 이름이 다르다면? this를 붙여주지 않아도 구분이 가능하니 붙여주지 않아도 되겠죠!
그래도, 모든 인스턴스 변수에는 this 참조 변수가 숨겨진 지역 변수로 존재는 하고 있답니다 ^__^

public Person(String name1, int age, String sex) {
    name = name1;    // this를 사용하지 않아도 인수와 구분이 가능하니 this를 붙여주지 않아도 된다! (this도 사용가능)
    this.age = age;
    this.sex = sex;
}

this() : 같은 클래스의 다른 생성자를 호출할 때 사용하는 메서드

생성자 내부에서 호출하는 메서드입니다.
클래스 내부에 있는 생성자 중에서, 메서드의 이름, 파라미터 개수가 일치하는 생성자를 찾아 호출해주는 기능을 하는데, 이게 무슨 말인가 할 수 있으니 코드를 보며 설명해보겠습니다!

두 번째 메서드 public Person() 역시 생성자에 해당합니다.
단, 여기서 값을 바로 대입한다는 느낌이 아니라 위에 있는 this()를 활용하여 public Person(String name1, int age, String sex) 생성자를 호출해 객체를 초기화해주는 역할을 수행한다고 생각하면 되겠습니다!

클래스 내부에서 이미 만들어진 생성자를 호출해 초기화를 하고 싶은 경우에는 this() 키워드로 생성자를 만들어줄 수 있습니다.

public class Person {
    private String name;
    private int age;
    private String sex;

    // 생성자 : 인스턴스가 생성될 때 호출되는, 인스턴스 초기화 메서드
    public Person(String name1, int age, String sex) {
        this.name = name1;
        this.age = age;
        this.sex = sex;
    }

    // this() 사용 : 메서드 이름, 파라미터 개수가 맞는 위의 생성자를 찾아 호출을 해 객체를 만들게 될 것이다.
    public Person() {
        this("민재리", 25, "male");   
    }
}

2. Java의 Generic 타입

Generic 기본 개념

Generic은 해석하면 "일반적인" 이라는 뜻을 갖고 있습니다.
그럼 Generic 타입은? 일반적인 타입? 잘 와닿지 않을텐데요... 더 자세하게 말하면 데이터 타입을 일반화한다는 의미라고 정의할 수 있을 것 같아요!

그럼 다시 일반화란 무엇인가요? 라는 질문으로 넘어갈 것 같은데,
일반화는 개별 사례들의 공통되는 속성을 일반적인 개념이나 주장으로 뽑아내는 개념입니다! 추상화의 한 형태라고 볼 수 있죠.
뭐 예시를 들어보자면 개, 고양이, 사자, 호랑이와 같은 다양한 종류의 생명체들을 "동물"이라는 포괄적이고 공통되는 특성으로 묶는 것이 일반화에 해당한다고 볼 수 있습니다.

이를 Java에 적용해보면, 메서드(클래스도 해당)를 하나 만들었는데 int 타입도 지원하고 싶고, Double 타입도 지원하고 싶고, String 타입도 지원하고 싶을 때!
이 int, Double, String을 T(Type)라는 포괄적이고 공통되는 키워드로 일반화시켜 사용할 수 있도록 한거죠! 이게 Generic 타입입니다.

위와 같은 핵심 개념을 이제 자세하고 조금 어렵게 풀어서, "특정타입이 클래스나 메서드 내부가 아닌 외부에서 사용자에 의해 지정되는 것" 혹은 "클래스나 메서드에서 사용할 내부 데이터 타입을 컴파일 시에 지정하는 것"과 같이 설명할 수도 있습니다!

코드로 살펴보죠!
무슨 타입이던지 담을 수 있는 박스 클래스를 만들어보겠습니다.
클래스 안에는 모든 값을 다 담을 수 있는 item, Setter, Getter가 모두 구현되어 있습니다.

public class Box<T> {
    private T item;

    public void setItem(T newItem) {
        this.item = newItem;
    }

    public T getItem() {
        return this.item;
    }
}

그럼 위에서 만든 Box 클래스를 숫자를 담을 수 있는 integerBox와 문자열을 담을 수 잇는 stringBox로 각각 만들어줄 수 있게 됩니다.
Box 클래스는 제네릭으로 선언되어 있기 때문에, 같은 Box 클래스를 사용하면서 동시에 다양한 데이터 타입을 담을 수 있게 되는거죠!

public class Main {
    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<>();
        integerBox.setItem(0);
        System.out.println(integerBox.getItem());   // 0

        Box<String> stringBox = new Box<>();
        stringBox.setItem("애플");
        System.out.println(stringBox.getItem());    // 애플
    }
}

Generic 타입에서의 기본 타입과 참조 타입

여기서 잠깐!
다시 세미나 자료로 돌아와서 "기본 타입은 제네릭 타입에서 사용할 수 없지만, 참조 타입은 가능한데요"라고 설명해주셨던 파트장님의 말을 이해해봅시다.

Java의 원시(기본) 타입은 boolean, byte, short, int, long, float, double, char까지 총 8개가 존재했습니다.
즉, Generic에서 위 8개의 타입을 사용할 경우에는 아래와 같은 에러가 발생하게 된다는 의미라고 볼 수 있죠.

스크린샷 2024-04-02 오후 1 46 39

왜 원시 타입은 Generic 타입에서 사용할 수 없는 것일까요?

이는 제네릭의 동작 방식이 "컴파일 시점에 타입을 지정하여, 컴파일러가 타입 체크를 수행한다"는 점과 관련이 있습니다.
제네릭은 객체에 대한 참조(Reference)를 다루면서 위 작업을 수행할 수 있는데, 객체가 아닌 값(= 메모리 내에 고정된 공간을 이미 차지하고 있음)에 속하는 원시 타입은 제네릭의 동작 방식에 적합하지 않은 것이죠!

Generic 사용 방법과 필요성

Generic은 클래스와 인터페이스, 그리고 메서드에 선언할 수 있습니다.
클래스, 인터페이스는 class/interface 클래스명/인터페이스명<T>의 구조로, 메서드는 <T> 반환타입 메서드명(T t) 구조로 사용할 수 있습니다.

위에서 라고 표시한 T는 "타입 파라미터"라고 부르는 녀석인데요,
따로 이걸 써야한다와 같은 규칙은 없지만, 관습적으로 아래 단어들의 첫 대문자를 따와 타입 성질에 맞춰 사용하곤 합니다!

<T> Type 일반적으로 사용하는 제네릭 Type
<E> Element 주로 자바 Collection framework에 사용되는 Element
<K> Key map에서 매개변수로 사용되는 Key
<V> Value map에서 매개변수로 사용되는 Value
<N> Number 숫자를 나타낼 때 사용하는 Number
<R> Result 결괏값을 나타내는 Result

이렇게 Generic을 사용하면, 다양한 데이터 타입에 대해 동작할 수 있게 되니 코드의 확장성재사용성이 높아지게 됩니다.

또한, 코드의 타입 안정성을 향상시키는데도 도움이 된다고 합니다.
다양한 데이터 타입을 사용할 수 있게 되는 것과 타입을 안정적으로 사용할 수 있는 것이 무슨 상관일까? 라는 질문이 있을 수 있어 위의 Box 클래스 예시를 이어서 설명해보겠습니다.

Generic으로 선언되어 있는 Box 클래스의 인스턴스를 생성할 때, 타입이 정의되기 때문에 다른 타입의 데이터를 저장하려고 하면 컴파일 에러를 발생시킬 수 있게 됩니다.
getItem의 경우에도 일일이 타입 캐스팅을 하지 않아도 되기에, 휴먼 이슈를 줄일 수 있는 거죠!

public class Main {
    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<Integer>();
        integerBox.setItem(10); 

        Box<String> stringBox = new Box<String>();
        stringBox.setItem("Hello World"); 
        // stringBox.setItem(0); // 컴파일 에러: String 타입의 Box에 Integer를 할당하려고 하면 컴파일 시점에 에러 발생

        // 타입 캐스팅 불필요 : 만약, Generic이 아니었다면 (Integer) integerBox.getItem();의 형태로 타입 캐스팅이 필요했을 것임
        // 이 타입 캐스팅은 개발자가 처리해줘야 하기 때문에 휴먼 이슈를 발생시켜 타입 안정성이 저하된다고 볼 수 있음
        Integer intValue = integerBox.getItem(); 
        String stringValue = stringBox.getItem(); 

        System.out.println(intValue);
        System.out.println(stringValue);
    }
}

Generic 와일드카드

Generic Wildcard란 것도 있습니다. 간략하게만 보고 넘어가봅시다.
계속 반복인데 Generic은 보통 타입을 라고 선언하면, 이 안에는 Integer를 넣어도 String을 넣어도 모두 가능하게 되는 것을 의미했었죠?
그런데 만약 여기에 넣을 수 있는 타입의 범위를 지정하고 싶어질 때? 사용할 수 있는 것이 바로 Generic 와일드카드입니다.

Generic 와일드카드는 비한정 와일드카드, 상한 제한 와일드카드, 하한 제한 와일드카드 3가지의 종류로 나누어집니다. (List는 Generic 타입의 예시로 작성)

  • 비한정 와일드카드 List<?> : 모든 클래스나 인터페이스 타입이 올 수 있다는 것. 말 그대로 한정하지 않는다는 뜻
  • 상항 제한 와일드카드 List<? extends Number> : 해당 타입 또는 해당 타입의 하위 클래스만 올 수 있다는 뜻. (예시는 Number 타입과 Number의 하위 클래스 타입들(예: Integer, Double 등)의 리스트만 허용한다는 의미)
  • 비한정 와일드카드 List<? super Integer> : 해당 타입 또는 해당 타입의 상위 클래스만 올 수 있다는 뜻.

제네릭 와일드카드로 코드의 유연성을 높여줄 수 있지만, 이는 Generic 타입의 안정성을 해칠 수도 있기에 조심해서 사용해야겠습니다!


3. final, static, static final

final : "최종적인"의 의미를 갖게 하는 키워드

"최종적인"이라는 것은 "이후에 변경할 없다"라는 의미라고 볼 수 있겠죠?
다양하게 변수, 메서드, 클래스에 final 키워드를 붙여 "최종적으로 더 이상 변경할 수 없는 의미"를 지니도록 만들게 됩니다.

  • 변수에서의 final : 초기화 이후로는 값을 변경할 수 없는 상수(Constant)가 됩니다.
  • 메서드에서의 final : 오버라이드(상위 객체의 메서드를 하위 객체에서 재정의하는 것) 될 수 없는 메서드가 됩니다.
  • 클래스에서의 final : 더 이상 상속할 수 없는 클래스가 됩니다. 다시 말해, final 클래스는 부모 클래스가 될 수 없다는 뜻입니다.

static : "정적인, 고정된"의 의미를 갖게 하는 키워드

"정적인, 고정된"의 의미를 갖고 있는 static은 말 그대로 "객체(인스턴스)가 아니라 클래스에 고정되어 있다"것을 의미합니다.
사실, 정확하게 말하면 클래스에 고정되어 있다는 의미보다는 메모리에 고정적으로 할당되어 있기 때문에, 인스턴스 생성과는 무관하게 사용할 수 있다는 말이 정확하다고 볼 수 있지만! 그냥 이해의 편리성을 위해 인스턴스가 아니라 클래스에 고정된 필드와 메서드라고 이해합시다.

static 키워드로 선언된 필드, 메서드는 프로그램이 시작될 때 생성되서, 종료될 때 사라집니다.
static을 호출하기 위해서는 클래스에 고정되어있기 때문에 별도의 인스턴스 이름으로 호출하지 않고, 클래스이름.static멤버이름으로 호출해서 사용하게 됩니다.

static final : 클래스에 고정되어있고, 동시에 값을 변경할 수 없는 키워드

final static으로 써도 상관 없습니다.
위에서 설명했던 static 키워드와 final 키워드를 합친 그대로의 의미를 갖게 된다고 생각하면 됩니다.

인스턴스 생성과는 상관없이 클래스에 고정되어 있어 무관하게 사용할 수 있으면서 (static), 값이 변할 수 없는 상수 (final)의 역할을 갖게 되는 키워드입니다!

final : 변수의 변경 불가능성 제어, static : 클래스 레벨에서의 공유성 제어를 각각 조절하는 키워드


4. super와 super() 키워드

super : 부모 클래스의 멤버(필드, 메서드)를 가리킬 때 사용하는 키워드

자기 자신을 가리킬 때는 this라는 키워드를 사용한다고 앞에서 배웠다면, 자식 클래스의 입장에서 부모 클래스가 갖고 있는 것들을 가리키고 싶을 때 사용하는 키워드는 super입니다.

인스턴스 내부 멤버와 파라미터로 받는 값의 이름이 동일할 때, 이를 구분하기 위해 내부 멤버에는 this라는 키워드를 사용해서 구분했었는데요. 동일하게 이번에는 부모 클래스가 갖고 있는 멤버와 자식 클래스가 갖고 있는 멤버의 이름이 동일할 때, 부모 클래스의 멤버를 접근할 때는 super라는 키워드를 사용해서 구분할 수 있습니다!

만약, 설정해주지 않으면 하위 클래스의 멤버가 우선시되기 때문에 키워드를 사용하지 않으면 상위 클래스의 멤버를 접근할 수 없다고 합니다.

public abstract class Animal {
    String name = "동물입니다";

    public void printName() {
        System.out.println(name);  // 동물입니다
    }
}

// Animal 클래스는 부모 클래스, Turtle 클래스는 자식 클래스의 관계
public class Turtle extends Animal {
    String name = "거북입니다";

    public void printName() {
        System.out.println(super.name);  // 부모 클래스 멤버에 접근 -> 동물입니다
        System.out.println(name);        // 본인(자식 클래스) 멤버에 접근 -> 거북입니다
    }
}

super() : 부모 클래스의 생성자를 호출할 때 사용하는 메서드

부모 클래스를 상속받아 자식 클래스의 인스턴스를 생성하면, 부모 클래스에 들어있는 멤버(필드, 메서드) 역시 자식 클래스에 포함되어 있게 됩니다.
이때 자식 클래스의 인스턴스가 생성될 때 부모 클래스의 생성자를 호출하는 역할을 하는 것이 super() 메서드입니다.

기본적으로 직접 코드를 추가해주지 않더라도, 세미나에서 파트장님이 설명해주신 것처럼 기본 생성자를 호출하는 super();가 호출됩니다.
하지만, 기본 생성자가 아니라 파라미터를 통해 값을 받는 사용자 지정 생성자가 있다면, 이는 자동으로 추가되지 않기 때문에 해당 메서드를 사용해서 부모 클래스의 생성자를 호출해줘야 하는 것이죠!
super()는 이럴 때 사용합니다. 아래 코드도 같은 예시네요.

// 부모 클래스
public class Person {
    private String name;
    private int age;
    private String sex;

    // 생성자 : 인스턴스가 생성될 때 호출되는, 인스턴스 초기화 메서드
    public Person(String name, int age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
}

// 자식 클래스 : Person을 상속받음
public class Member extends Person {
    private Part part;

    public Member(String name, int age, String sex, Part part) {
        super(name, age, sex);  // super() 키워드로 부모 클래스의 생성자를 직접 지정해서 호출
        this.part = part;
    }
}

5. SOLID 원칙

SOLID 원칙은 Design Pattern 스터디에서 OOP의 개념에 대해 스터디원들의 이해를 돕기 위해 설명했던 자료가 있어 공유드립니다.
결국 SOLID를 공부할 때 중요한 것은, "프로그램의 재사용(확장과 변경)에 있어 유용할 수 있도록 객체를 구성한다는 것!" 이라고 말할 수 있겠네요.

SRP (Single Responsibility Principle) : 단일 책임 원칙

10

OCP (Open-Close Principle) : 개방-폐쇄 원칙

11

LSP (Liskov Substitution Principle) : 리스코프 치환 원칙

12

ISP (Interface Segregation Principle) : 인터페이스 분리 원칙

13

DIP (Dependency Inversion Principle) : 의존성 역전 원칙

14


6. 스프링의 의존성 주입 (Dependency Injection) 방식

스프링 컨테이너에서 관리하는 객체 Bean을 미리 생성하고, 이 Bean 객체를 원하는 객체에 주입하는 것이 의존성 주입입니다!

의존성 주입(Dependency Injection)이라는 개념을 배울 때,
**의존(Dependence)**이 무엇이고 **주입(Injection)**이 무엇인지 알지 못한다면 의존성 주입이라는 키워드에 대해 제대로 이해할 수 없을 것이기에 개념 정리 먼저 하고 가보겠습니다.

의존(Dependence)은 한 객체가 다른 객체를 참조하는 행위입니다.
그래서 흔히 "A 객체가 B 객체를 의존 (A->B와 같이 표현)"하면
1)"A 객체를 동작시키기 위해서는 B 객체가 반드시 있어야 한다"라고 표현할 수도 있고,
또 다르게는 2)"B 객체에 대해 변경사항이 발생하게 되었을 때, A 객체의 변경사항 반영도 동시에 이루어져야한다"라고 표현할 수 있습니다.

예시를 하나 들어보죠. 라면을 혼자서 끓여먹는 상황을 가정해보겠습니다.
라면을 끓여먹기 위해서는 일단 라면이 있어야 할 것이고, 냄비, 물, 불, 젓가락 등등이 필요할 것입니다.
만약, '물이 없다고 한다면?' 혹은 '라면이 없다고 한다면?' 저는 라면을 끓여먹을 수 없겠죠. 즉, 라면을 끓여먹는다는 개체를 동작시키기 위해서는 라면, 냄비, 물, 불, 젓가락 등의 객체가 반드시 있어야 합니다. 또한, 라면의 종류가 바뀌었을 때 라면을 끓여먹는다는 객체에 대해서도 영향이 미칠 것입니다.
다시 말해, "라면을 끓여먹는 객체는 라면, 냄비, 물, 불, 젓가락 등의 객체에 의존적이다"라고 말할 수 있습니다 ^__^ 이해가 되시나요?

주입은 외부에서 생성한 객체(값이나 메서드 해당)를 할당하는 행위를 의미합니다.

예시를 이어가보겠습니다.
위에서 말한 예시는 라면을 끓여먹기 위해 많은 객체들을 스스로 처리하고 있었습니다. 혼자서 끓여먹는 상황이었으니까요!
그런데 만약 라면을 분식집에 가서 먹는다고 한다면, 분식집 아주머니가 라면을 골라주실 것이고 물 양을 맞춰주실 거고..등등 라면을 만들어주실 겁니다.
이 경우 라면을 끓여먹기 위한 다른 객체들의 준비(=생성)가 외부에서 이루어진다고 볼 수 있습니다! 이것을 주입(Injection)이라고 말합니다.

23

종합하자면, 의존성 주입(Dependency Injection)은 객체에게 필요한 의존성(다른 객체)을 객체 외부에서 제공받는 방식을 말합니다.
그리고 Spring Boot에서 의존성을 주입해주는 객체 외부에 해당하는 것이 바로 "스프링 컨테이너"인거죠! (이제 이 글 처음에 써있던 말이 이해가 되실거라 믿습니다..!)

필드 주입 (Field Injection)

클래스에 있는 필드를 통해 직접 의존성을 주입하는 방식입니다.
별도의 메서드를 구현하지 않아도 되니 코드는 매우 짧아진다는 장점이 생기지만, 최근 필드에 직접 접근하는 것은 권장되지 않는 방식입니다.

public class SoptUser {
    @Autowired
    private final String name;

    @Autowired
    private final Part part;
}

수정자 주입(Setter Injection)

필드에 직접 접근하지 않고 간접적으로 접근하는 용도로 Setter를 활용하는 방식입니다.
메서드는 꼭 필수로 사용하는 것이 아닌 만큼 필수 사항이 아닌 의존성을 주입하거나, 설정되어있는 의존성을 수정해야하는 경우 이 방식을 사용할 수 있습니다.

public class SoptUser {
    private final String name;
    private final Part part;

    @Autowired
    public void setName(String name) {
        this.name = name;
    }
    @Autowired
    public void setPart(Part part) {
        this.part = part;
    }
}

생성자 주입(Constructor Injection)

생성자를 통해 의존성을 주입하는 방식을 말합니다.
Spring에서 3가지의 DI 방식 중에서 권장하고 있는 것이 바로 이 생성자 주입 방식인데요! 왜 그런지 이유를 생각해볼까요?

일단 생성자 주입 방식은 명확합니다.
별도의 함수를 사용하는 것이 아니라 객체를 생성할 때 반드시 호출되도록 되어 있기에, 생성하는 객체가 어떤 의존성을 가지고 있는지 파악하기가 직관적일 것입니다. -> 이 부분은 순환 참조를 방지하는데도 장점으로 다가옵니다!
또한 생성자 주입은 객체가 생성되는 처음에 의존성이 생기기에 이후로 의존성 주입을 놓칠 수 없게 된다는 점, 또한 의존성의 변경이 이루어질 수 없다는 점이 안정성을 높여주게 된다고 볼 수 있겠죠.

public class SoptUser {
    private final String name;
    private final Part part;

    @Autowired
    public SoptUser(String name, Part part) {
        this.name = name;
        this.part = part;
    }
}

7. Java Record

Java Record는 Python의 Dictionary, Kotlin의 Data Class와 같이 불변(immutable) 데이터를 효율적으로 다루기 위해 Java14에 도입된 기능입니다.
몇 가지 특징을 중심으로 알아보죠!

  • record는 데이터를 저장하고 전달하는 용도로 쓰입니다. -> 복잡한 로직을 작성하는 데 적합하지 않습니다.
  • record 내의 필드는 private final로 정의됩니다. -> 생성 시점에서 초기화된 데이터가 이후에 변경될 수 없도록 안정성을 제공합니다.
  • record는 다른 클래스를 상속받을 수 없으며, private final 필드 이외의 인스턴스 필드를 선언할 수 없습니다. -> 단, static으로 선언된 필드는 가능합니다.
  • 무엇보다도 같은 용도를 Class로 만들게 되면 필드, 생성자, getter 메서드, 그리고 equals(), hashCode(), toString() 메서드 등을 직접 정의해줬어야 하는데 record는 이를 자동으로 가지고 있습니다. -> 이 부분은 아래 코드로 살펴보죠!

Class를 사용했을 때 모두 다 작성 해줘야함

public class Person {
    private final String name;
    private final int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && Objects.equals(name, person.name);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
    
    @Override
    public String toString() {
        return "Person{" +
               "name='" + name + '\'' +
               ", age=" + age +
               '}';
    }
}

record는 위의 메서드가 자동으로 정의됨

아 여기서 추가로 알아둬야 할 점이 하나 있다면, Getter 메서드는 get+필드명()으로 지정해줬던 class와 다르게 record에서는 필드명() 메서드가 getter로 지정된다는 점 정도가 있을 것 같네요!

public record Person(
    String name, 
    int age
) {
}
Clone this wiki locally