Backend/Java

[Java] 객체지향 프로그래밍 1(Object-oriented Programming 1) - TIL 221213

짜잉이 2022. 12. 13. 16:23

📖 자바의 정석 Chapter 06 참고


1. 객체지향언어

1.1 객체지향언어의 역사

  • 모의실험(simulation)을 위해 실제 세계와 유사한 가상 세계를 컴퓨터 속에 구현하려는 노력이 객체지향이론을 탄생시켰다.
  • 객체지향이론의 기본 개념은 '실제 세계는 사물(객체)로 이루어져 있으며, 발생하는 모든 사건들은 사물 간의 상호작용이다.'라는 것이다. 
    • 실제 사물의 속성과 기능을 분석한 다음, 데이터(변수)와 함수로 정의함으로써 실제 세계를 컴퓨터 속에 옮겨 놓은 것과 같은 가상세계를 구현하고 모의실험을 함으로써 많은 시간과 비용을 절약할 수 있었다.
  • 객체지향이론은 상속, 캡슐화, 추상화 개념을 중심으로 점차 구체적으로 발전되었다.
  • 프로그램 규모가 점점 커지고 사용자들의 요구가 빠르게 변화함에 따라 절차적 언어의 한계를 느끼고 객체지향언어를 이용한 개발방법론이 대안으로 떠올랐다.
  • 1995년 자바가 발표되고 1990년대 말 인터넷의 발전으로 객체지향언어는 프로그래밍 언어의 주류로 자리잡게 되었다.

1.2 객체지향언어

객체지향 언어의 주요특징

1. 코드의 재사용성높다.
    새로운 코드를 작성할 때 기존의 코드를 이용하여 쉽게 작성할 수 있다.
2. 코드의 관리가 용이하다.
     코드 간의 관계를 이용해서 적은 노력으로 쉽게 코드를 변경할 수 있다.
3. 신뢰성이 높은 프로그래밍을 가능하게 한다.
     제어자메서드를 이용해서 데이터를 보호하고 올바른 값을 유지하도록 하며, 코드의 중복을 제거하여 코드의 불일치로 인한 오동작을 방지할 수 있다.
  • 객체지향 언어의 가장 큰 장점 '코드의 재사용성이 높고 유지보수가 용이하다.'는 것이다. 이런 장점은 프로그램의 개발과 유지보수에 드는 시간과 비용을 획기적으로 개선하였다.
  • 상속, 다형성과 같은 객체지향개념을 학습할 때 재사용성유지보수 그리고 중복된 코드의 제거. 이 3가지 관점에서 보면 보다 쉽게 이해할 수 있다.
  • 객체지향개념에 너무 얽매여서 고민하기보다 일단 프로그램을 기능적으로 완성한 후, 어떻게 하면 보다 객체지향적으로 코드를 개선할 수 있을지 고민하여 점차 개선해나가는 것이 좋다.

2. 클래스와 객체

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

클래스의 정의: 클래스란 '객체를 정의해 놓은 것' 또는 '객체의 설계도 또는 '이다. (객체지향이론의 관점)
클래스의 용도: 클래스는 객체를 생성하는데 사용된다.
객체의 정의: 실제로 존재하는 것. 사물 또는 개념
객체의 용도: 객체가 가지고 있는 기능과 속성에 따라 다름

유형의 객체: 책상, 의자, 자동차, TV와 같은 사물
무형의 객체: 수학공식, 프로그램 에러와 같은 논리나 개념

프로그래밍에서의 객체: 클래스에 정의된 내용대로 메모리에 생성된 것
  • 클래스(제품 설계도) - 객체(제품)의 관계라고 할 수 있다. 
    e.g TV 설계도 - TV, 붕어빵 기계 - 붕어빵
  • 클래스는 단지 객체를 생성하는데 사용될 뿐, 객체 그 자체는 아니다. 프로그래밍에서는 먼저 클래스를 생성한 다음, 클래스로부터 객체를 생성하여 사용한다.
  • JDK(Java Development Kit)에서는 프로그래밍을 위해 많은 유용한 클래스(Java API)를 기본적으로 제공한다. 이 클래스들을 이용해 원하는 기능의 프로그램을 보다 쉽게 작성할 수 있다.

 

