Backend
home

2024. 7. 12

객체지향 프로그래밍

자바의 핵심 개념인 객체지향 프로그래밍에 대하여 정리

객체지향 프로그래밍(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
복사