[Java] 객체지향 프로그래밍 - 역사와 클래스

객체지향(OOP) 언어

OOP의 역사

객체지향 이론의 기본 개념은 '실제 세계는 사물객체로 이루어져 있으며, 발생하는 모든 사건들은 사물간의 상호작용이다'에요. 실제 사물의 속성과 기능을 분석한 다음, 데이터(변수)와 함수로 정희하여 실제 세계를 컴퓨터 속에 옮겨 놓은 것과 같은 가상 세계를 구현하고 이 가상세계에서 모의실험을 함으로써 많은 시간과 비용을 절약할 수 있게 되요.

프로그램의 규모가 커미족 사용자들의 요구가 빠르게 변하는 상황을 절차적 언어로 극복하기 어렵다는 한계를 느끼고 객체지향 언어를 이용한 개발방법론이 입지를 넓혀가고 있어요.

OOP의 특징

1. 코드의 재사용성이 높다

새로운 코드를 작성할 때 기존의 코드를 다시 사용해서 쉽게 작성이 가능해요.

2. 코드의 관리가 용이하다

코드간의 관계를 이용해서 적은 노력으로 쉽게 코드를 변경할 수 있어요.

3. 신뢰성이 높은 프로그래밍이 가능하다

제어자와 메서드를 이용해 데이터를 보호하고 올바른 값을 유지해요. 그리고 코드의 중복을 제거하여 코드의 불일치로 인한 오동작을 방지할 수 있어요.

위와 같은 장점이 있다곤 하지만, 제대로 활용하기 어려워요. 그래서 거시적으로 설계하는데 있어 시간을 많이 소비하곤 해요. 그러니 너무 객체지향개념에 얽매여서 고민하기 보단 일단 프로그램을 기능적으로 완성한 다음 어떻게 객체지향적으로 개선을 할 수 있을지 고민하여 점차 개선해나가는 것이 좋아요(리팩토링Refactoring이라고 하죠 ㅎㅎ).

클래스와 객체

클래스와 객체의 정의와 용도

클래스class는 객체를 '정의'한 것이에요. 쉽게 말하면 일종의 도면이에요. 클래스라는 도면을 설계해서, 이를 토대로 객체object생성한다고 생각하시면 되요.

객체는 TV, 에어컨 같은 사물, 사람 그리고 동물과 같은 유형 또는 수학 공식, 프로그램 에러 등 무형의 논리와 개념이 될 수 있어요.

클래스와 객체의 상관관계 예

클래스객체
제품 설계도제품
TV 설계도TV
수학 공식의 수식수학 공식

설계도가 한번 잘 만들어지면 이를 기반한 사물이나 개념은 의도하는 대로 작동할 뿐만 아니라, 이미 설계도가 있어 이를 무한하게 만들 수 있어요물론 컴퓨터 메모리가 되는 선까지요 ㅎㅎ. 그래서 매번 객체를 생성할 때마다 어떻게 객체를 만들지 고민하지 않아도 되요. 그냥 클래스를 통해 생성하기만 하면 되니까요.

JDK는 프로그래밍을 위해 수 많은 유용한 클래스(Java API)를 기본적으로 제공해요. 우리는 이 클래스들을 이용해서 원하는 기능의 프로그램을 쉽게 작성할 수 있어요.

객체와 인스턴스

클래스를 통해 객체를 생성하는 것을 인스턴스화instantiate라고 해요. 그리고 클래스를 통해 생성된 객체를 해당 클래스의 인스턴스instance라고 해요. 예를 들면 TV라는 클래스로 생성된 객체를 TV 클래스의 인스턴스라고 해요.

객체와 인스턴스를 혼용하는 경우가 있는데, 확연한 구분을 위해 객체는 클래스를 통해 생성된 것이고, 그 객체는 해당 클래스의 인스턴스라고 구분하여 사용하길 권해요.

인스턴스를 생성하는 예제를 간단하게 하자면 아래와 같아요.

// 클래스 선언
class Tv{
}

// 인스턴스 생성
Tv t = new Tv();