2.2 객체와 인스턴스

클래스  ---인스턴스화--->  인스턴스(객체)
  • 클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화(instantiate)라고 한다.
  • 어떤 클래스로부터 만들어진 객체를 그 클래스의 인스턴스(instance)라고 한다.
  • 인스턴스는 객체와 같은 의미이지만 문맥에 따라 구별하여 사용하는 것이 좋다. 
  • 객체는 모든 인스턴스를 대표하는 포괄적인 의미를 갖고 있고, 인스턴스는 어떤 클래스로부터 만들어진 것인지를 강조하는 보다 구체적인 의미를 갖고 있다.

 

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

  • 객체가 가지고 있는 속성과 기능을 그 객체의 멤버(구성원, member)라 한다.
  • 클래스란 객체를 정의한 것이므로 클래스에는 객체의 모든 속성과 기능이 정의되어 있다. 
  • 클래스로부터 객체를 생성하면 클래스에 정의된 속성과 기능을 가진 객체가 만들어진다.
  • 일반적으로 멤버변수를 먼저 선언하고 메서드를 선언한다. 
속성(property): 멤버변수(member variable), 특성(attribute), 필드(field), 상태(state)
기능(function): 메서드(method), 함수(function), 행위(behavior)

객체지향 프로그래밍에서는 속성기능을 각각 변수메서드로 표현한다.
e.g. 속성(변수) - int channel; // 채널
       기능(메서드) - void channelUp() { channel++; } // TV의 채널을 높이는 기능을 하는 메서드

 

2.4 인스턴스의 생성과 사용

클래스로부터 인스턴스를 생성하는 방법

클래스명 변수명;         // 클래스의 객체를 참조하기 위한 참조변수를 선언
변수명 = new 클래스명(); // 클래스의 객체를 생성 후, 객체의 주소를 참조변수에 저장

Tv t ;                  // Tv클래스 타입의 참조변수 t를 선언. 메모리에 참조변수 t를 위한 공간이 마련된다.
                        // Tv인스턴스를 참조하기 위한 변수 t를 선언
t = new TV();           // Tv인스턴스를 생성한 후, 생성된 Tv인스턴스의 주소를 t에 저장
                        // 연산자 new에 의해 Tv클래스의 인스턴스가 메모리의 빈 공간에 생성된다.
                        // 멤버변수는 각 자료형에 해당하는 기본값으로 초기화된다.
                        
t.channel = 7;          // Tv인스턴스의 멤버변수 channel의 값을 7로 한다.
t.channelDown();        // Tv인스턴스의 메서드 channelDown()을 호출한다.
  • 참조변수 t를 통해 Tv인스턴스접근할 수 있다. 참조변수 t가 Tv인스턴스를 '가리키고 있다' 또는 '참조하고 있다'.
  • 인스턴스의 멤버변수(속성)를 사용하려면 '참조변수.멤버변수'와 같이 하면 된다.
  • 인스턴스참조변수를 통해서만 다룰 수 있으며, 참조변수의 타입인스턴스의 타입일치해야 한다.
    e.g. 인스턴스와 참조변수의 관계는 TV리모콘(참조변수)을 사용하여 TV(인스턴스)를 다루는 것과 같다.
  • 객체 생성 == 인스턴스 생성
  • 같은 클래스로부터 생성되었을지라도 각 인스턴스의 속성(멤버변수)은 서로 다른 값을 유지할 수 있으며, 메서드의 내용은 모든 인스턴스에 대해 동일하다.
  • 참조변수에는 하나의 값(주소)만이 저장될 수 있으므로 둘 이상의 참조변수가 하나의 인스턴스를 가리키는(참조하는) 것은 가능하지만 하나의 참조변수로 여러 개의 인스턴스를 가리키는 것은 가능하지 않다.

 

