객체지향 프로그래밍
자바의 핵심 개념인 객체지향 프로그래밍에 대하여 정리
객체지향 프로그래밍(OOP - Object Oriented Programming)
•
소프트웨어 개발할 때 부품에 해당하는 객체를 먼저 만들고, 이 객체들을 하나씩 조립해서 완성된 프로그램을 만든다.
•
객체(Object)란 물리적으로 존재하거나 개념적인 것 중에서 다른 것과 식별 가능한 것을 말한다.
•
물리적으로 존재하는 자동차, 사람 / 개념적으로 존재하는 강의, 주문
•
객체는 속성과 동작으로 구성되며, Java에서는 이를 필드(Field)와 메서드(Method)라고 부른다.
•
현실 세계의 객체를 소프트웨어 객체로 설계하는 것을 객체 모델링(Object Modeling)이라 한다.
•
모든 현실 세계는 객체들의 상호작용을 통해 이루어지듯이 객체지향 프로그램에서도 객체들은 다른 객체들은 다른 객체와 서로 상호작용하며 동작한다.
•
객체들의 상호작용 수단은 메소드(Method)이다.
•
객체는 단독으로 존재할 수 있지만 대부분 다른 객체와 관계를 맺고 있다.
•
관계 종류:
◦
집합 관계 - 완성과 부품의 관계
◦
사용 관계 - 다른 객체의 필드를 읽고 변경하거나 메소드를 호출하는 관계
(사람 객체, 자동차 객체)
◦
상속 관계 - 부모에게서 필드와 메소드를 물려받는 관계
(기계 객체, 자동차 객체)
객체지향 프로그래밍 특징
•
캡슐화
◦
객체의 필드, 메소드를 하나로 묶고 실제 구현 내용을 외부에 감추는 것.
◦
외부에서는 객체 내부 구조를 알지 못하고, 객체가 노출해서 제공하는 필드와 메소드만을 이용할 수 있다.
◦
캡슐화는 외부의 잘못된 사용으로 객체가 손상되는 것을 방지한다.
◦
Java에서는 캡슐화를 위해 접근 제한자(Access modifier)를 사용한다.
•
상속
◦
부모 객체는 자식 객체에게 필드와 메소드를 물려주어 자식 객체가 이를 이용할 수 있도록 할 수 있다.
◦
상속을 하면, 중복 코딩을 하지 않아도 되기에 코드의 재사용성을 높일 수 있다.
◦
부모 객체에서 필드와 메소드를 수정하면, 자식 객체들은 수정된 필드와 메소드를 사용할 수 있기에 유지보수 시간을 단축할 수 있다.
•
다형성
◦
동일한 사용 방법이나 다양한 결과가 나타나는 성질을 의미한다.
◦
기계의 부품을 교환하면 성능이 달라지듯, 프로그램을 구성하는 객체를 바꾸면 프로그램의 실행 성능이 달라질 수 있다.
◦
다형성을 구현하기 위해서는 자동 타입 변환과 재정의(Overriding) 기술이 필요하다.
클래스
•
현실에서 자동차 생성하려면 자동차의 설계도가 필요하듯, 객체지향 프로그래밍에도 설계도가 필요하다.
•
Java에서는 클래스가 객체를 생성하기 위한 설계도 역할을 한다.
•
클래스로부터 객체가 생성되는 과정을 인스턴스화(Instance) 라고 하며, 생성된 객체는 인스턴스(Instance) 라고 부른다.
(클래스 - 설계도, 생성된 객체 - 인스턴스)
•
클래스 선언은 객체를 생성을 위한 설계도를 작성하는 작업이기에 어떻게 객체를 생성하고 (생성자)
객체가 가져야 할 데이터가 무엇이고(필드), 객체의 동작은 무엇인지(메소드)를 포함한다.
(클래스 만들 때 필요한 것 - 생성자, 필드, 메소드)
// public class는 공개클래스를 선언한다는 뜻으로,
// 하나의 소스파일에는 소스파일명과 동일한 하나의 클래스만 공개 클래스로 선언할 수 있다!!!
public class 클래스명 {
}
Java
복사
•
하나의 소스 파일에는 여러 개의 클래스를 선언할 수 있다.
•
여러 개의 클래스 선언이 포함된 소스 파일을 컴파일 하면, 바이트코드 파일(.class)은 클래스 선언 수만큼 생겨난다.
•
특별한 이유가 없다면, 파일 하나 당 클래스 하나를 선언하는 것이 좋다.
public class SportsCar {
}
class Tire {
}
Java
복사
•
new 연산자는 객체를 생성시킨 후 객체의 주소를 반환한다.
// 클래스명 변수명 = new 클래스명();
Student s1 = new Student();
Java
복사
•
클래스의 용도
◦
직접 실행되지 않고, 다른 클래스에서 이용되는 라이브러리 클래스
◦
main() 메소드를 가지고, 직접 실행되는 실행 클래스
•
클래스 구성 멤버
◦
생성자 : 객체 생성 시 초기화 역할 담당
▪
new 연산자로 객체를 생성할 때, 객체의 초기화 역할을 담당한다.
▪
메소드와 유사한 선언 형태를 가지나, 반환 타입이 없다.
▪
생성자 이름은 반드시 클래스 이름과 동일해야 한다.
◦
필드 : 객체의 데이터가 저장되는 곳
▪
객체의 데이터를 저장하는 역할로, 선언 형태는 변수 선언과 동일하지만 쓰임새는 다르다.
◦
메소드 : 객체의 동작으로 호출 시에 실행되는 블록
▪
객체가 수행할 동작으로 객체 간 상호작용을 위해 호출된다.
▪
객체 내부의 함수를 메소드라 한다.
public class ClassName { // 필드 선언
int filedName; // 생성자 선언
ClassName() {
}
// 메소드 선언
int methodName() {
}
}
Java
복사
•
필드:
◦
객체의 속성 데이터를 저장하는 용도로 사용되며 선언 방법은 변수와 동일하다.
◦
예제 코드
public class CarExample {
public static void main(String[] args) {
Car myCar = new Car();
System.out.println("내 자동차 모델명은? : " + myCar.model);
System.out.println("내 자동차 현재 속도는? : " + myCar.speed);
System.out.println("내 자동차는 지금 시동이 걸려 있나? : " + myCar.start);
System.out.println("내 자동차 휠의 브랜드는? : " + myCar.tire.brand);
System.out.println(myCar);
System.out.println("=========================================");
Car fatherCar = new Car();
System.out.println("아빠 자동차 모델명은? : " + fatherCar.model);
System.out.println("아빠 자동차 현재 속도는? : " + fatherCar.speed);
System.out.println("아빠 자동차는 지금 시동이 걸려 있나? : " + fatherCar.start);
System.out.println("아빠 자동차 휠의 브랜드는? : " + fatherCar.tire.brand);
System.out.println(fatherCar);
}
}
Java
복사
•
생성자
◦
모든 클래스에는 하나 이상의 생성자가 존재한다.
◦
객체 초기화: 필드를 초기화하거나 메소드를 호춣해서 객체를 사용할 준비를 하는 것을 의미한다.
◦
객체마다 동일한 값을 가져야 한다면, 필드 선언 시 초기값을 대입하는 게 좋고, 객체마다 다른 값을 가져야 한다면, 생성자에서 필드를 초기화하는 것이 좋다.
public class Korean {
String nation = "대한민국";
String name, ssn;
public Korean(String n, String s) {
name = n;
ssn = s;
}
}
public class KoreanExample {
public static void main(String[] args) {
Korean k1 = new Korean("최인규", "990101-1001234"); System.out.println(k1.nation + ", " + k1.name + ", " + k1.ssn); Korean k2 = new Korean("김자바", "001231-4004321"); System.out.println(k2.nation + ", " + k2.name + ", " + k2.ssn);
}
}
Java
복사
◦
this 키워드 - this는 현재 객체를 의미한다.
public Korean(String name, String ssn) {
this.name = name;
this.ssn = ssn;
}
Java
복사
◦
하지만 클래스에 생성자 선언이 하나도 없다 하더라도 객체 생성은 가능하다.
▪
왜냐하면 클래스에 생성자 선언이 없으면, 컴파일러는 기본 생성자를 자동으로 추가시키기 때문이다.
클래스명 변수명 = new 클래스명();
Java
복사
◦
개발자가 명시적으로 선언한 생성자가 있는 경우 컴파일러는 기본 생성자를 추가하지 않는다.
◦
명시적으로 선언하는 목적은 객체가 원하는 형태로 초기화하기 위해서이다.
◦
여러 개의 생성자를 선언하여, 다양한 형태로 초기화 할 수 있다.
◦
생성자 오버로딩이 많아질 경우, 생성자 간의 중복 코드가 발생할 수 있다.
▪
이럴 때에는 공통 코드를 한 생성자에만 집중적으로 작성하고, 나머지 생성자는 this를 사용하여 공통 코드를 가지고 있는 생성자를 호출하는 방법으로 개선할 수 있다.
◦
다양하게 초기화를 위해서는 생성자 오버로딩(Overloading)이 필요하다.
(오버로딩은 매개변수가 다른 생성자를 여러 개 선언하는 것을 의미한다.)
◦
생성자 오버로딩 시 매개변수 타입, 개수, 순서가 다르게 선언되어야 한다.
public class Car {
String model = "그랜저"; int speed;
boolean start;
Tire tire = new Tire();
public Car(String m, int sp, boolean st) {
model = m;
speed = sp;
start = st;
}
}
public class CarExample {
public static void main(String[] args) {
Car newCar = new Car("K5", 200, true);
System.out.println("모델명 :" + newCar.model);
System.out.println("시동 여부 :" + newCar.start);
System.out.println("현재 속도 :" + newCar.speed);
// Car oldCar = new Car(); The constructor Car() is undefined
}
}
Java
복사
•
메소드
◦
객체의 동작에 실행 블록을 정의하는 것을 의미한다.
◦
메소드 호출: 실제 블록을 실제로 실행한다.
◦
클래스로부터 객체가 생성된 후에는 생성자와 또 다른 메소드 내부에서 호출할 수 있으며, 객체 외부에서도 호출할 수 있다.
◦
객체 내부에서는 메소드 명으로 호출하면 되지만, 객체 외부에서는 참조 변수와 객체 접근 연산자(.) 를 이용해 메소드를 호출할 수 있다.
◦
리턴 타입
▪
메소드가 실행한 후 호출한 곳으로 전달하는 결과값의 데이터 타입을 의미한다.
▪
리턴 타입이 void가 아니라면 반드시 return 문 뒤에 반환값을 지정해야 한다.
◦
메소드명: CamelCase로 작성한다.
◦
메소드 오버로딩
▪
생성자처럼 오버로딩이 가능하다.
▪
같은 클래스 내부에 메소드의 이름은 같지만 매개변수의 타입, 개수, 순서가 다른 메소드를 선언하는 것을 의미한다.
▪
다양한 매개값을 처리하기 위해 사용한다.
▪
System.out.println() 메소드가 대표적인 사례이다.
◦
매개변수
▪
메소드를 호출할 때 전달한 매개값을 받기 위해 사용된다.
▪
전달할 매개값이 없다면 매개변수는 생략 가능하다.
▪
매개값의 개수가 때에 따라 달라지는 경우가 있으며, 이런 경우에는 가변 길이 매개변수를 사용할 수 있도록 메소드를 아래와 같이 선언해야 한다.
▪
가변 길이 매개변수는 메소드 호출 시 매개값을 쉼표로 구분해서 개수와 상관없이 제공할 수 있게 된다.
int sum(int...values) {
if (power) {
int result = 0;
for (int i : values){
result += i;
}
return result;
}
return 0;
}
}
int sumResult = calc.sum(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
System.out.println("sumResult = " + sumResult);
calc.powerOff();
Java
복사
▪
매개값들은 자동으로 배열 형태로 변환되어 메소드에서 사용된다. 그렇기에 매개값에 직접 배열을 제공해도 된다.
반환타입 메소드명(타입 ... 매개변수) {
실행할코드
}
Java
복사
•
인스턴스 멤버 & 정적 멤버
◦
필드와 메소드는 선언 방법에 따라 인스턴스 멤버와 정적 멤버로 분류할 수 있다.
◦
인스턴스 멤버로 선언되면, 객체가 생성되어야 사용할 수 있고, 정적 멤버로 선언되면 객체 생성 없이도 사용할 수 있다.
•
평균, 학점 결과 출력 실습
// StudentExample.java
package com.oop.basic.practice01;
public class StudentExample {
public static void main(String[] args) {
Student s1 = new Student("Kim", 100, 90, 95, 89);
Student s2 = new Student("Lee", 60, 70, 99, 98);
Student s3 = new Student("Park", 68, 86, 60, 40);
System.out.println("Kim, " + "평균 : " + s1.getAvg() + ", 학점: " + s1.getGrade() + "학점");
System.out.println("Lee, " + "평균 : " + s2.getAvg() + ", 학점: " + s2.getGrade() + "학점");
System.out.println("Park, " + "평균 : " + s3.getAvg() + ", 학점: " + s3.getGrade() + "학점");
}
}
Java
복사
// Student.java
package com.oop.basic.practice01;
public class Student {
String name;
int korean;
int english;
int math;
int science;
public Student(String name, int korean, int english, int math, int science) {
this.name = name;
this.korean = korean;
this.english = english;
this.math = math;
this.science = science;
}
double getAvg() {
int totalSum;
totalSum = korean + english + math + science;
return (double) totalSum / 4;
}
String getGrade() {
String grade;
double avg = getAvg();
if (avg >= 90 && avg <= 100) {
grade = "A";
} else if (avg < 90 && avg >= 70) {
grade = "B";
} else if (avg >= 30 && avg < 70) {
grade = "C";
} else {
grade = "F";
}
return grade;
}
}
// 출력 결과
// Kim, 평균 : 93.5, 학점: A학점
// Lee, 평균 : 81.75, 학점: B학점
// Park, 평균 : 63.5, 학점: C학점
Java
복사
•
책 리스트 출력 실습
// Book.java
package com.oop.basic.practice02;
public class Book {
String name;
int bookPrice;
double bookDiscountRate;
// 기본 생성자
Book() {
}
public Book(String name, int bookPrice, double bookDiscountRate) {
this.name = name;
this.bookPrice = bookPrice;
this.bookDiscountRate = bookDiscountRate;
}
double getDiscountBookPrice(double bookDiscountRate) {
return bookPrice - (bookPrice * bookDiscountRate * 0.01);
}
}
Java
복사
// BookExample.java
package com.oop.basic.practice02;
public class BookExample {
public static void main(String[] args) {
Book book1 = new Book("SQL Plus", 50000, 5.0);
Book book2 = new Book("Java 2.0", 40000, 3.0);
Book book3 = new Book("JSP Servlet", 60000, 6.0);
Book[] bookArr = {book1, book2, book3};
System.out.println("책이름\t 가격\t 할인율\t 할인후금액");
System.out.println("-------------------------------------");
for (Book b : bookArr) {
System.out.println(b.name + "\t" + b.bookPrice + "원" + "\t " + b.bookDiscountRate + "%\t" +
b.getDiscountBookPrice(b.bookDiscountRate) + "원");
}
}
}
Java
복사
•
이자 계산 프로그램 실습
// AccountExample.java
package com.oop.basic.practice03;
public class AccountExample {
public static void main(String[] args) {
Account ac = new Account("441-0290-1203", 500000, 7.3);
System.out.println("계좌정보: " + ac.account + " " + "현재잔액: " + ac.balance);
// 600000원 출금
ac.withdraw(600000);
// 20000원 입금
ac.deposit(20000);
// account 객체의 계좌정보 출력
System.out.println("계좌정보: " + ac.account + " 현재잔액: " + ac.balance);
// 이자 출력 - 현재 잔고를 기준으로 고객에게 줄 이자 금액을 출력
System.out.println("이자: " + ac.calculateInterest());
}
}
Java
복사
// Account.java
package com.oop.basic.practice03;
public class Account {
String account; // 계좌번호
int balance; // 잔액
double interestRate; // 이율
double calculateInterest() {
// 잔액 * 이율
return balance * interestRate / 100;
}
// 예수금
void deposit(int money) {
if (money < 0) {
return;
}
// 입금
balance += money;
}
// 인출금
void withdraw(int money) {
if (money > balance) {
System.out.println("출금할 수 없습니다.");
} else {
System.out.println("계좌정보: " + account + "현재잔액: " + (money - balance));
}
}
public Account(String account, int balance, double interestRate) {
this.account = account;
this.balance = balance;
this.interestRate = interestRate;
}
}
Java
복사
•
학생들의 나이, 키, 신장, 몸무게 평균 구하기 (소수점 둘째짜리까지 출력)
// Student.java
package com.oop.basic.practice04;
public class Student {
String name; // 이름
int age; // 나이
int height; // 키
int weight; // 몸무게
// Student() 생성자 생성
Student() {
}
// 매개변수 생성자 생성
public Student(String name, int age, int height, int weight) {
this.name = name;
this.age = age;
this.height = height;
this.weight = weight;
}
}
Java
복사
// StudentExample.java
package com.oop.basic.practice04;
public class StudentExample {
public static void main(String[] args) {
// Student 객체를 3개 생성하여 배열에 넣는다.
Student s1 = new Student("홍길동", 25, 171, 81);
Student s2 = new Student("한사람", 23, 183, 72);
Student s3 = new Student("홍길동", 26, 175, 65);
Student[] sArr = {s1, s2, s3};
int ageSum = 0;
int heightSum = 0;
int weightSum = 0;
// 배열의 객체 정보 모두 출력 (for)
// 나이, 신장, 몸무게 합 구하기
System.out.println("이름 \t 나이 신장 몸무게");
for (Student st: sArr) {
System.out.println(st.name + "\t " + st.age + "\t " + st.height + "\t " + st.weight);
ageSum += st.age;
heightSum += st.height;
weightSum += st.weight;
}
System.out.println();
// 주의사항 : 평균값은 소수점 3자리에서 반올림하여 2자리까지 표현한다 (Math.round)
// 나이의 평균
System.out.println("나이의 평균: " + Math.round(ageSum / 3.0 * 100) / 100.0);
// 신장의 평균
System.out.println("신장의 평균: " + Math.round(heightSum / 3.0 * 100) / 100.0);
// 몸무게의 평균
System.out.println("몸무게의 평균: " + Math.round((double)weightSum / 3 * 100) / 100.0);
}
}
Java
복사