[Java] 객체 지향 프로그래밍
1. 개요
1.1 객체 지향 프로그래밍(OOP) 개념
- 객체 지향 프로그래밍(Object-Oriented Programming, OOP)은 소프트웨어 설계와 프로그래밍 패러다임 중 하나로, 프로그램을 객체라는 기본 단위로 구성하는 방식을 의미한다.
- 객체는 데이터와 해당 데이터를 처리하는 메서드(함수)를 포함하며, 이 두 가지를 하나의 단위로 묶어 캡슐화(encapsulation)한다.
1.2 OOP 특징
개념 | 설명 |
---|---|
클래스(Class) | 객체를 정의하는 틀 혹은 설계도이다. 클래스는 객체의 속성과 행동을 정의한다. |
객체(Object) | 클래스에 의해 생성된 인스턴스이다. 객체는 클래스에서 정의된 속성과 행동을 가진다. |
상속(Inheritance) | 한 클래스가 다른 클래스의 속성과 메서드를 물려받는 기능이다. 이를 통해 코드의 재사용성을 높일 수 있다. |
다형성(Polymorphism) | 동일한 메서드나 연산자가 다양한 방식으로 동작할 수 있게 하는 기능이다. 이는 주로 오버로딩과 오버라이딩을 통해 구현된다.(동일한 이름의 메서드가 상황에 따라 다른 일을 할 수 있음) |
캡슐화(Encapsulation) | 객체의 속성과 메서드를 하나로 묶고, 외부에서 접근을 제한하여 객체의 내부 상태를 보호하는 메커니즘이다. |
추상화(Abstraction) | 복잡한 시스템을 단순화하여 중요한 부분만을 모델링하는 과정이다. 이를 통해 복잡성을 줄이고 코드의 이해와 유지보수를 쉽게 한다. |
- 즉, 객체 지향 프로그래밍은 코드의 재사용성, 유지보수성, 확장성을 높이는 데 유리하며, 복잡한 소프트웨어 시스템을 관리하기 쉽게 한다.
- 대표적인 객체 지향 프로그래밍 언어로는 자바(Java), C++, 파이썬(Python), C# 등이 있다.
2. 클래스와 인스턴스
2.1 클래스, 객체, 인스턴스
위 용어를 붕어빵 틀에 비유해보자.
개념 | 비유 | 설명 |
---|---|---|
클래스(Class) | 붕어빵 틀 | 붕어빵의 모양, 크기, 재료 등을 정의하는 설계도. 객체의 속성(크기, 모양, 재료)과 행동(굽는 방법)을 정의. |
객체(Object) | 붕어빵 | 붕어빵 틀로 만든 실제 붕어빵. 같은 틀에서 나왔지만 재료(팥, 슈크림 등)에 따라 다를 수 있음. |
인스턴스(Instance) | 특정한 붕어빵 | 붕어빵 틀로 만들어진 구체적인 붕어빵 하나. 예: 팥이 들어간 붕어빵, 슈크림이 들어간 붕어빵. |
- 즉, 객체를 만들 때마다 새로 정의하는 것이 아니고, 기존에 객체의 형태를 미리 정의해 놓은 클래스를 사용하는 것이다.
- 클래스에서 객체가 생성되는 것을 실체화 라고 하며, 구체적으로 생성된 객체를 인스턴스라고 한다.(일반적으로 인스턴스와 객체를 혼용하기도 함)
2.2 클래스 생성
- 패키지 이름: projectOOP
- 클래스 이름: _01_othersOOP
클래스 직접 사용
Math는 클래스이고, 클래스 안에는 변수(PI 등), 메서드(floor, ceil 등)이 존재한다.
package projectOOP;
public class _01_othersOOP {
public static void main(String[] args) {
System.out.println(Math.PI);
System.out.println(Math.floor(1.8));
System.out.println(Math.ceil(1.8));
}
}
클래스 복제 사용
FileWriter 클래스를 사용하여 파일을 생성할 수 있다.
package projectOOP;
import java.io.FileWriter;
import java.io.IOException;
public class _01_othersOOP {
public static void main(String[] args) throws IOException {
FileWriter f1= new FileWriter("data.text");
f1.write("Hello");
f1.write("Java");
f1.close();
}
}
즉, 클래스와 인스턴스를 정리하면 다음과 같다.
- 클래스: System, Math, FileWriter
- 인스턴스: f1
3. 변수와 메소드
3.1 메소드 생성하기
printA
,printB
라는 메소드를 생성해보자
package projectOOP;
public class _02_myOOP {
public static String delimiter="";
public static void main(String[] args) {
delimiter="---";
printA();
printB();
delimiter="***";
printA();
printB();
}
public static void printA(){
System.out.println(delimiter);
System.out.println("A");
}
public static void printB() {
System.out.println(delimiter);
System.out.println("B");
}
}
프로그램 실행 흐름
- delimiter를 “—“로 설정
- printA()를 호출하여 delimiter와 “A”를 출력
- printB()를 호출하여 delimiter와 “B”를 출력
- delimiter를 “***“로 변경
- printA()를 호출하여 새로운 delimiter와 “A”를 출력
- printB()를 호출하여 새로운 delimiter와 “B”를 출력
4. 클래스 사용하기
delimiter와 printA, printB 메소드가 서로 연관되어 있으므로, 클래스를 통해 이를 하나의 단위로 묶어서 관리해보자
출력 결과는 동일하다.
package projectOOP;
// Print 클래스 생성
class Print{
public static String delimiter="";
public static void A(){
System.out.println(delimiter);
System.out.println("A");
}
public static void B() {
System.out.println(delimiter);
System.out.println("B");
}
}
public class _02_myOOP {
public static void main(String[] args) {
Print.delimiter="---";
Print.A(); // Print라는 클래스에 소속되어있는 A메소드
Print.B();
Print.delimiter="***";
Print.A();
Print.B();
}
}
- 이렇게 Print라는 클래스가 출력과 관련된 작업을 수행한다는 것을 쉽게 알 수 있다! -> 가독성 ↑
- 클래스안에는 변수와 메소드들이 들어있는데, 이를 멤버라고한다. (위 코드에서 멤버는 delimiter, A, B)
5. 인스턴스
위 코드를 인스턴스를 생성하여 효율적으로 만들어보자. 출력 결과는 위와 동일하다
package projectOOP;
class Print {
// Instance를 만들어내기 위해 "static"을 지워야 함
public String delimiter="";
public void A(){
System.out.println(delimiter);
System.out.println("A");
}
public void B(){
System.out.println(delimiter);
System.out.println("B");
}
}
public class _01_othersOOP {
public static void main(String[] args) {
Print p1 = new Print();
p1.delimiter="---";
Print p2 = new Print();
p2.delimiter="***";
p1.A();
p1.B();
p2.A();
p2.B();
}
}
- 이처럼 Print라는 클래스 복제한 인스턴스를 통해 한 번 정의한 클래스를 여러 곳에서 재사용할 수 있게 함으로써 코드의 간결성과 코드의 중복을 제거할 수 있다.
6. static
static
이 있는건 클래스 소속(클래스 이름을 통해 직접 접근하거나 호출 가능), 없는 것은 인스턴스 소속(인스턴스를 통해서만 접근하거나 호출 가능)이다.
package projectOOP;
class Foo {
// 1) 정적 변수
public static String classVar = "I class var";
// 2) 인스턴스 변수
public String instanceVar = "I instance var";
// 3) 정적 메서드
public static void classMethod() {
System.out.println(classVar); // Ok: 정적 메서드에서 정적 변수에 접근 가능
// System.out.println(instanceVar); // Error: 정적 메서드에서는 인스턴스 변수에 접근 불가
}
// 4) 인스턴스 메서드
public void instanceMethod() {
System.out.println(classVar); // Ok: 인스턴스 메서드에서 정적 변수에 접근 가능
System.out.println(instanceVar); // Ok: 인스턴스 메서드에서 인스턴스 변수에 접근 가능
}
}
public class _02_StaticApp {
public static void main(String[] args) {
/*** 클래스 변수 -> 클래스 이름을 통해 호출 가능 ***/
System.out.println(Foo.classVar); // Ok: 클래스 변수는 클래스 이름을 통해 접근 가능
/*** 인스턴스 변수 -> 인스턴스를 통해서만 접근 가능 ***/
// System.out.println(Foo.instanceVar); // Error: 인스턴스 변수는 인스턴스를 통해서만 접근 가능
/*** 클래스 메서드 -> 클래스 이름을 통해 호출 가능 ***/
Foo.classMethod(); // Ok: 클래스 메서드는 클래스 이름을 통해 호출 가능
/*** 인스턴스 메서드 -> 인스턴스를 통해서만 호출 가능 ***/
// Foo.instanceMethod(); // Error: 인스턴스 메서드는 인스턴스를 통해서만 호출 가능
// 인스턴스 생성
Foo f1 = new Foo();
Foo f2 = new Foo();
/*** 인스턴스 변수를 통해 접근 ***/
System.out.println(f1.classVar); // I class var: 인스턴스를 통해 정적 변수에 접근 가능하지만 권장되지 않음
System.out.println(f1.instanceVar); // I instance var: 인스턴스를 통해 인스턴스 변수에 접근 가능
/*** 정적 변수 변경 ***/
f1.classVar = "changed by f1";
System.out.println(Foo.classVar); // changed by f1: 정적 변수는 모든 인스턴스에 공유됨
System.out.println(f2.classVar); // changed by f1: 정적 변수는 모든 인스턴스에 공유됨
/*** 정적 변수 다시 변경 ***/
f2.classVar = "changed by f2";
System.out.println(Foo.classVar); // changed by f2: 정적 변수는 모든 인스턴스에 공유됨
System.out.println(f2.instanceVar); // I instance var: 인스턴스 변수는 각 인스턴스별로 독립적
}
}
7. 생성자와 this
- 생성자(Constructor)는 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드로, 객체 초기화를 위해 사용된다.
- 생성자는 반환 타입이 없고, 직접 호출되지 않으며 객체 생성 시 new 키워드를 통해 호출된다.
- 생성자의 이름은 클래스의 이름과 동일해야 한다.
package projectOOP;
class Print {
// 생성자
public Print(String _delimiter){
delimiter=_delimiter;
}
public String delimiter="";
public void A(){
System.out.println(delimiter);
System.out.println("A");
}
public void B(){
System.out.println(delimiter);
System.out.println("B");
}
}
public class _01_othersOOP {
public static void main(String[] args) {
Print p1 = new Print("---");
Print p2 = new Print("***");
p1.A();
p1.B();
p2.A();
p2.B();
}
}
- 생성자나 메소드 내에서 인스턴스 변수와 로컬 변수의 이름이 동일할 때, this를 사용하여 인스턴스 변수를 명확히 구분할 수 있다.
- 아래 코드에서
this.delimiter
는 객체의 인스턴스 변수이고,delimiter
는 생성자의 매개변수이다.- 즉, this는 인스턴스 자기 자신을 의미하며 객체 자신을 참조하기 위해 사용된다.
package projectOOP;
class Print {
// 생성자
public String delimiter="";
public Print(String delimiter){
this.delimiter = delimiter;
}
public void A(){
System.out.println(this.delimiter);
System.out.println("A");
}
public void B(){
System.out.println(this.delimiter);
System.out.println("B");
}
}
public class _01_othersOOP {
public static void main(String[] args) {
Print p1 = new Print("---");
Print p2 = new Print("***");
p1.A();
p1.B();
p2.A();
p2.B();
}
}
8. 활용
8.1 클래스화
아래 코드를 클래스화 시켜보자
package projectOOP;
public class AccountingApp {
// 공급가액
public static double valueOfSupply = 10000.0;
// 부가가치세율
public static double vatRate = 0.1;
public static double getVAT() {
return valueOfSupply * vatRate;
}
public static double getTotal() {
return valueOfSupply + getVAT();
}
public static void main(String[] args) {
System.out.println("Value of supply : " + valueOfSupply);
System.out.println("VAT : " + getVAT());
System.out.println("Total : " + getTotal());
}
}
class Accounting{
public static double valueOfSupply;
public static double vatRate = 0.1;
public static double getVAT() {
return valueOfSupply * vatRate;
}
public static double getTotal() {
return valueOfSupply + getVAT();
}
}
public class AccountingApp {
public static void main(String[] args) {
Accounting.valueOfSupply = 10000.0;
System.out.println("Value of supply : " + Accounting.valueOfSupply);
System.out.println("VAT : " + Accounting.getVAT());
System.out.println("Total : " + Accounting.getTotal());
}
}
package projectOOP;
// 부가가치세와 관련된 정보를 모아둔 클래스
class Accounting{
// 부가가치세율
public static double valueOfSupply;
public static double vatRate = 0.1;
public static double getVAT() {
return valueOfSupply * vatRate;
}
public static double getTotal() {
return valueOfSupply + getVAT();
}
}
public class AccountingApp {
public static void main(String[] args) {
// 공급가액
Accounting.valueOfSupply = 10000.0;
System.out.println("Value of supply : " + Accounting.valueOfSupply);
System.out.println("VAT : " + Accounting.getVAT());
System.out.println("Total : " + Accounting.getTotal());
}
}
8.2 인스턴스화
공급가액, 부가가치세 등 값이 변경되면 어떻게해야할까?
package projectOOP;
// 부가가치세와 관련된 정보를 모아둔 클래스
class Accounting{
// 부가가치세율
// static 제거
public double valueOfSupply;
public static double vatRate = 0.1;
public double getVAT() {
return valueOfSupply * vatRate;
}
public double getTotal() {
return valueOfSupply + getVAT();
}
}
public class AccountingApp {
public static void main(String[] args) {
Accounting a1 = new Accounting();
a1.valueOfSupply = 10000.0;
Accounting a2 = new Accounting();
a2.valueOfSupply = 20000.0;
System.out.println("Value of supply : " + a1.valueOfSupply); // 인스턴스에 소속된 변수이므로 static 제거해야함
System.out.println("Value of supply : " + a2.valueOfSupply);
}
}
댓글남기기