이때 클래스 변수는 참조형 변수에요. 무슨 말이냐면 Tv 인스턴스는 메모리 주소 어딘가에 적재되는 것이고 변수 t는 이 메모리 주소를 가리키는 것참조이에요. 그 말은 t는 그 해당 인스턴스가 아닌 다른 인스턴스도 얼마든지 가리킬 수 있다는 것이에요. t = Tv 인스턴스가 아닌 것이죠. 인스턴스는 얼마든지 만들어질 수 있고, t는 새로 생성된 인스턴스를 가리킬 수도 있는 거에요. 예를 들면,

Tv t1 = new Tv();
Tv t2 = new Tv();

t1, t2는 서로 다른 인스턴스의 주소를 가리켜요. 근데 여기서,

t2 = t1;

이렇게 되면 기존에 t2가 참조한 인스턴스의 주소가 아닌, t1이 참조한 인스턴스를 참조하게 되죠. 이때 원래 t2가 참조한 메모리는 아무도 참조하지 않으니까 JVM에서 더이상 사용할 일이 없는 메모리다 판단하고 메모리 해제 처리해요.

변수와 메서드

선언 위치에 따른 변수 종류

변수Variable클래스 변수, 인스턴스 변수, 지역 변수 3가지 종류가 있어요. 이를 구분하는 것은 변수가 선언된 위치에요.

1. 클래스 변수
클래스 내 static 키워드가 붙은 변수에요. 클래스가 생성되어 있지 않아도 접근이 가능한 변수에요. 생성된 객체 또는 프로그램이 해당 변수를 공유하고 싶을 때 사용해요. {클래스 이름}.{변수 명} 형식으로 사용할 수 있어요.

클래스가 메모리에 적재되었을 때 생성되어 프로그램이 종료될 때까지 유지되요. 앞에 public 키워드가 붙으면 같은 프로그램 내에서 어디든 사용할 수 있는 전역 변수global variable 성격을 가져요.

2. 인스턴스 변수
멤버 변수로, 클래스를 통해 객체를 생성하면 해당 객체 고유의 속성 변수에요. 객체 고유의 속성을 관리할 때 사용해요.

3. 지역 변수
메서드 내에서 선언된 변수에요. 메서드 내부에서만 사용할 경우에 사용해요.

메서드

메서드Method는 특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것이에요. 입력이 주어지면 출력을 한다는 점에서 수학의 함수와 유사해요.

메서드가 작업을 수행하는데 필요한 값만 넣고 원하는 결과를 얻으면 될 뿐, 어떤 과정을 거치는지 알 필요가 없어요. 그래서 메서드를 내부가 보이지 않는 블랙 박스black box라고도 해요.

메서드 사용 이유

얻는 이점은 여러가지지만 그 중 대표적으로 3가지를 꼽자면,

1. 높은 재사용성(Reusablity)
한번 만들어 놓은 메서드는 몇 번이고 호출이 가능하고, 이를 API로 만들면 여러 프로그램에서 사용이 가능해요.

2. 중복된 코드 제거
프로그램을 만들다 보면 같은 내용의 코드가 여러 곳에서 반복해서 나타나요. 이런 코드들을 하나의 메서드로 묶어서 작성해 놓으면, 메서드 호출 문장 하나로 대체가 가능해요.

그렇게 전체 소스 코드를 줄이고 변경사항이 발생했을 때 메서드만 바꿔주면 변경할 코드 양도 현저히 줄어들어요.

3. 프로그램 구조화
큰 규모의 프로그램에서는 문장들을 작업 단위로 나눠서 여러 메서드에 담해 프로그램의 구조를 단순화시킬 수 있어요.

예를 들면,

public class App{
	int a, b;
    
    public static main(String[] args){
    	// 변수를 초기화 한다.
        a = 2, b = 4;
        // 두 변수를 곱한다.
        System.out.println(a * b);
        // 두 변수를 더한다.
        System.out.println(a + b);
    }
}

main에서 많은 것을 담당하고 있어요. 이게 1000줄만 넘어가도, 개인적으로 전 감당할 수 없었어요 ㅎㅎ. 그래서 아래와 같이 구조화시키면 주석도 넣을 필요가 없을 정도의 가독성을 보여주고 문제가 발생한 부분만 찾아가 수정할 수 있는 장점이 있어요.

public class App{
	int a, b;
    
    public static main(String[] args){
		init();
        printMultiply();
        printAdd();
    }
    
