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
복사