2.5 객체 배열

Tv tv1, tv2, tv3; 
Tv[] tvArr = new Tv[3]; // 길이가 3인 Tv타입의 참조변수 배열. 참조변수 배열(객체 배열)을 생성.
                        // 각 요소(tvArr[0], tvArr[1], tvArr[2])는 참조변수의 기본값인 null로 자동 초기화 된다.
 
 
// 객체를 생성해서 배열의 각 요소에 저장
tvArr[0] = new Tv();
tvArr[1] = new Tv();
tvArr[2] = new Tv();

// 배열의 초기화 블럭을 사용하면 한 줄로 간단히 쓸 수 있다.
Tv[] tvArr = {new Tv(), new Tv(), new Tv()};


// 다뤄야할 객체의 수가 많을 때는 for문을 사용한다.
Tv[] tvArr = new Tv[100];
// Tv객체를 생성해서 Tv객체 배열의 각 요소에 저장
for(int i = 0; i < tvArr.length; i++) {
	tvArr[i] = new Tv();
}
  • 객체 배열은 참조변수들을 하나로 묶은 참조변수 배열이다. 객체 배열 안에 객체가 저장되는 것은 아니고, 객체의 주소가 저장된다.
  • 객체 배열도 같은 타입의 객체만 저장할 수 있다. 

 

2.6 클래스의 또 다른 정의

프로그래밍적인 관점에서 클래스의 정의와 의미

 

1. 클래스 - 데이터와 함수의 결합

데이터 저장형태의 발전 과정

1. 변수: 하나의 데이터를 저장할 수 있는 공간
2. 배열: 같은 종류여러 데이터하나의 집합으로 저장할 수 있는 공간
3. 구조체(structure): 서로 관련여러 데이터종류에 관계없이 하나의 집합으로 저장할 수 있는 공간
4. 클래스: 데이터와 함수의 결합(구조체+함수). 서로 관련된 변수들을 정의하고 이들에 대한 작업을 수행하는 함수들을 함께 정의한 것. 변수와 함수가 서로 유기적으로 연결돼 작업이 간단 명료해진다.
e.g. 자바에서는 문자열문자열을 다루는데 필요한 함수들을 함께 묶기 위해서 String이라는 클래스로 문자열을 다룬다.

2. 클래스 - 사용자정의 타입(user-defined type)

  • 사용자정의 타입(user-defined type): 프로그래밍 언어에서 제공하는 자료형(primitive type) 외에 프로그래머가 서로 관련된 변수들을 묶어서 하나의 타입으로 새로 추가하는 것.
  • 자바와 같은 객체지향언어에서는 클래스가 곧 사용자 정의 타입이다.
  • 기본형 개수는 8개. 참조형 개수는 정해져 있지 않다(프로그래머가 새로운 타입을 추가할 수 있기 때문).
비 객체지향적 코드 객체지향적 코드
int  hour1, hour2, hour3;
int  minute1, minute2, minute3;
float  second1, second2, second3;
Time  t1 = new Time();
Time  t2 = new Time();
Time  t3 = new Time();
=> 시, 분, 초를 하나로 묶는 사용자 정의 타입인 Time 클래스를 정의하여 사용.
int[ ]  hour = new int[3];
int[ ]  minute = new int[3];
float[ ]  second = new float[3];
Time[ ]  t = new Time[3];
t[0] = new Time();
t[1] = new Time();
t[2] = new Time();

 


3. 변수와 메서드

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

class Variables { /* 클래스 영역 */
    int iv;         // 인스턴스변수
    static int cv;  // 클래스변수(static변수, 공유변수)
    
