일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 자바의 정석 기초편 ch4
- 자바의 정석 기초편 ch12
- 스프링 mvc2 - 타임리프
- 자바의 정석 기초편 ch6
- 자바의 정석 기초편 ch3
- 스프링 mvc1 - 서블릿
- 스프링 mvc2 - 로그인 처리
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch11
- 2024 정보처리기사 시나공 필기
- @Aspect
- 2024 정보처리기사 수제비 실기
- 자바의 정석 기초편 ch2
- jpa 활용2 - api 개발 고급
- 스프링 db2 - 데이터 접근 기술
- 스프링 db1 - 스프링과 문제 해결
- 자바의 정석 기초편 ch1
- 코드로 시작하는 자바 첫걸음
- 스프링 고급 - 스프링 aop
- 자바의 정석 기초편 ch13
- 자바의 정석 기초편 ch7
- 자바의 정석 기초편 ch5
- 자바의 정석 기초편 ch9
- 자바의 정석 기초편 ch8
- 타임리프 - 기본기능
- 자바의 정석 기초편 ch14
- 스프링 mvc1 - 스프링 mvc
- jpa - 객체지향 쿼리 언어
- 스프링 입문(무료)
- 게시글 목록 api
- Today
- Total
나구리의 개발공부기록
클래스와 데이터, 프로젝트 환경 구성, 클래스가 필요한 이유, 클래스 도입, 객체 사용, 클래스, 객체, 인스턴스 정리, 배열 도입, 문제와 풀이 본문
클래스와 데이터, 프로젝트 환경 구성, 클래스가 필요한 이유, 클래스 도입, 객체 사용, 클래스, 객체, 인스턴스 정리, 배열 도입, 문제와 풀이
소소한나구리 2024. 12. 20. 18:12출처 : 인프런 - 김영한의 실전 자바 - 기본편 (유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
1. 프로젝트 환경 구성
1) 프로젝트 생성
(1) 프로젝트 생성
- Name: java-basic
- build system: IntelliJ
- JDK: 자바 버전 17 이상
2. 클래스가 필요한 이유
1) 학생 정보 출력 프로그램 만들기
(1) 클래스가 필요한 이유
- 자바는 클래스와 객체로 이루어져 있는데, 해당 내용은 너무 많은 내용을 포함하고 있어 한번에 이해하기가 쉽지 않음
- 점진적으로 이해해보기위해 문제를 풀어보면서 이해해보기
(2) 예시 출력
-
더보기이름: 학생1 나이: 15 성적: 90
이름: 학생2 나이: 16 성적: 80
(3) ClassStart1 - 변수 사용
- 아래의 코드는 학생이 늘어날 때마다 변수를 추가로 선언해야 하고 출력하는 코드를 반복적으로 추가해야함
- 이런 문제는 배열을 사용하면 해결할 수 있음
package class1;
public class ClassStart1 {
public static void main(String[] args) {
String student1Name = "학생1";
int student1Age = 15;
int student1Grade = 90;
String student2Name = "학생2";
int student2Age = 16;
int student2Grade = 80;
System.out.println("이름: " + student1Name + " 나이: " + student1Age + " 성적: " + student1Grade);
System.out.println("이름: " + student2Name + " 나이: " + student2Age + " 성적: " + student2Grade);
}
}
(4) ClassStart2 - 배열을 활용
- 배열을 사용하면 반복문을 통해 출력문의 코드를 줄일 수 있음
package class1;
public class ClassStart2 {
public static void main(String[] args) {
String[] studentNames = {"학생1", "학생2"};
int[] studentAges = {15, 16};
int[] studentGrades = {90, 80};
for (int i = 0; i < studentNames.length; i++) {
System.out.println("이름: " + studentNames[i] + " 나이: " + studentAges[i] + " 성적: " + studentGrades[i]);
}
}
}
(5) 배열 사용의 한계
- 배열을 사용해서 코드 변경을 최소화하는데에는 성공했으나, 한 학생의 데이터가 3개의 배열에 나누어져 있어 데이터를 변경할 때 매우 조심해서 작업해야함
- 예를 들어 학생 2의 데이터를 제거하려면 각각의 배열마다 학생 2의 요소를 정확하게 찾아서 제거해주어야 하는데 이 배열의 인덱스 순서를 항상 정확하게 맞추어야 하는등 신경써야할 문제가 많고 수정 과정에서 실수할 가능성이 매우 높아짐
- 이 코드는 컴퓨터가 볼 때는 아무 문제가 없지만 사람이 관리하게는 좋은 코드가 아님
- 지금처럼 이름 나이 성적을 각각 따로 나누어서 관리하는 것은 사람이 관리하기 좋은 방식이 아니며 사람이 관리하기 좋은 방식은 학생이라는 개념을 하나로 묶는 것임
- 그리고 각각의 학생 별로 본인의 이름, 나이, 성적을 관리하는 것임
3. 클래스 도입
1) 클래스 도입으로 문제 해결
(1) Student 클래스
- class 키워드를 사용해서 학생 클래스를 정의
- 학생 클래스는 내부에 이름, 나이, 성적 변수를 가지며 이렇게 클래스에 정의한 변수들을 멤버 변수, 또는 필드라고함
- 멤버 변수(Member Variable): 특정 클래스에 소속된 멤버
- 필드(Field): 데이터 항목을 가리키는 전통적인 용어로 데이터베이스, 엑셀 등에서 데이터 각각의 항목을 필드라고함
- 자바에서는 멤버 변수, 필드는 같은 뜻이며 클래스에 소속된 변수를 뜻함
- 클래스는 관례상 대문자로 시작하고 낙타 표기법을 사용함
package class1;
public class Student {
String name;
int age;
int grade;
}
(2) ClassStart3
- Student1과 student2를 통해서 각 필드에 접근하여 값들을 출력
package class1;
public class ClassStart3 {
public static void main(String[] args) {
Student student1;
student1 = new Student();
student1.name = "학생1";
student1.age = 15;
student1.grade = 90;
Student student2 = new Student();
student2.name = "학생2";
student2.age = 16;
student2.grade = 80;
System.out.println("이름: " + student1.name + " 나이: " + student1.age + " 성적: " + student1.grade);
System.out.println("이름: " + student2.name + " 나이: " + student2.age + " 성적: " + student2.grade);
}
}
(3) 클래스와 사용자 정의 타입
- 타입은 데이터의 종류나 형태를 나타냄
- int는 정수타입, String은 문자타입처럼 학생(Student)라는 사용자 정의 타입을 만든 것이며 클래스를 사용하여 타입을 직접 만들 수 있음
- 사용자가 직접 정의하는 사용자 정의 타입을 만들려면 설계도가 필요한데 이 설계도가 클래스임
- 그리고 설계도인 클래스를 사용하여 실제 메모리에 만들어진 실체를 객체 또는 인스턴스라고 함
- 즉 클래스를 통해 사용자가 원하는 종류의 데이터 타입을 마음껏 정의할 수 있음
** 용어정리: 클래스, 객체 인스턴스
- 클래스는 설계도
- 해당 설계도(클래스)를 기반으로 실제 메모리에 만들어진 실체를 객체 또는 인스턴스라 하며 같은의미로 사용됨(미묘한 뉘앙스 차이가 존재하는데 뒤에서 설명함)
- 위 예제는 학생(Student) 클래스를 기반으로 학생1(Student1), 학생2(Student2) 객체 또는 인스턴스를 만든 것임
2) 코드 분석
(1) 변수 선언
- Student 타입을 받을 수 있는 변수를 선언
- int는 정수를 String을 문자를 담을 수 있듯이 Student는 Student 타입의 객체(인스턴스)를 받을 수 있음
Student student1
(2) 객체 생성
- 객체를 사용하려면 먼저 설계도인 클래스를 기반으로 객체(인스턴스)를 생성해야 함
- new Student(): new는 새로 생성한다는 뜻이며 new Student()는 Student 클래스 정보를 기반으로 새로운 객체를 생성하라는 뜻으로 이렇게 하면 메모리에 실제 Student 객체(인스턴스)를 생성함
- 객체를 생성할 때는 new 클래스명() 을 사용하면 되고 마지막에 ()로 꼭 추가해야함
- Student 클래스는 String name, int age, int grade 멤버 변수를 가지고 있으므로 이 변수를 사용하는데 필요한 메모리 공간도 함께 확보함
student1 = new Student();
(3) 참조값 보관
- 객체를 생성하면 자바는 메모리 어딘가에 있는 이 객체에 접근할 수 있는 참조값(주소)을 반환함
- 아래에서 x001이라고 표현한 것이 참조값인데 실제로 x001처럼 표현되는 것은 아니고 이해를 돕기위한 예시표현임
- new 키워드를 통해 객체가 생성되고 나면 참조값을 반환하며 앞서 선언한 변수인 Student student1에 생성된 객체의 참조값(x001)을 보관함
- Student student1 변수는 메모리에 존재하는 실제 Student 객체(인스턴스)의 참조값을 가지고 있으므로 이 변수를 통해서 객체를 접근(참조)할 수 있음
- 즉, student1 변수를 통해 메모리에 있는 실제 객체를 접근하고 사용할 수 있음
student1 = x001;(참조값)
(4) 참조값을 변수에 보관해야 하는 이유
- 객체를 생성하는 new Student() 코드 자체에는 아무런 이름이 없고 단지 Student 클래스를 기반으로 메모리에 실제 객체를 만드는 것이므로 생성한 객체에 접근할 수 있는 방법이 필요함
- 이런 이유로 객체를 생성할 때 반환되는 참조값을 어딘가에 보관해야하므로 변수에 참조값을 저장하고 이를 통해서 실제 메모리제 존재하는 객체에 접근할 수 있는 것임
- 이후에 학생2까지 생성하게 되면 Student 객체가 메모리에 2개 생성되고 각각 참조값이 다르므로 서로 구분할 수 있음
- 참조값을 확인하고 싶으면 아래처럼 객체를 담고 있는 변수를 출력해보면 됨
Student student1 = new Student(); //1. Student 객체 생성
Student student1 = x001; //2. new Student()의 결과로 x001 참조값 반환
student1 = x001; //3. 최종 결과
// 참조값 출력
System.out.println("student1 = " + student1);
System.out.println("student2 = " + student2);
/* 출력 결과
student1 = class1.Student@30f39991
student2 = class1.Student@452b3a41
*/
4. 객체 사용
1) 객체 사용
(1) 객체에 접근
- 클래스를 통해 생성한 객체를 사용하려면 메모리에 존재하는 객체에 접근해야하는데 .(점, dot)을 사용하여 접근할 수 있음
// 객체 값 대입
student1.name = "학생1";
student1.age = 15;
student1.grade = 90;
// 객체 값 사용
System.out.println("이름: " + student1.name + " 나이: " + student1.age + " 성적: " + student1.grade);
(2) 객체 값 대입
- 객체가 가지고 있는 멤버 변수에 값을 대입하기 위해 .(점, dot) 키워드를 사용하여 객체에 접근하면 되는데, 이 키워드는 변수에 들어있는 참조값을 읽어서 메모리에 존재하는 객체에 접근함
- student1. 이라고 하면 student1 변수가 가지고 있는 참조값을 통해 실제 객체에 접근하고 student1은 x001이라는 참조값을 가지고 있으므로 x001 위치에 있는 Student 객체에 접근함
- 즉, student1.name = "학생1" 일 때 .(점, dot)으로 student1 변수가 가지고 있는 참조값(x001)을 통해서 실제 객체에 접근하고 그 객체의 필드인 name에 "학생1" 데이터가 저장됨
(3) 객체 값 읽기
- 객체의 값을 읽는 것도 앞서 설명한 내용과 동일하며 .(점, dot) 키워드를 통해 참조값을 사용해서 객체에 접근한 다음 원하는 작업을 하면 됨
- println(student1.name)일 때 .(점, dot)으로 student1 변수가 가지고 있는 참조값을 통해서 실제 객체에 접근하고 그 객체의 필드인 name이 가지고 있는 값을 읽어서 출력이 됨
5. 클래스, 객체, 인스턴스 정리
1) 정리
(1) 클래스 - Class
- 객체를 생성하기 위한 틀 또는 설계도이며 객체가 가져야할 속성(변수)과 기능(메서드)를 정의함
- 학생이라는 클래스는 속성으로 name, age, grade를 가지도록 작성할 수 있었음
- 기능(메서드)에 대한 내용은 뒤에서 설명할 예정
- 예를 들어 붕어빵 틀을 생각해보면 붕어빵을 만들기 위한 틀일 뿐이며 이 틀을 클래스에 비유하기도하며, 자동차 설계도를 생각해보면 실제 존재하는 것이 아니라 개념만으로 있는 것으로 이런 설계도를 클래스에 비유하기도 함
- 그리고 틀로 찍어내어 실제 먹을 수 있는 붕어빵, 설계도를 통해 생산한 실제 존재하는 자동차를 객체 또는 인스턴스라 함
(2) 객체 - Object
- 클래스에서 정의한 속성과 기능을 가진 실체이며 객체는 서로 독립적인 상태를 가짐
- 예제 코드에서 Student라는 같은 클래스에서 만들어졌지만 student1은 학생1의 속성을 가지는 객체이고 student2는 학생2의 속성을 가지는 객체로 서로 다른 객체임
(3) 인스턴스 - Instance
- 특정 클래스로부터 생성된 객체를 의미로 객체와 인스턴스라는 용어는 자주 혼용되어 사용됨
- 인스턴스는 주로 객체가 어떤 클래스에 속해 있는지 강조할 때 사용하는데 예를 들어 student1 객체는 Student 클래스의 인스턴스라도 표현할때 주로 사용함
(4) 객체 vs 인스턴스
- 둘다 클래스에서 나온 실체라는 의미에서 비슷하게 사용되지만 용어상 인스턴스는 객체보다 좀 더 관계에 초점을 맞춘 단어라고 보면됨
- 보통 student1은 Student의 객체라고 말하는 대신 student1은 Student1 인스턴스라고 말하는 것 처럼 특정 클래스와의 관계를 명확히 할 때 인스턴스라는 용어를 주로 사용함
- 모든 인스턴스는 객체이지만 인스턴스라고 부르는 순간은 특정 클래스로부터 그 객체가 생성되었음을 강조하고 싶을 때임
- 하지만 둘다 클래스에서 나온 실체라는 핵심 의미는 같기 때문에 보통 둘을 구분하지 않고 사용함(대부분 구분하지 않고 사용한다고 함)
6. 배열 도입
1) 시작
(1) 클래스에 배열도입
- 클래스와 객체 덕분에 학생 데이터를 마치 실제 학생이 있고 그 안에 학생의 정보가 있는 것 처럼 구조적으로 이해하기 쉽게 변경 되었으므로 각각의 학생 별로 객체를 생성하고 해당 객체에 학생의 데이터를 관리하면 됨
- 그러나 코드를 보면 아쉬운 부분이 있는데 학생을 출력하는 부분을 계속 추가해 주어야한다는 점임
- 배열을 사용하면 특정 타입을 연속한 데이터 구조로 묶어서 편리하게 관리할 수 있으므로 Student 클래스를 사용한 변수들도 Student 타입이기 때문에 학생도 배열을 사용해서 하나의 데이터 구조로 묶어서 관리할 수 있음
(2) ClassStart4
- Student 클래스를 기반으로 student1, student2 인스턴스를 생성하고 필요한 값을 채움
- Student를 담을 수 있는 배열을 생성하고 해당 배열에 student1, student2 인스턴스를 보관
- Student[] students = new Student[2]
- Student 타입의 변수를 2개 보관할 수 있는 사이즈 2의 배열을 생성하며 이 배열은Student 타입의 참조값을 보관할 수 있음
- 최초 생성시에는 참조값을 대입하지 않았기 때문에 null 값으로 초기화가 됨 - student[0] = student1
- 자바에서 대입은 항상 변수에 들어있는 값을 복사(중요함)하므로 student1에 들어있는 참조값을 복사하여 배열에 저장함
- student1에 보관된 참조값을 읽고 복사해서 배열에 대입한다고 표현함 - student[1] = student2도 실행하면 배열은 x001, x002의 참조값을 가지고 있기 때문에 x001(학생1), x002(학생2) Student 인스턴스에 모두 접근할 수 있음
- 배열에 들어있는 객체를 사용할 때는 먼저 배열에 접근하고 그 다음에 객체에 접근하면 됨(출력 문의 코드를 보면 이해가 쉬움)
package class1;
public class ClassStart4 {
public static void main(String[] args) {
Student student1 = new Student();
student1.name = "학생1";
student1.age = 15;
student1.grade = 90;
Student student2 = new Student();
student2.name = "학생1";
student2.age = 16;
student2.grade = 80;
Student[] students = new Student[2];
students[0] = student1;
students[1] = student2;
System.out.println("이름: " + students[0].name + " 나이: " + students[0].age + " 성적: " + students[0].grade);
System.out.println("이름: " + students[1].name + " 나이: " + students[1].age + " 성적: " + students[1].grade);
}
}
** 자바에서 대입은 항상 변수에 들어 있는 값을 복사해서 전달함
- 자바에서 변수의 대입은 모두 변수에 들어있는 값을 복사해서 전달함
- 오른쪽 변수인 student1, student2에는 참조값이 들어있는데 이 값을 복사해서 왼쪽에 있는 배열에 전달했으므로 기존 student1, student2에 들어있던 참조값은 당연히 그대로 유지됨
** 주의
- 변수에는 인스턴스 자체가 들어있는 것이 아니라 인스턴스의 위치를 가리키는 참조값이 들어있을 뿐임
- 따라서 대입시에 인스턴스가 복사되는 것이 아니라 참조값이 복사되는 것임
2) 리펙토링
(1) ClassStart5
- 배열을 사용한 덕분에 출력에서 for문을 도입할 수 있게 되었음
- new Student[] {student1, student2}를 통해서 배열을 생성할 때 값을 입력할 수 있으며 Student[] students = {student1, student2}처럼 더욱 최적화 할 수 있음
- 일반 for문으로 출력할 수 있지만 향상된 for문(Enhanced For Loop)으로 배열의 요소를 하나씩 꺼낼 수 있음(인텔리제이에서는 iter라고 치면 됨)
package class1;
public class ClassStart5 {
public static void main(String[] args) {
// 객체 생성 코드 동일
Student[] students = new Student[]{student1, student2};
// 일반 for문
for (int i = 0; i < students.length; i++) {
System.out.println("이름: " + students[i].name + " 나이: " + students[i].age + " 성적: " + students[i].grade);
}
// 향상된 for문 사용 - IntelliJ 에서 iter 를 입력하면 됨
for (Student student : students) {
System.out.println("이름: " + student.name + " 나이: " + student.age + " 성적: " + student.grade);
}
}
}
7. 문제와 풀이
1) 영화 리뷰 관리하기1
(1) 문제 설명 및 요구사항
- 영화 리뷰 정보를 관리를 위해 영화 리뷰 정보를 담을 수 있는 MovieReview 클래스를 생성
- 1. MovieReview 클래스에는 영화 제목 - title, 리뷰 내용 - review 멤버 변수를 포함해야 함
- 2. MovieReviewMain 클래스 안에 main() 메서드를 포함하여 영화 리뷰 정보를 선언하고 출력
- 배열 없이 출력
(2) 예시 코드 구조 및 출력 예시
public class MovieReview {
String title;
String review;
}
public class MovieReviewMain {
public static void main(String[] args) {
// 영화 리뷰 정보 선언
// 영화 리뷰 정보 출력
}
}
영화 제목: 인셉션, 리뷰: 인생은 무한 루프
영화 제목: 어바웃 타임, 리뷰: 인생 시간 영화!
(3) 답안
package class1.ex;
public class MovieReview {
String title;
String review;
}
package class1.ex;
public class MovieReviewMain {
public static void main(String[] args) {
MovieReview movieReview1 = new MovieReview();
movieReview1.title = "인셉션";
movieReview1.review = "인생은 무한 루프";
MovieReview movieReview2 = new MovieReview();
movieReview2.title = "어바웃 타임";
movieReview2.review = "인생 시간 영화!";
System.out.println("영화 제목: " + movieReview1.title + ", 리뷰: " + movieReview1.review);
System.out.println("영화 제목: " + movieReview2.title + ", 리뷰: " + movieReview2.review);
}
}
영화 제목: 인셉션, 리뷰: 인생은 무한 루프
영화 제목: 어바웃 타임, 리뷰: 인생 시간 영화!
2) 영화 리뷰 관리하기2
(1) 요구사항
- 기존 문제에 배열을 도입하여 출력
- 리뷰를 출력할 때 배열과 for문을 사용하여 출력문을 한번(한줄)만 사용하여 출력
(2) 답안
package class1.ex;
public class MovieReviewMain1 {
public static void main(String[] args) {
MovieReview movieReview1 = new MovieReview();
movieReview1.title = "인셉션";
movieReview1.review = "인생은 무한 루프";
MovieReview movieReview2 = new MovieReview();
movieReview2.title = "어바웃 타임";
movieReview2.review = "인생 시간 영화!";
MovieReview[] movieReviews = {movieReview1, movieReview2};
for (MovieReview movieReview : movieReviews) {
System.out.println("영화 제목: " + movieReview.title + ", 리뷰: " + movieReview.review);
}
}
}
출력 결과는 동일
3) 상품 주문 시스템 개발
(1) 문제 설명 및 요구사항
- 온라인 상점의 주문 관리 시스템을 생성
- 상품 주문정보를 담을 수 있는 ProductOrder 클래스를 생성
- 1. ProductOrder 클래스에는 상품명 - productName, 가격 - price, 주문 수량 - quantity 라는 멤버 변수를 포함해야 함
- 2. ProductOrderMain클래스 안에 main() 메서드를 포함하여 여러 상품의 주문 정보를 배열로 관리하여 해당 정보들을 출력하고 최종 결제 금액을 계산하여 출력
(2) 예시 코드 구조 및 출력 예시
public class ProductOrder {
String productName;
int price;
int quantity;
}
public class ProductOrderMain {
public static void main(String[] args) {
// 여러 상품의 주문 정보를 담는 배열 생성
// 상품 주문 정보를 `ProductOrder` 타입의 변수로 받아 저장
// 상품 주문 정보와 최종 금액 출력
}
}
상품명: 두부, 가격: 2000, 수량: 2
상품명: 김치, 가격: 5000, 수량: 1
상품명: 콜라, 가격: 1500, 수량: 2
총 결제 금액: 12000
(3) 정답
package class1.ex;
public class ProductOrderMain {
public static void main(String[] args) {
ProductOrder[] productOrders = new ProductOrder[]{new ProductOrder(), new ProductOrder(), new ProductOrder()};
productOrders[0].productName = "두부";
productOrders[0].price = 2000;
productOrders[0].quantity = 2;
productOrders[1].productName = "김치";
productOrders[1].price = 5000;
productOrders[1].quantity = 1;
productOrders[2].productName = "콜라";
productOrders[2].price = 1500;
productOrders[2].quantity = 2;
int totalPrice = 0;
for (ProductOrder productOrder : productOrders) {
System.out.println("상품명: " + productOrder.productName + ", 가격: " + productOrder.price + ", 수량: " + productOrder.quantity);
totalPrice += productOrder.price * productOrder.quantity;
}
System.out.println("총 결제 금액: " + totalPrice);
}
}
상품명: 두부, 가격: 2000, 수량: 2
상품명: 김치, 가격: 5000, 수량: 1
상품명: 콜라, 가격: 1500, 수량: 2
총 결제 금액: 12000
'인프런 - 실전 자바 로드맵 > 실전 자바 - 기본편' 카테고리의 다른 글
접근제어자, 접근 제어자 이해, 접근 제어자 종류, 접근 제어자 사용(필드,메서드,클래스레벨) 캡슐화, 문제와 풀이 (0) | 2025.01.04 |
---|---|
패키지, 패키지(시작 / import) 패키지 규칙, 패키지 활용 (0) | 2024.12.28 |
생성자, 생성자 - 필요한 이유, this, 생성자 - 도입, 기본 생성자, 생성자 - 오버로딩과 this(), 문제와 풀이 (1) | 2024.12.28 |
객체 지향 프로그래밍, 절차 지향 프로그래밍, 클래스와 메서드, 문제와 풀이 (0) | 2024.12.25 |
기본형과 참조형, 기본형 vs 참조형, 참조형과 메서드 호출, 변수와 초기화, null, NullPointerException, 문제와 풀이 (1) | 2024.12.21 |