
오늘은 Java의 클래스와 객체를 배웠다.
자바 프로그래밍 수업을 들은지 3년 전이나 지나서 거의 초면인 것 같다.
수업 시간에Car 클래스를 직접 작성하면서 필드와 생성자, 생성자 오버로딩, this, Getter/Setter가 어떤 역할을 하는지 하나씩 확인했다.
또 Lombok의 annotation과 Builder까지 같이 보면서 Java에서 반복되는 코드를 어떻게 줄일 수 있는지도 배웠다.
오늘의 학습 포인트는 다음과 같다.
클래스는 인스턴스 생성을 위한 템플릿이다
- 구성요소: 변수, 메서드
- 변수, 메서는 클래스 소유가 아닌 인스턴스 소유
변수에는 기본 값(9개), 참조타입이 있다.
기본 타입이 아닌 모든 것 = 주소를 담을 그릇
접근지정자 변수타입 변수명 = literal value;
실습 코드 Car.java를 복기해보자.
public class Car {
private String brand;
private String model;
}
private과 public의 차이는 하단에서 다루겠다. 그런데 실무에서는 주로 private을 쓰고 getter, setter함수를 통해서 validation을 검증한다고 한다.
Car는 자동차 객체를 만들기 위한 클래스다.
클래스 안에 선언한:
private String brand;
private String model;
은 자동차 객체가 가질 수 있는 값이다.
이를 필드(field) 또는 멤버 변수라고 한다.
예를 들어:
Car car = new Car("kia", "K5");
라는 객체를 만들면 개념적으로 다음과 같은 값이 들어간다.
Car 객체
brand → "kia"
model → "K5"
private으로 필드 선언하기Car.java에서 필드를 모두 private으로 선언했다.
private String brand;
private String model;
private은 해당 필드에 클래스 외부에서 직접 접근하지 못하도록 한다.
따라서 외부에서 이런 방식으로 사용하는 것은 불가능하다.
car.brand = "kia";
대신 클래스가 제공하는 메서드를 통해 값을 조회하거나 변경한다.
이 구조는 나중에 배우게 될 캡슐화와도 연결된다.
여기서 중요한 점은 private이 객체 생성을 막는 키워드는 아니라는 것이다.
private String brand;
private String model;
public Car() {
}
필드가 private이어도 생성자가 열려 있다면:
Car car = new Car();
처럼 객체를 만들 수 있다.
들어가기에 앞서 메서드와 생성자의 차이를 배웠다.
접근지정자 반환타입 메서드명 ([매개변수]){}
반드시 기억해야할 차이점은
메서드는 반환값이 있어 반환 타입을 명시하지만, 생성자에서는 반환타입을 명시하지 않는다.
그래서 이걸 like a method라고 한다.
가장 먼저 작성한 생성자는 다음과 같다.
public Car() {
}
생성자는 객체가 만들어질 때 호출된다.
일반 메서드와 비슷하게 생겼지만 몇 가지 특징이 있다.
생성자
- 클래스 이름과 이름이 같다.
- 반환 타입을 작성하지 않는다.
- 객체를 만들 때 호출된다.
- 보통 new 연산자와 함께 사용한다.
사용하는 코드는 다음과 같다.
Car car = new Car();
여기서:
new Car()
가 Car() 생성자를 호출한다. -> new 연산자 사용
객체를 만들면서 바로 값을 넣을 수도 있다.
public Car(String brand) {
this.brand = brand;
}
이제 객체를 만들 때 다음처럼 값을 전달할 수 있다.
Car car = new Car("kia");
"kia"가 생성자의 brand 매개변수로 전달된다.
그리고:
this.brand = brand;
를 통해 객체의 brand 필드에 저장된다.
흐름을 정리하면:
new Car("kia")
↓
생성자에 "kia" 전달
↓
brand 매개변수
↓
this.brand = brand
↓
객체의 brand에 "kia" 저장
생성자는 단순히 객체를 만드는 것뿐 아니라 객체가 처음 만들어질 때 필요한 값을 넣어주는 역할도 한다.
Car 클래스에는 생성자가 하나만 있는 것이 아니었다.
public Car() {
}
public Car(String brand) {
this.brand = brand;
}
public Car(String brand, String model) {
this.brand = brand;
this.model = model;
}
같은 이름의 생성자를 여러 개 만들 수 있다.
대신 매개변수의 개수나 타입이 달라야 한다.
이를 생성자 오버로딩(Constructor Overloading)이라고 한다.
Car car = new Car();
다음 생성자가 사용된다.
public Car() {
}
brand 하나만 전달하는 경우Car car = new Car("kia");
다음 생성자가 사용된다.
public Car(String brand) {
this.brand = brand;
}
객체 상태는 다음처럼 된다.
brand → "kia"
model → null
brand, model을 모두 전달하는 경우Car car = new Car("kia", "K5");
다음 생성자가 사용된다.
public Car(String brand, String model) {
this.brand = brand;
this.model = model;
}
결과:
brand → "kia"
model → "K5"
즉 객체를 어떤 형태로 만들고 싶은지에 따라 여러 생성자를 준비할 수 있다.
this생성자 안에서 가장 처음 눈에 들어온 표현은:
this.brand = brand;
였다.
양쪽에 같은 brand가 있어서 처음 보면 구분하기 어렵다.
왼쪽의:
this.brand
는 현재 객체가 가지고 있는 필드다.
오른쪽의:
brand
는 생성자가 전달받은 매개변수다.
따라서:
this.brand = brand;
는 다음 의미다.
전달받은
brand값을 현재 객체의brand필드에 저장한다.
같은 방식으로:
this.model = model;
도 이해할 수 있다.
this는 현재 만들어지고 있는 객체 자신을 가리키는 키워드라고 이해했다.
필드를 private으로 만들었기 때문에 외부에서는 직접 값을 읽을 수 없다.
그래서 Getter를 사용한다.
public String getBrand() {
return brand;
}
사용:
car.getBrand();
getBrand()를 호출하면 brand 값을 반환한다.
model도 같은 방식이다.
public String getModel() {
return model;
}
값을 변경할 때는 Setter를 사용할 수 있다.
public void setBrand(String brand) {
this.brand = brand;
}
사용:
car.setBrand("hyundai");
Setter 안에서도 this를 사용한다.
this.brand = brand;
의미는 생성자에서 사용한 것과 같다.
전달받은
brand값을 현재 객체의brand에 저장한다.
외부 코드
↓
Getter / Setter
↓
private 필드
필드를 외부에 바로 공개하지 않고 정해진 메서드를 통해 값을 다룰 수 있다.
오늘은 Car 객체의 정보를 반환하는 메서드도 작성했다.
public String carInfo() {
return brand + "\t" + model;
}
이 메서드는 매개변수가 없다.
대신 String 값을 반환한다.
Car car = new Car("kia", "K5");
System.out.println(car.carInfo());
결과:
kia K5
Getter를 이용해서 작성할 수도 있다.
public String carInfo() {
return getBrand() + "\t" + getModel();
}
클래스 내부에서는 자신의 private 필드에 접근할 수 있기 때문에 둘 다 사용할 수 있다.

