객체지향 프로그래밍 1 (1-3)

객체지향언어

객체지향언어는 객체지향이론의 상속, 캡슐화, 추상화 개념을 중심으로 발전한 기존의 프로그래밍언어에 몇 가지 새로운 규칙을 추가한 보다 발전된 형태의 것이다. 이러한 규칙들을 이용해 코드간에 서로 관계를 맺어 줌으로써 보다 유기적으로 프로그램을 구성하는 것이 가능해졌다.

객체지향언어의 주요 특징

1. 코드의 재사용성이 높다 : 새로운 코드를 작성할 때 기존의 코드를 이용하여 쉽게 작성할 수 있다.

2. 코드의 관리가 용이하다 : 코드간의 관계를 이용해서 적은 노력으로 쉽게 코드를 변경할 수 있다.

3. 신뢰성이 높은 프로그래밍을 가능하게 한다 : 제어자와 메서드를 이용해서 데이터를 보호하고 올바른 값을 유지하도록 하며, 코드의 중복을 제거하여 코드의 불일치로 인한 오동작을 방지할 수 있다.

이러한 특징들은 프로그램의 개발과 유지보수에 드는 시간과 비용을 기적적으로 개선하였다.
앞으로 상속, 다형성과 같은 객체지향개념을 학습할 때 재사용성, 유지보수, 중복된 코드의 제거 이 세가지 관점에서 보는 노력을 하자.

코드 설계시 너무 객체지향갠며에 얽매이기 보단, 일단 프로그램을 기능적으로 완성한 후 객체지향적으로 리팩토링하는 쪽으로 생각하자.

클래스와 객체

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

클래스란 '객체를 정의해놓은 것' 또는 '객체의 설계도 또는 틀' 이다.
클래스는 객체를 생성하는데 사용되며, 객체는 클래스에 정의된 대로 생성된다.

객체의 사전적 정의는 '실제로 존재하는 것'이다. 우리가 주변에서 볼 수 있는 사물들이 객체이다.
객체지향이론에서는 사물과 같은 유형적인 것 뿐만 아니라, 개념이나 논리와 같은 무형적인 것들도 객체로 간주한다.
프로그래밍에서 객체란 클래스에 정으된 내용대로 메모리에 생성된 것을 뜻한다.