    void method() { /* 메서드 영역 */
    	int lv = 0; // 지역변수
    }
}
  • iv와 cv는 클래스 영역에 선언되어있으므로 멤버변수이다.
  • 그 중 cv는 키워드 static과 함께 선언되어 있으므로 클래스 변수이며, iv는 인스턴스변수이다.
  • lv는 메서드인 method()의 내부, 즉 '메서드 영역'에 선언되어 있으므로 지역변수이다.
  • 멤버변수를 제외한 나머지 변수들은 모두 지역변수이다.
  • 멤버변수 중 static이 붙은 것은 클래스변수, 붙지 않은 것은 인스턴스변수이다.

 

변수의 종류와 특징

변수의 종류 선언위치 생성시기
클래스변수
(class variable)
클래스 영역 클래스메모리에 올라갈
인스턴스변수
(instance variable)
인스턴스가 생성되었을 때
지역변수
(local variable)
클래스 영역 이외의 영역
(메서드, 생성자, 초기화 블럭 내부)
변수 선언문이 수행되었을 때

1. 인스턴스변수(instance variable)

  • 클래스 영역에 선언된다.
  • 클래스의 인스턴스를 생성할 때 만들어진다. 그래서 인스턴스 변수의 값을 읽어오거나 저장하기 위해서는 먼저 인스턴스를 생성해야 한다.
  • 인스턴스는 독립적인 저장공간을 가지므로 서로 다른 값을 가질 수 있다.
  • 인스턴스마다 고유한 상태를 유지해야하는 속성은 인스턴스변수로 선언한다.

2. 클래스변수(class variable) (static변수, 공유변수)

  • 클래스 변수는 인스턴스변수 앞에 static을 붙여 선언한다.
  • 모든 인스턴스가 공통된 저장공간(변수)을 공유하게 된다.
  • 한 클래스의 모든 인스턴스들이 공통적인 값을 유지해야하는 속성은 클래스변수로 선언해야 한다.
  • 인스턴스를 생성하지 않고도 언제라도 바로 사용할 수 있다는 특징이 있다.
  • '클래스이름.클래스변수' 형식으로 사용한다.
  • 클래스가 메모리에 '로딩(loading)'될 때 생성되어 프로그램이 종료될 때까지 유지된다.
  • public을 앞에 붙이면 같은 프로그램 내에서 어디서나 접근할 수 있는 '전역변수(global variable)'의 성격을 갖는다.

3. 지역변수(local variable)

  • 메서드 내에 선언되어 메서드 내에서만 사용 가능하다.
  • 메서드가 종료되면 소멸되어 사용할 수 없다.

 

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

class Card {
    /* 인스턴스변수 */
    // 각 Card인스턴스는 자신만의 무늬와 숫자를 유지하고 있어야 함
    String kind; // 무늬
    int number;  // 숫자
    
    /* 클래스변수 */
    // 각 카드의 폭과 높이는 모든 인스턴스가 공통적으로 같은 값을 유지해야 함
    static int width = 100;  // 폭
    static int height = 250; // 높이
}


// 인스턴스변수
Card c1 = new Card();
c1.kind = "Heart";
c1.number = 7;

Card c2 = new Card();
c2.kind = "Spade";
c2.number = 4;


// 클래스변수
Card.width
Card.height
  • Card 클래스의 클래스변수(static변수) width, height는 Card 클래스의 인스턴스를 생성하지 않고 '클래스이름.클래스변수' 형태로 사용할 수 있다.
  • Card인스턴스인 c1과 c2는 '클래스변수인 width와 height를 공유하기 때문에, c1의 width와 height를 변경하면 c2의 width와 height값도 바뀐다. 
  • 인스턴스 변수는 인스턴스가 생성될 때마다 생성되므로 인스턴스마다 각각 다른 값을 유지할 수 있지만, 클래스 변수는 모든 인스턴스가 하나의 저장공간을 공유하므로 항상 공통된 값을 갖는다.

 

3.3 메서드

  • '메서드(method)': 특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것. 수학의 함수와 유사하다. 

메서드를 사용하는 이유

