주제 : 김영한님의 자바 실전 강의 총 정리
내용 : 객체 지향 프로그래밍 ~ 자바 실전
절차 지향 프로그래밍 vs 객체 지향 프로그래밍
프로그래밍 방식은 크게 절차 지향 프로그래밍과 객체 지향 프로그래밍으로 나눌 수 있다.
절차 지향 프로그래밍
객체 지향 프로그래밍
둘의 중요한 차이
절차지향 프로그램에서 음악 플레이어는 데이터와 기능이 분리되어 있다. 하지만, 객체지향 프로그래밍에서는 기능을 하나로 묶어서 음악 플레이어라는 개념을 온전히 하나의 클래스에 담을 수 있다. 이를 위해서는 음악 플레이어가 어떤 속성(데이터)를 가지고 어떤 기능(메서드)를 제공하는지 초점을 맞춰야 한다.
참고(중요!) : 여기서 만드는 add() 메서드에는
static키워드를 사용하지 않는다. 메서드는 객체를 생성해야 호출할 수 있다. 그런데, static이 붙으면 객체를 생성하지 않고도 메서드를 호출할 수 있다.
음악 플레이어
volume , isOnon(),off(),volumeUp(),volumeDown(),showStatus()public class MusicPlayer { int volume = 0; boolean isOn = false; void on() { isOn = true; System.out.println("음악 플레이어를 시작합니다"); } void off() { isOn = false; System.out.println("음악 플레이어를 종료합니다"); } void volumeUp() { volume++; System.out.println("음악 플레이어 볼륨:" + volume); } void volumeDown() { volume--; System.out.println("음악 플레이어 볼륨:" + volume); } void showStatus() { System.out.println("음악 플레이어 상태 확인"); if (isOn) { System.out.println("음악 플레이어 ON, 볼륨:" + volume); } else { System.out.println("음악 플레이어 OFF"); } } }
MusicPlayer클래스에 음악 플레이어에 필요한 속성과 기능을 모두 정의했다. 이제 음악 플레이어가 필요한 곳에서 이 클래스만 있으면 음악 플레이어를 생성해서 사용할 수 있다. 음악 플레이어를 사용하는데 필요한 모든 속성과 기능이 하나의 클래스에 포함되어 있다!
정리 : Main 클래스에서 MusicPlayer의 기능을 사용하는 것!
단, 객체 생성이 먼저 되어야 하고 출력해야하는 프로그램들은 Main클래스에서 정의해야 한다. (ex. 문제1의 area, perimeter, square)
public class MusicPlayerMain4 { public static void main(String[] args) { MusicPlayer player = new MusicPlayer(); player.volume = 0; player.isOn = false; //음악 플레이어 켜기 player.on(); //볼륨 증가 player.volumeUp(); //볼륨 증가 player.volumeUp(); //볼륨 감소 player.volumeDown(); //음악 플레이어 상태 player.showStatus(); //음악 플레이어 끄기 player.off(); } }
캡슐화(중요!!)
MusicPlayer 를 보면 음악 플레이어를 구성하기 위한 속성과 기능이 마치 하나의 캡슐에 쌓여있는 것 같다. 이렇게 속성과 기능을 하나로 묶어서 필요한 기능을 메서드를 통해 외부에 제공하는 것을 캡슐화라 한다.
[문제1] - 절차 지향 직사각형 프로그램을 객체 지향으로 변경하기
다음은 직사각형의 넓이(Area), 둘레 길이(Perimeter), 정사각형 여부(square)를 구하는 프로그램이다.
절차 지향 프로그래밍 방식으로 되어 있는 코드를 객체 지향 프로그래밍 방식으로 변경해라.
Rectangle클래스를 만들어라.RectangleOopMain에 해당 클래스를 사용하는main()코드를 만들어라.
public class RectangleProceduralMain { public static void main(String[] args) { int width = 5; int height = 8; int area = calculateArea(width, height); System.out.println("넓이: " + area); int perimeter = calculatePerimeter(width, height); System.out.println("둘레 길이: " + perimeter); boolean square = isSquare(width, height); System.out.println("정사각형 여부: " + square); } static int calculateArea(int width, int height) { return width * height; } static int calculatePerimeter(int width, int height) { return 2 * (width + height); } static boolean isSquare(int width, int height) { return width == height; } }[정답]
# Rectangle 클래스 public class Rectangle { int width = 5; int height = 8; int calculateArea(){ return width * height; } int calculatePerimeter(){ return 2*(width + height); } boolean isSquare(){ return width == height; } } # RectangleOopMain 클래스 public class RectangleOopMain { public static void main(String[] args) { Rectangle rectangle = new Rectangle(); rectangle.width = 5; rectangle.height = 8; int area = rectangle.calculateArea(); System.out.println(area); int perimeter = rectangle.calculatePerimeter(); System.out.println(perimeter); boolean square = rectangle.isSquare(); System.out.println(square); } }
객체를 생성하는 시점에 어떤 작업을 하고 싶다면 생성자를 이용하면 된다.
생성자를 알아보기 전에 먼저 생성자가 왜 필요한지 코드로 알아보자
public class MemberInit { String name; int age; int grade; // 추가 void initMember(String name, int age, int grade){ this.name = name; this.age = age; this.grade = grade; } }public class MethodInitMain3 { public static void main(String[] args) { MemberInit member1 = new MemberInit(); member1.initMember("user1", 15, 90); MemberInit member2 = new MemberInit(); member2.initMember("user2", 16, 80); MemberInit[] members = {member1, member2}; for (MemberInit s : members) { System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" + s.grade); } } }
this
Member의 코드를 다시 보자
initMember(String name...)의 코드를 보면메서드(initMember)의 매개변수에 정의한 String name과Member의 멤버 변수의 이름이 String name으로 둘다 똑같다. 나머지 변수 이름도 name, age, grade로 모두 같다.
멤버 변수와 메서드의 매개변수의 이름이 같으면 둘을 어떻게 구분할 수 있을까?
- 이 경우 매개변수(initMember)가 우선순위를 가진다.
- 멤버 변수에 접근하려면
this.이라고 해주면 된다. 여기서 this는 인스턴스 자신의 참조값을 가리킨다.
진행 과정
this.name = name; //1. 오른쪽의 name은 매개변수(initMember)에 접근
this.name = "user"; //2. name 매개변수의 값 사용
x001.name = "user"; //3. this.은 인스턴스의 멤버 변수에 접근
this 제거
만약 이 예제에서this를 제거하면 어떻게 될까?this.name = name아래와 같이 수정하면
name은 둘다 매개변수를 뜻하게 된다.
따라서, 맴버변수의 값이 변경되지 않는다.name = name정리
- 매개변수의 이름과 멤버 변수의 이름이 같은 경우
this를 사용해서 둘을 명확하게 구분해야 한다.this는 인스턴스 자신을 가리킨다.
생성자와 메서드의 차이
- 생성자는 메서드와 비슷하지만 다음과 같은 차이가 있다.
- 생성자의 이름은 클래스 이름과 같아야 한다. 따라서 첫 글자도 대문자로 시작한다.
- 생성자는 반환 타입이 없다. 비워두어야 한다.
- 나머지는 메서드와 같다.
public class MemberConstruct { String name; int age; int grade; MemberConstruct(String name, int age, int grade){ System.out.println("생성자 호출 name=" + name + ",age=" + age + ",grade=" + grade); this.name = name; this.age = age; this.grade = grade; } }
생성자 호출
생성자는 인스터를 생성하고 나서 즉시 호출된다. 생성자를 호출하는 방법은 다음 코드와 같이 new 명령어 다음에 생성자 이름과 매개변수에 맞추어 인수를 전달한다.new 생성자이름(생성자에 맞는 인수 목록)
new 클래스이름(생성자에 맞는 인수 목록)
new MemberConstruct("user1", 15, 90)public class ConstructMain1 { public static void main(String[] args) { MemberConstruct member1 = new MemberConstruct("user1", 15, 90); MemberConstruct member2 = new MemberConstruct("user2", 16, 80); MemberConstruct[] members = {member1, member2}; for (MemberConstruct s : members) { System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" + s.grade); } } }
생성자 장점
중복 호출 제거 : 생성자 덕분에 객체를 생성하면서 동시에 생성 직후에 필요한 작업을 한번에 처리할 수 있게 되었다.//생성자 등장 전 MemberInit member = new MemberInit(); member.initMember("user1", 15, 90); //생성자 등장 후 MemberConstruct member = new MemberConstruct("user1", 15, 90);
제약 - 생성자 호출 필수
방금 코드에서 생성자 등장 전 코드를 보자. 이 경우initMember(...)를 실수로 호출하지 않으면 어떻게 될까? 이 메서드를 실수로 호출하지 않아도 프로그램은 작동한다. 하지만 회원의 이름과 나이, 성적 데이터가 없는 상태로 프로그램이 동작하게 된다. 만약에 이 값들을 필수로 반드시 입력해야 한다면, 시스템에 큰 문제가 발생할 수 있다.
결국 아무 정보가 없는 유령 회원이 시스템 내부에 등장하게 된다.생성자의 진짜 장점은 객체를 생성할 때 직접 정의한 생성자가 있다면 직접 정의한 생성자를 반드시 호출해야 한다는 점이다. 참고로 생성자를 메서드 오버로딩 처럼 여러개 정의할 수 있는데, 이 경우에는 하나만 호출하면 된다.
MemberConstruct클래스의 경우 다음 생성자를 직접 정의했기 때문에 직접 정의한 생성자를 반드시 호출해야 한다.MemberConstuct(String name, int age, int grade){..}
- 다음과 같이 직접 정의한 생성자를 호출하지 않으면 컴파일 오류가 발생한다.
MemberConstruct member3 = new MemberConstruct(); // 컴파일 오류 발생 // MemberConstruct 안에 생성자 호출을 하지 않았다. MemberConstruct member3 = new MemberConstruct("user1", 15, 90); // 이렇게 호출해주어야 한다.
정리
- 생성자는 반드시 호출되어야 한다.
- 생성자가 없으면 기본 생성자가 제공된다.
- 생성자가 하나라도 있으면 기본 생성자가 제공되지 않는다.
생각해보면 생성자를 만들지 않았는데, 생성자를 호출한 적이 있다.
public class MemberInit { String name; int age; int grade; MemberInit() { //생성자 필요 } }
- 기본 생성자
- 매개변수가 없는 생성자를 기본 생성자라 한다.
- 클래스에 생성자가 하나도 없으면 자바 컴파일러는 매개변수가 없고, 작동하는 코드가 없는 기본 생성자를 자동으로 만들어준다.
- 생성자가 하나라도 있으면 자바는 기본 생성자를 만들지 않는다.
- 만약 자바에서 기본 생성자를 만들어주지 않는다면 생성자 기능이 필요하지 않은 경우에도 모든 클래스에 개발자가 직접 기본 생성자를 정의해야 한다. 생성자 기능을 사용하지 않는 경우도 많기 때문에 이런 편의 기능을 제공한다.
@NoArgsConstructor애노테이션은 클래스에 기본 생성자를 자동으로 추가해줍니다. 따라서 개발자가 명시적으로 생성자를 작성할 필요가 없습니다
- 생성자도 메서드 오버로딩처럼 매개변수만 다르게 해서 여러 생성자를 제공할 수 있다.
public class MemberConstruct { String name; int age; int grade; //추가 MemberConstruct(String name, int age) { this.name = name; this.age = age; this.grade = 50; } MemberConstruct(String name, int age, int grade) { System.out.println("생성자 호출 name=" + name + ",age=" + age + ",grade=" + grade); this.name = name; this.age = age; this.grade = grade; } }
[문제1] - Book과 생성자
BookMain코드가 작동하도록Book클래스를 완성하세요.
특히,Book클래스의 생성자 코드에 중복이 없도록 주의하세요.public class Book { String title; String author; int page; // 기본생성자 Book() { this("", "", 0); } // title 과 author 만을 매개변수로 받는 생성자 // 지금 메서드 오버로딩해서 매개변수를 여러개 생성하였음 Book(String title, String author) { // 여기 this 는 반드시 바로 와야 한다. this(title, author, 0); } // 모든 필드를 매개변수로 받는 생성자 Book(String title, String author, int page) { this.title = title; this.author = author; this.page = page; } void displayInfo() { System.out.println("제목: " + title + ", 저자: " + author + ", 페이지: " + page); } }public class BookMain { public static void main(String[] args) { Book book1 = new Book(); book1.displayInfo(); // title과 author만을 매개변수로 받는 생성자 Book book2 = new Book("Hello Java", "Seo"); book2.displayInfo(); // 모든 필드를 매개변수로 받는 생성자 Book book3 = new Book("JPA 프로그래밍", "kim", 700); book3.displayInfo(); } }