클래스를 정의하고 클래스를 통해 객체를 생성하는 이유는 클래스를 한번만 잘 생성한다면, 매번 객체를 생성할 때마다 어떻게 객체를 만들어야 할지 고민하지 않아도 되기 때문이다.(재사용성(

객체와 인스턴스

클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화(instantiate) 라고 하며, 어떤 클래스로부터 만들어진 객체를 그 클래스의 인스턴스(instance) 라고 한다.

인스턴스와 객체는 같은 의미로 두 용어의 사용을 엄격히 구분할 필요는 없지만, 문맥에 따라 구별하여 사용하는 것이 좋다.

객체의 구성요소 - 속성과 기능

객체는 속성과 기능으로 이루어져 있으며, 일반적으로 객체는 다수의 속성과 기능을 가진다. 즉 객체는 속성과 기능의 집합이라고 볼 수 있다.

  • 속성(property) : 멤버변수, 특성, 필드, 상태
  • 기능(function) : 메서드, 함수, 행위

객체지향 프로그래밍에서는 속성과 기능을 각각 변수와 메서드로 표현한다.

속성기능
크기, 길이, 높이, 색상, 볼륨, 채널켜기, 끄기, 볼륨 조절, 채널 변경
class Tv{
    // 색상 등, 속성 --> 변수
    String color; 
    boolean power;
    int channel;
    
    // 전원 등, 기능 --> 메소드
    void power() { ... } 
    void channelChange() { ... }
    void volumnChange() {...}
    }

인스턴스의 생성과 사용

위 Tv 클래스 는 Tv의 설계도 일뿐, 클래스의 인스턴스를 생성해야 Tv를 사용할 수 있다. 클래스로부터 인스턴스를 생성하는 방법은 여러 가지가 있지만 일반적으로 다음과 같이 한다.

class Tv{
    // 색상 등, 속성 --> 변수
    String color; 
    boolean power;
    int channel;
    
    // 전원 등, 기능 --> 메소드
    void power() { ... } 
    void channelChange() { ... }
    void volumnChange() {...}
}
    
 class TvTest{
 	public static void main(String args[]){
    
    Tv t; // 1
    t = new Tv(); // 2
    t.channel = 7;
    t.channelChange();
    }
}
  1. Tv 클래스 타입의 참조변수 t를 선언한다. 메모리에 참조변수 t를 위한 공간이 마련된다. 아직 인스턴스가 생성되지 않았으므로 참조변수가 아무것도 할 수 없는 상태이다.

  2. new 연산자에 의해 Tv 클래스의 인스턴스가 메모리의 빈 공간에 생성된다. 예를들어 주소 0x100 에 생성되었다면, 이 때, 멤버변수는 각 자료형에 해당하는 기본값으로 초기화된다.
    이 후, 대입연산자에 의해 생성된 객체의 주소값이 참조변수 t에 저장된다. 이제 참조변수 t를 통해 Tv인스턴스에 접근할 수 있다.
    인스턴스를 다르기 위해서는 참조변수가 반드시 필요하다.

객체 배열

객체 역시 배욜로 다루는 것이 가능하며, 이를 '객체 배열' 이라고 한다.
하지만 객체 배열안에 실제 객체가 저장되는 것이 아닌, 객체의 주소를 저장하는 것 이다.
객체 배열은 참조변수들을 하나로 묶은 참조 변수 배열 이다.

클래스의 또 다른 정의

클래스는 '객체를 생성하기 위한 틀' 이며, '클래스는 속성과 기능으로 정의되어 있다.' 고 했다. 이는 객체지향론적의 관점이며, 프로그래밍적 관점에서 살펴보자.

  1. 클래스 - 데이터와 함수의 집합 :
    프로그래밍언어에서 데이터 처리를 위한 데이터의 저장형태는
    변수 -> 배열 -> 구조체 -> 클래스 의 형태로 발전해왔다.
  • 변수 : 하나의 데이터를 저장할 수 있는 공간
  • 배열 : 같은 종류의 여러 데이터를 하나의 집합으로 저장할 수 있는 공간
  • 구조체 : 서로 관련된 여러 데이터를 종류에 관계없이 하나의 집합으로 저장할 수 있는 공간
  • 클래스 : 데이터와 함수의 결합(구조체 + 함수)
  1. 클래스 - 사용자정의 타입 :
    프로그래밍언어에서 제공하는 자료형 외에 프로그래머가 서로 관련된 변수들을 묶어서 하나의 타입으로 새로 추가하는 것을 사용자정의 타입이라고 한다.
    다른 프로그래밍언어도 사용자정의 타입을 제공하지만,
    자바같은 객체지향언어에서는 클래스가 곧 사용자 정의 타입이다.

변수와 메서드

선언 위치에 따른 변수의 종류

변수는 클래스 변수, 인스턴스 변수, 지역 변수 모두 세 종류가 있다.
변수의 종류를 결정짓는 중요한 요소는 '변수의 선언된 위치' 이므로 변수의 종류를 파악하기 위해서는 변수가 어느 영역에 선언되었는지를 확인하는 것이 중요하다.

멤버변수를 제외한 나머지 변수들은 모두 지역변수이며, 멤버변수 중 static이 붙은 것은 클래스변수, 붙지 않은 것은 인스턴스변수이다.

class Variables{
	int iv; // 인스턴스 변수
    static int cv; // 클래스 변수(static 변수, 공유변수)
    
    void method() {
    int lv = 0; // 지역변수
변수의 종류선언 위치생성 시기
클래스 변수
(static)
클래스 영역클래스가 메모리에 올라갈 때
인스턴스 변수클래스 영역인스턴스가 생성되었을 때
지역변수클래스 영역 이외의 영역
(메서드, 생성자, 초기화 블럭 내부)
변수 선언문이 수행되었을 때
  1. 인스턴스 변수 :
    클래스의 인스턴스를 생성할 때 만들어진다. 그렇기 때문에 인스턴스 변수의 값을 컨트롤하기 위해서는 먼저 인스턴스를 생성해야한다.
    인스턴스는 독립적인 저장공간을 가지므로 서로 다른 값을 가질 수 있다.
    인스턴스마다 고유한 상태를 유지해야하는 속성의 경우, 인스턴스변수로 선언한다.

  2. 클래스 변수 :
    클래스 변수를 선언하기 위해서는 인스턴스변수 앞에 static을 붙이기만 하면 된다.
    인스턴스마다 독립적인 저장공간을 갖는 인스턴스변수와는 달리, 클래스변수는 모든 인스턴스가 공통된 저장공간을 공유하게 된다.
    한 클래스의 모든 인스턴스들이 공통적인 값을 유지해야하는 속성의 경우, 클래스 변수로 선언한다.

  3. 지역 변수 :
    메서드 내에 선언되어 메서드 내에서만 사용이 가능하며, 메서드가 종료되면 소멸되어 사용할 수 없게 된다.
    지역변수가 선안된 {} 블럭 안에서만 사용 가능하다.

클래스 변수와 인스턴스 변수

클래스 변수와 인스턴스 변수의 차이를 이해하기 위해 Card 클래스를 만든다.

class card {
	String kind; // 무늬
    int number; // 숫자
    
    static int width = 100; // 폭
    static int height = 250; // 높이
}

각 Card 인스턴스는 자신만의 무늬와 숫자를 가져야 하므로 인스턴스 변수로 선언하고,
카드의 사이즈, 폭과 높이는 공통적으로 같은 값을 유지해야 하므로 클래스 변수로 선언한다.
모든 카드의 width의 값을 변경하지 않고 한 카드의 width의 값만 변경해도 모든 카다의 width값이 변경된다.

메서드

메서드는 특정 작업을 수행하는 일련의 문장을 하나로 묶은 것이다.
기본적으로 수학의 함수와 유사하다.

메서드를 사용하는 이유
1. 높은 재사용성 : 한번 만들어 놓은 메서드는 몇 번이고 호출할 수 있고, 다른 프로그램에서도 사용이 가능하다.

  1. 중복된 코드의 제거 : 프로그램을 작성하다보면 동일한 기능을 하는 문장들이 반복되곤 한다.
    이러한 문장들을 묶어서 하나의 메소드로 작성하면, 전체 소스코드의 양이 줄어들고 유지보수가 용이해진다.

  2. 프로그램의 구조화 : 큰 규모의 프로그램에서는 문장들을 작업단위로 나눠서 여러 개의 메서드에 담아 프로그램의 구조를 단순화 시키는 것이 필수적이다.
    메서드를 사용하면, 프로그램의 전체 흐름이 한눈에 들어올 정도로 단순하게 구조화할 수 있다.

매개변수의 유효성 검사

메서드의 구현부를 작성할 때, 매개변수의 값이 적절한 것인지 확인해야한다.
'사용자가 알아서 적절한 값을 넘겨주겠지' 라는 생각은 절대로 가져서는 안된다.
타입만 맞으면 어떤 값도 매개변수를 통해 올 수 있기 때문에, 그에 대비한 코드를 작성해야 한다.

JVM의 메모리 구조

응용프로그램이 실행되면, JVM은 시스템으로부터 프로그램을 수행하는데 필요한 메모리를 할당받고 JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리한다.

  1. 메서드 영역 :
    프로그램 실행 중 어떤 클래스가 사용되면, JVM은 해당 클래스의 클래스파일(*.class)를 읽어서 분석하여 클래스에 대한 정보를 이곳에 저장한다. 클래스의 클래스변수도 이 영역에 함께 생성된다.

  2. 힙(heap) :
    인스턴스가 생성되는 공간.
    프로그램 실행 중 생성되는 인스턴스는 모두 이곳에 생성된다.
    즉, 인스턴스 변수들이 생성되는 공간이다.

  3. 호출 스택(call stack) :
    메서드의 작업에 필요한 메모리 공간을 제공한다. 메서드가 호출되면, 호출스택에 호출된 메서드를 위한 메모리가 할당되며, 이 메모리는 메서드가 작업을 수행하는 동안 지역변수(매개변수 포함)들과 연산의 중간결과를 저장하는데 사용된다.
    메서드가 작업을 마치면 할당되었던 메모리공간은 반환된다.
    각 메서드를 위한 메모리상의 작업공가은 서로 구별되며, 호출스택의 특징은 아래와 같다.

  • 메서드가 호출되면 수행에 필요한 만큼의 메모리를 스택에 할당받는다.
  • 메서드가 수행을 마치고나면 사용했던 메모리를 반환하고 스택에서 제거된다.
  • 호출스택의 제일 위에 있는 메서드가 현재 실행 중인 메서드다.
  • 아래에 있는 메서드가 바로 위의 메서드를 호출한 메서드다.

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

자바에서는 메서드를 호출할 때 매개변수로 지정한 값을 메서드의 매개변수에 복사해서 넘겨준다.
매개변수의 타입이 기본형(primitive type) 일 때는, 기본형의 값이 복사되지만, 참조형(reference type) 이면, 인스턴스의 주소가 복사된다.

이는 기본형이 매개변수로 넘어가면 마치 깊은 복사가 일어나고, 참조형은 얕은 복사가 일어난다고 이해하면 된다.
이를 통해 기본형 매개변수는 값을 읽기만(read only) 할 수 있고, 참조형 매개변수는 변수의 값을 읽고 변경할 수 있다.(read & write)

기본형 매개변수는 그 값의 복사된, 다른 객체가 넘겨지는 것이고,
참조형 매개변수는 인스턴스의 주소 자체가 넘어가 실제 값이 바뀔 수 있는 것이다.

이는 배열도 객체와 같이 참조변수를 통해 데이터가 저장된 공간에 접근한다는 점을 이용해, 임시적으로 간단한 처리를 할 때는 별도의 클래스를 선언하지 않고, 배열의 길이가 1인 배열을 생성해서 넘겨줘도 참조형 매개변수와 같은 결과를 얻을 수 있다.

재귀 호출

메서드의 내부에서 메서드 자신을 다시 호출하는 것을 '재귀호출(recursive call) 이라고 하고, 재귀호출을 하는 메서드를 '재귀 메서드' 라고 한다.

하지만 오로지 재귀호출뿐이라면, 무한히 자기 호출을 하기 때문에 무한 반복에 빠지게된다. 그러므로 이를 종료 시켜줄 조건문이 필수적으로 필요하다.

이 구조는 반복문과 비슷한데, 메서드를 호출하는 것은 매개변수의 복사와 종료 후 복귀할 주소 저장 등이 추가로 필요하기 때문에 반복문보다 수행시간이 오래 걸린다.

그렇다면 왜 굳이 재귀호출을 사용할까?
그 이유는 재귀호출이 주는 논리적 간결함. 직관성 때문이다.
몇겹의 반복문과 조건문으로 이루어진 코드가 재귀호출로 작성하면 보다 단순한 구조로 바뀔 수도 있다.(ex 팩토리얼)
아무리 효율적이라도 알아보기 힘들게 작성하는 것 보다 비효율적이라도 알아보기 쉽게 작성하는 것이 논리적 오류가 발생할 확률도 줄어들고 나중에 수정하기 쉽다.
재귀호출에 드는 비용과 재귀호출의 간결함이 주는 이득을 잘 저울질 해보자.

클래스 메서드(static 메서드)와 인스턴스 메서드

변수에서 그랬던 것과 같이, 메서드 앞에 static이 붙어있으면 클래스 메서드이고 그렇지 않으면 인스턴스 메서드이다.
클래스 메서드도 클래스 변수처럼 객체를 생성하지 않고 클래스이름.메서드이름(매개변수) 와 같은 식으로 호출이 가능하다.
반면 인스턴스 메서드는 반드시 객체를 생성해야만 호출할 수 있다.

그렇다면 클래스를 정의할 때, 어떤 경우에 static을 사용해서 클래스 메서드로 정의해야할까?
클래스는 '데이터(변수)와 데이터에 관련된 메서드의 집합' 이므로, 같은 클래스 내에 있는 메서드와 멤버변수는 아주 밀접한 관계에 있다.

인스턴스 메서드는 인스턴스 변수와 관련된 작업을 하는, 즉 메서드의 작업을 수행하는데 인스턴스 변수를 필요로 하는 메서드이다.
그런데 인스턴스 변수는 인스턴스를 생성해야만 만들어지므로 인스턴스 메서드 역시 인스턴스를 생성해야만 호출할 수 있는 것이다.

반면에 메서드 중에서 인스턴스와 관계없는(인스턴스 변수나 인스턴스 메서드를 사용하지 않는) 메서드를 클래스 메서드(static) 로 정의한다.

위 내용은 반드시 해야하는 것은 아니지만, 일반적으로 그렇게 한다.

정리
1. 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙힌다.
2. 클래스 메서드는 인스턴스를 생성하지 않아도 사용할 수 있다.
3. 클래스 메서드는 인스턴스 변수를 사용할수 없다.
4. 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static를 붙이는 것을 고려한다.

클래스 멤버와 인스턴스 멤버간의 참조와 호출

같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다.
단, 클래스멤버가 인스턴스 멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야 한다.
그 이유는 인스턴스 멤버가 존재하는 시점에 클래스 멤버는 항상 존재하지만, 클래스 멤버가 존재하는 시점에는 인스턴스 멤버가 존재하지 않을 수도 있기 때문이다.

class test {

    int iv; // 인스턴스 변수
    static int cv; // 클래스 변수
    
    void instanceMethod() {} // 인스턴스 메서드
    static void staticMethod() {} // static 메서드
    
    void instanceMethod2() { // 인스턴스 메서드
    	instanceMethod(); // 다른 인스턴스 메서드 호출 가능
        staticMethod(); // static 메서드 호출 가능
        
        System.out.println(iv); // 인스턴스 변수 호출 가능
        System.out.println(cv); // 클래스 변수 호출 가능
    }
    
    static void staticMethod2() { // static 메서드
    	intstanceMethod(); // 인스턴스 메서드 호출 불가 X
        staticMethod(); // static 메서드 호출 가능
        
        System.out.println(iv); // 인스턴스 변수 호출 불가 X
        System.out.println(cv); // 클래스 변수 호출 가능
    }

좋은 웹페이지 즐겨찾기