1. 높은 재사용성(reusability)

  • 한번 만들어 놓은 메서드는 몇 번이고 호출할 수 있고 다른 프로그램에서도 사용이 가능하다.
    e.g. Java API에서 제공하는 메서드들

2. 중복된 코드의 제거

  • 반복되는 문장들을 묶어 하나의 메서드로 작성해 메서드를 호출하는 한 문장으로 대체할 수 있다.

3. 프로그램의 구조화

  • 큰 규모의 프로그램에서는 문장들을 작업단위로 나눠서 여러 개의 메서드에 담아 프로그램의 구조를 단순화 시키는 것이 필수적이다.
  • main 메서드는 프로그램의 전체 흐름이 한눈에 들어올 정도로 단순하게 구조화 하는 것이 좋다. (main 메서드 안에 모든 문장을 넣는 식으로 작성 X)
  • 처음에 프로그램을 설계할 때 내용이 없는 메서드를 작업단위로 만들어 놓고, 하나씩 완성해가는 것도 프로그램을 구조화하는 좋은 방법이다.

 

3.4 메서드의 선언과 구현

메서드 정의하기

반환타입 메서드이름 (타입 변수명, 타입 변수명, ...) /* 선언부 */
{                                                  /* 구현부 */
	// 메서드 호출시 수행될 코드
}

int add(int a, int b) /* 선언부 */
{                     /* 구현부 */
    int result = a + b;
    return result; // 호출한 메서드로 결과를 반환한다.
}
  • 메서드는 크게 두 부분, '선언부(header, 머리)''구현부(body, 몸통)'로 이루어져 있다.

 

메서드 선언부(method declaration, method header)

// int: 반환타입(출력)
// add: 메서드이름
// (int a, int b): 매개변수 선언(입력)

int add(int a, int b) {
    int result = a + b;
    return result; // 결과를 반환
}
  • 매서드 선언부는 '메서드의 이름''매개변수 선언', 그리고 '반환타입'으로 구성되어 있다.

매개변수 선언(parameter declaration)

  • 매개변수는 메서드가 작업을 수행하는데 필요한 값들(입력)을 제공받기 위한 것이다.
  • 필요한 값의 개수만큼 변수를 선언한다. 변수 타입을 생략할 수 없다.
  • 입력해야 할 값의 개수가 많은 경우에는 배열이나 참조변수를 사용하면 된다.
  • 매개변수도 메서드 내에 선언된 것이기 때문에 '지역변수(local variable)'이다.

매서드의 이름(method name)

  • 메서드는 특정 작업을 수행하므로 'add'처럼 동사로 짓는 경우가 많다.
  • 이름만으로도 메서드의 기능을 쉽게 파악할 수 있도록 함축적이면서도 의미있는 이름을 지어야 한다.

반환타입(return type)

  • 메서드의 작업수행 결과(출력)인 '반환값(return value)'의 타입을 적는다.
  • 반환값이 없는 경우 반환타입으로 'void'를 적어야 한다.

매서드의 구현부(method body, 메서드 몸통)

  • 메서드의 선언부 다음에 오는 괄호{ }를 '메서드의 구현부'라고 한다.
  • 메서드의 구현부에 메서드를 호출했을 때 수행될 문장들을 넣는다.

return문

int add(int x, int y)
{
    int result = x + y;
    return result; // 작업결과(반환값)을 호출한 메서드로 반환한다.
                   // 변수 result의 타입(int)과 메서드 add의 반환타입(int)은 서로 일치해야 한다.
}
  • 메서드의 반환타입이 'void'가 아닌 경우, 구현부{ } 안에 'return 반환값;'이 반드시 포함되어 있어야 한다.
  • 작업을 수행한 결과인 반환값을 호출한 메서드로 전달할 때 반환값의 타입은 반환타입과 일치하거나 적어도 자동 형변환이 가능한 것이어야 한다.
  • 메서드로의 입력(매개변수)은 여러 개 일 수 있어도 출력(반환값)은 최대 하나만 허용한다.
    (=return문은 단 하나의 값만 반환할 수 있다.)