위 사진처럼 오늘 여러 실습을 진행하기는 했지만 그 중 Car 클래스를 중점으로 살펴봤다.
package features.car;
public class Car {
private String brand;
private String model;
public Car() {
}
public Car(String brand) {
this.brand = brand;
}
public Car(String brand, String model) {
this.brand = brand;
this.model = model;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String carInfo() {
return brand + "\t" + model;
}
}
이 코드를 흐름으로 읽으면 다음과 같다.
Car 클래스 정의
↓
brand / model 필드 정의
↓
생성자를 통해 객체 초기화
↓
Getter로 값 조회
↓
Setter로 값 변경
↓
carInfo()로 객체의 기능 사용
처음에는 각각의 문법이 따로 보였는데 하나의 객체를 기준으로 보니 연결해서 이해하기 쉬웠다.
new를 이용한 객체 생성객체를 생성할 때 다음 코드를 사용했다.
Car car = new Car("kia", "K5");
이 한 줄을 나누면 다음과 같다.
Car car = new Car("kia", "K5");
타입 변수 이름 객체 생성
Car는 클래스 타입이다.
car는 만들어진 객체를 가리키는 변수다.
new Car("kia", "K5")
에서는 객체를 생성하면서 알맞은 생성자를 호출한다.
흐름은 다음과 같다.
new
↓
Car(String brand, String model) 호출
↓
필드 초기화
↓
Car 객체 생성
↓
car가 생성된 객체를 가리킴
Java에서는 생성자나 Getter/Setter처럼 반복해서 작성하는 코드가 많다.
Lombok을 사용하면 annotation으로 이런 코드를 줄일 수 있다.
예를 들어 직접 작성하면:
public Car() {
}
Lombok에서는:
@NoArgsConstructor
를 사용할 수 있다.
모든 필드를 매개변수로 받는 생성자도:
public Car(String brand, String model) {
this.brand = brand;
this.model = model;
}
직접 작성하는 대신:
@AllArgsConstructor
를 사용할 수 있다.
예:
@NoArgsConstructor
@AllArgsConstructor
public class Car {
private String brand;
private String model;
}
이렇게 작성해도 필요한 생성자를 사용할 수 있다.
Car car1 = new Car();
Car car2 = new Car("kia", "K5");
즉 annotation을 사용하면 객체 생성 방식이 달라지는 것이 아니라 반복해서 작성하던 생성자 코드를 줄일 수 있다.
@Getter, @SetterGetter와 Setter도 Lombok을 사용하면 줄일 수 있다.
직접 작성한 코드:
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
Lombok을 사용하면:
@Getter
@Setter
public class Car {
private String brand;
private String model;
}
처럼 작성할 수 있다.
필드가 많아질수록 직접 작성해야 할 Getter와 Setter 수도 많아진다.
이런 반복 코드를 annotation으로 줄일 수 있다는 점이 편리해 보였다.
객체를 만들 때 생성자 외에 Builder 방식도 사용할 수 있다.
Car car = Car.builder()
.brand("kia")
.model("K5")
.build();
생성자 방식은:
Car car = new Car("kia", "K5");
처럼 값을 순서대로 전달한다.
필드가 적을 때는 간단하다.
하지만 값이 많아지면:
Car car = new Car(
"kia",
"K5",
"black",
2026,
50000000
);
각 값이 무엇을 의미하는지 코드만 보고 바로 알기 어려울 수 있다.
Builder를 사용하면:
Car car = Car.builder()
.brand("kia")
.model("K5")
.color("black")
.year(2026)
.price(50000000)
.build();
처럼 어떤 값을 넣는지 이름으로 확인할 수 있다.
그래서 필드가 많아질수록 코드를 읽기 편해진다.
Car car = new Car("kia", "K5");
장점:
Car car = Car.builder()
.brand("kia")
.model("K5")
.build();
장점:
처음 배우는 단계에서는 생성자를 먼저 이해하고 Builder를 보면 구조가 더 잘 보이는 것 같다.
Java의 생성자는 내가 기존에 사용했던 TypeScript의 class와도 비슷한 부분이 있었다.
Java:
public class Car {
private String brand;
private String model;
public Car(String brand, String model) {
this.brand = brand;
this.model = model;
}
}
TypeScript:
class Car {
brand: string;
model: string;
constructor(
brand: string,
model: string
) {
this.brand = brand;
this.model = model;
}
}
둘 다 객체가 만들어질 때 생성자를 통해 값을 넣는 구조다.
TypeScript에서는 다음처럼 더 짧게 작성할 수도 있다.
class Car {
constructor(
public brand: string,
public model: string
) {}
}
문법은 다르지만 객체가 자신의 값을 가지고 시작한다는 구조는 비슷했다.
Getter와 Setter를 배우면서 React의 state가 떠올랐다.
Java:
private String brand;
public void setBrand(String brand) {
this.brand = brand;
}
React:
const [brand, setBrand] = useState("kia");
React에서도 state를 직접 수정하지 않고:
setBrand("hyundai");
를 사용한다.
둘은 같은 개념은 아니다.
Java의 Getter/Setter는 객체의 필드 접근을 관리하기 위한 방법이다.
React의 setter는 state 변경을 React에 알려 리렌더링으로 연결하기 위해 사용한다.
다만 두 개를 비교하면서 이런 공통점을 느꼈다.
데이터를 직접 여기저기서 바꾸기보다 정해진 방법을 통해 관리한다.
기존에 알고 있던 React와 연결해서 보니 Java의 객체 구조도 조금 더 쉽게 이해할 수 있었다.
오늘 배운 내용을 하나의 흐름으로 정리하면 다음과 같다.
Class
객체가 어떤 값을 가지고
어떤 기능을 사용할지 정의
↓
Field
객체가 가지고 있는 데이터
↓
Constructor
객체가 처음 만들어질 때
필요한 값을 초기화
↓
new
생성자를 호출해 객체 생성
↓
this
현재 객체 자신을 가리킴
↓
Getter / Setter
객체의 값을 조회하거나 변경
↓
Method
객체가 할 수 있는 기능 정의
여기에 반복되는 코드를 줄이고 싶다면 Lombok을 사용할 수 있다.
Lombok
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Builder
각 annotation이 어떤 Java 코드를 대신 만들어주는지 알고 사용하는 것이 중요할 것 같다.
객체를 만들기 위한 구조를 정의한다.
클래스를 바탕으로 실제로 만들어진 값이다.
객체가 가지고 있는 데이터다.
객체가 생성될 때 실행되며 초기값을 설정한다.
매개변수의 개수나 타입을 달리해 여러 생성자를 정의한다.
this현재 객체 자신을 가리킨다.
반복해서 작성하는 Java 코드를 annotation으로 줄일 수 있게 도와준다.
필드 이름을 보면서 객체의 값을 설정할 수 있는 객체 생성 방식이다.
오늘은 클래스와 객체 생성의 기본 구조를 처음 배웠다.
오늘은 Java 문법을 Car라는 객체가 어떻게 정의되고 만들어지고 값을 가지게 되는지 처음부터 따라가 본 날이었다. 특히 Annotation이 제일 충격적이었다. 너무 좋다. 어떻게.. @setter만으로 코드 몇줄을 커버하는건지..
아직 객체지향 개념 자체가 익숙하지는 않지만 클래스와 생성자를 기준으로 코드를 읽는 방법은 조금씩 잡히기 시작했다.
이번 TIL은 객체생성 중심으로 작성하다보니 DTO 관련된 내용이 누락되었다. 내일 아침에 수업 전에 DTO 관련 코드를 다시 열어보고 수업에 임해야겠다!
맥OS JDK 환경 변수 세팅 시 참고한 자료
https://gymdev.tistory.com/72
JDK 변경 화면!
SpringBoot 3.x을 사용하기 위해 JDK 23에서 JDK 17로 변경했다.

잘 보고갑니다~