    private static void init(){
    	a = 2, b = 4;
	}
    
    private static void printMultiply(){
        System.out.println(a * b);
    }
    
    private static void printAdd(){
        System.out.println(a + b);
    }
}

메서드 호출

인자(Argument)와 매개변수(Parameter)

메서드를 호출하게 되면 입력 값을 넣어줘야 하는 경우가 있어요. 이때 이 입력 값을 매개변수parameter라고 해요.

doSomething(3, 5);	// 호출했을 때 넣는 매개변수

그리고 메서드 선언부에서 사용되는 입력 값을 인자argument라고 해요. 혼용되는 경우가 있지만 엄연히 구분해서 부르는 것을 권장해요.

// 여기서 인자는 a와 b
int doSomething(int a, int b){
	// 무언가...무언가 일어나고 있어...!
    return 1;
}

기본형 매개변수와 참조형 매개변수

자바에서는 메서들르 호출할 때 매개변수로 지정한 값을 메서드의 매개변수에 '복사'해서 넘겨줘요.

이 때, 매개변수 타입이 기본형primitive(int, char)이라면 저장된 값이 복사되고, 참조형Reference(class)이라면 해당 인스턴스의 '주소'가 복사되요.

class Data{ int x = 0; }
public class Main{

    public static void main(String[] args) {
        int a = 0;
        change(a);
        System.out.println(a);
        Data d = new Data();
        change(d);
        System.out.println(d.x);
    }

    private static void change(int a){
        ++a;
    }
    private static void change(Data a){
        ++a.x;
    }

}

위 코드를 돌려보면 inta는 값이 안 바뀌고 Data 클래스 내 x는 바뀌어요. 전자는 기본형, 후자는 참조형이에요.

기본형은 값을 복사한거지 a라는 변수의 주소, 즉 본체를 복사한 것이 아니에요. 그렇기 때문에 함수 호출을 해도 내부에서 복사된 값이 변한거지 a가 바뀐게 아니에요.

반면, Data 변수는 참조, 즉 주소 자체를 복사하기 때문에 d 자체가 매개변수로 들어가요. 그렇기 때문에 변화가 일어난 거에요.

클래스 메서드와 인스턴스 메서드

변수랑 똑같아요. 클래스 메서드는 앞에 static 키워드가 있고, 모든 곳에서 접근이 가능하고, 인스턴스 메서드는 생성된 인스턴스를 통해 사용할 수 있어요.

여기서 주의해야할 것은 클래스 메서드는 인스턴스 생성이 없이도 접근이 가능하기 때문에 인스턴스 변수를 사용할 수 없어요. 왜냐하면 인스턴스 변수는 인스턴스가 생성되야 메모리에 적재되어 사용할 수 있기 때문이죠.

class Test{
	public static void do(Test t){
    	// var = 0; // 사용할 수 없어요
        t.var = 1;	// 이렇게 생성된 인스턴스를 통해 사용은 할 수 있어요.
	}
    int var;
}

메서드/변수와 관련된 내용을 정리하자면,

1. 클래스를 설계 할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙여요
모든 인스턴스에서 같은 값이 유지되어야 하는 변수는 static을 붙여서 클래스 변수로 정의해요

2. 클래스 변수는 인스턴스를 생성하지 않아도 사용할 수 있어요
static이 붙은 변수는 클래스가 메모리에 올라갈 때 이미 자동적으로 생성되기 때문이에요.

3. 클래스 메서드는 인스턴스 변수를 사용할 수 없어요
위에 보인 예제를 보시면 알겠죠? 그리고 인스턴스 변수나 인스턴스 메서드에서는 클래스 변수/메서드를 사용할 수 있어요.

4. 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려해요
메서드의 작업 내용 중에서 인스턴스 변수를 필요로 하면 static 붙여선 안되지만 반대로 인스턴스 변수를 필요로 하지 않으면 static을 붙이는 것이 좋아요. 왜냐하면 메서드 호출 시간이 짧아져 성능이 향상되기 때문이에요. 인스턴스 메서드/변수는 실행 시 호출되어야할 메서드/변수를 찾는 과정이 필요해 시간이 더 걸리기 때문이에요.

좋은 웹페이지 즐겨찾기