지역변수(local variable)

int add(int x, int y) {
	int result = x + y;
    return result;
}

// 메서드 add와 multiply에 각각 선언된 변수 x, y, result는
// 이름만 같을 뿐 서로 다른 변수이다.

int multiply(int x, int y) {
	int result = x + y;
    return result;
}
  • 지역변수(local variable): 메서드 내에 선언된 변수
  • 메서드 내에 선언된 변수들은 그 메서드 내에서만 사용할 수 있으므로 서로 다른 메서드라면 같은 이름의 변수를 선언해도 된다.
  • 매개변수도 메서드 내에 선언된 것으로 간주되므로 지역변수이다.

 

3.5 메서드의 호출

메서드이름(값1, 값2, ...); // 메서드를 호출하는 방법

print99danAll();         // void print99danAll()을 호출
int result = add(3, 5);  // int add(int x, int y)를 호출하고, 결과를 result에 저장
  • main메서드 프로그램 실행 시 OS에 의해 자동적으로 호출된다.

 

인자(argument)와 매개변수(parameter)

public static void main(String args[]) {
...
    // 3, 5는 인자(argument, 원본)
    int result = add(3, 5); // 메서드 호출
...
}

// x, y는 매개변수(parameter, 복사본)
int add(int x, int y) {
    int result = x + y;
    return result;
}
  • 메서드를 호출할 때 괄호()안에 지정해준 값들을 '인자(argument)' 또는 '인수'라고 한다.
  • 인자의 개수와 순서는 호출된 메서드에 선언된 매개변수와 일치해야 한다. 
  • 인자는 메서드가 호출되면서 매개변수에 대입되므로, 인자의 타입 매개변수의 타입과 일치하거나 자동형변환이 가능한 것이어야 한다.
  • 반환 타입이 void가 아닌 경우, 메서드가 작업을 수행하고 반환한 값을 대입연산자로 변수에 저장하는 것이 보통이지만 저장하지 않아도 문제가 되지 않는다.

메서드의 실행흐름

  • 같은 클래스 내의 메서드끼리는 참조변수를 사용하지 않고도 서로 호출이 가능하지만 static 메서드 같은 클래스 내의 인스턴스 메서드 호출할 수 없다.
  • 호출 시 지정된 값은 메서드의 매개변수로 복사(대입)된다. 매서드는 호출 시 넘겨받은 값으로 연산을 수행하고 그 결과를 반환하면서 종료된다.

 

3.6 return문

  • return문 현재 실행중인 메서드를 종료하고 호출한 메서드로 되돌아간다.
  • 반환타입이 void인 경우, 컴파일러가 메서드의 마지막에 'return;'을 자동적으로 추가해준다. (반환타입이 void인 경우 return문 생략 가능)

반환값(return value)

  • return문의 반환값(return value)으로 주로 변수가 오긴 하지만 항상 그런 것은 아니다. return문에 수식을 넣어 간략하게 작성할 수 있고 이 경우 반환값으로는 수식을 계산한 결과가 반환된다.

매개변수의 유효성 검사

  • 매서드의 구현부{ }를 작성할 때, 제일 먼저 해야 하는 일은 매개변수의 값이 적절한 것인지 확인하는 것이다.
  • 타입만 맞으면 어떤 값도 매개변수를 통해 넘어올 수 있기 때문에, 가능한 모든 경우의 수에 대해 고민하고 그에 대비한 코드를 작성해야 한다.
  • 적절하지 않은 값이 매개변수를 통해 넘어온다면 매개변수의 값을 보정하거나, 보정하는 것이 불가능하다면 return문을 사용해서 작업을 중단하고 호출한 메서드로 되돌아가야한다.
  • 메서드를 작성할 때는 매개변수의 유효성 검사하는 코드를 반드시 넣자.