6. 객체지향 프로그래밍 (1)

KOO HEESEUNG·2021년 9월 27일
0

Java의 정석

목록 보기
1/8
post-thumbnail

1. 객체지향 언어의 특징

객체지향 언어는 기존의 프로그래밍 언어에 객체지향 개념을 더한 것이다.

객체지향 언어는 코드의 재사용성이 높고, 유지보수가 용이하며, 코드의 중복을 제거하여 신뢰성 높은 프로그래밍을 할 수 있다는 장점이 있다.

객체지향 프로그래밍의 핵심 요소로는 ① 캡션 ② 상속 ③ 추상화 ④ 다형성 이 있다.


2. 클래스와 객체

1. 클래스 : 객체 = 설계도 : 제품

클래스는 객체를 만들기 위해 객체를 정의해놓은 것이다.

객체는 실제 존재하는 것으로, 그 목적은 당연하게도 객체를 사용하는 데 있다.

객체는 속성(변수)과 기능(메서드)로 구성되어 있다.

ex)

class Tv {
  // 속성(변수)
  String color;
  boolean power;
  int channel;
  
  // 기능(메서드)
  void power() { power = !power; }
  void channelUp() { channel++; }
  void channelDown() { channel--; }
}

2. 객체? 인스턴스?

객체와 인스턴스는 지칭하는 범위가 다를 뿐, 거의 동일한 개념이다. 객체는 모든 인스턴스를 일반적으로 부르는 용어이며, 인스턴스는 특정 클래스로 생성된 객체를 뜻하는 용어이다.

3. 객체의 생성과 사용

객체는 ① 참조변수 선언 ② 객체 생성(참조변수와 연결) ③ 객체 사용 의 과정을 거친다.

⭐️ 인스턴스는 참조변수를 통해서만 사용할 수 있으며, 참조변수의 타입은 인스턴스의 타입과 동일해야 한다.

Tv t; // ① 참조변수 선언 
t = new Tv(); // ② 객체 생성(참조변수와 연결) // Tv t = new Tv(); // 참조변수 선언과 객체 생성을 한번에 할 수도 있다.

// ③ 객체 사용
t.power(); 
t.channel = 7;
t.channelUp();

System.out.println("현재 채널 : " + t.channel); // 현재 채널 : 8

4. 객체배열

객체배열은 여러 객체를 다뤄야 할 때 사용한다.

Tv[] tvArr = new Tv[3];

tvArr[0] = new Tv();
tvArr[1] = new Tv();
tvArr[2] = new Tv();

객체배열은 참조변수 배열이라 할 수 있다. 다시 말해, 참조변수만 생성되었을 뿐, 객체가 생성되어 배열 각 요소에 저장된 것은 아니다. 따라서 객체배열을 생성한 후에는 반드시 배열 각 요소에 객체를 생성하여 저장하는 과정을 거쳐야 객체를 사용할 수 있다.


5. 클래스의 정의

클래스는 2.1 에서와 같이 설계도라고 정의할 수 있지만, 또한 데이터와 함수의 결합, 사용자 정의 타입으로 정의할 수 있다.

1. 데이터와 함수의 결합

image

변수는 단 하나의 데이터만 저장할 수 있고, 같은 종류의 데이터를 효율적으로 다루기 위해 배열의 개념이 등장했지만, 배열은 같은 종류의 데이터만 다룰 수 있다는 한계가 존재했다. 이에 종류에 상관없이 서로 관련 있는 데이터의 묶음인 구조체가 등장하였고, 여기에 함수까지 한데 묶어 다룰 수 있게 발전한 것이 클래스이다.

2. 사용자 정의 타입

사용자는 필요할 때 클래스를 사용하여 자신이 원하는 타입을 직접 만들 수 있다. 예를 들어 시간을 표현하기 위해 아래와 같이 시(hour), 분(minute), 초(second) 를 한데 묶어 새로운 타입을 정의할 수 있는 것이다.

class Time {
  int hour;
  int minute;
  int second;
}

이렇게 클래스를 이용하면 보다 객체지향적으로 효율적인 코드 작성이 가능하다.


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

class Example {
  int iv;				 // ① 인스턴스 변수
  static int cv; // ② 클래스 변수
  
  void method {
    int lv;			 // ③ 지역 변수
  }
}

클래스 내부는 크게 두 가지 영역으로 나뉜다. 클래스 영역과 메서드 영역. 클래스 영역은 말 그대로 클래스 내부를 의미하며, 메서드 영역은 메서드 선언부부터 메서드가 끝나는 지점까지를 의미한다.

클래스 영역에서는 선언문(변수 선언, 메서드 선언 등)만 가능하며, 선언 순서는 상관없으나, 관례적으로 변수를 먼저 선언한다.

인스턴스 변수는 클래스 안에 선언되고, 그 인스턴스가 생성될 때 만들어지며, 인스턴스별로 다른 값을 가질 수 있다. 반면, 클래스 변수는 앞에 static을 붙이는데, 클래스가 메모리에 올라갈 때 생성되며, 이 클래스로 인스턴스를 생성했을 때, 모든 인스턴스가 같은 값을 가진다.

다시 말해, 인스턴스 변수는 인스턴스의 생성이 필수적이지만, 클래스 변수는 인스턴스를 생성하지 않아도 그 값을 변경할 수 있으며, 인스턴스를 여러 개 생성해도 모두 같은 클래스 변수값을 가진다.

지역 변수는 메서드 내에서 선언되고, 메서드가 종료되면 지역변수도 함께 소멸된다.


7. 메서드

메서드의 특징

메서드는 특정 작업을 문장으로 묶어 이름지어준 것을 말한다. 반복적으로 수행되는 작업을 하나의 메서드로 만듦으로써 코드의 중복을 제거하여 코드가 보다 간결해지고, 재사용성이 높아지며, 유지보수에도 용이하다.

메서드는 하나의 기능만 수행하도록 작성하여야 한다. 여러 기능이 하나의 메서드에 들어있을 경우, 코드의 재사용성이 떨어진다.

메서드의 구성

메서드 = 선언부(반환타입, 메서드명, 매개변수) + 구현부(메서드 호출시 수행될 코드)

int add(int a, int b) { // 선언부
  int result = a + b; // 구현부(return 까지)
  return result;
}

메서드는 매개변수로 여러 개의 값(0~n개)을 받을 수 있지만, 결과값으로는 최대 1개의 값(0~1개) 밖에 받지 못한다. 만약 여러 개의 결과값을 받고 싶다면 배열을 활용해야 한다.

return문

메서드의 반환타입이 void 인 경우를 제외하고, 모든 메서드는 구현부에서 return 문을 통해 결과값을 반환해야 한다. 반환되는 결과값은 메서드의 반환타입과 일치하거나 해당 타입으로 자동 형변환이 가능해야 한다.

int max(int a, int b) {
  if (a > b)
    return a;
}

위와 같은 경우에는 return문을 작성했지만 에러가 발생한다. if 문을 사용한다면 참일 때와 거짓일 때의 결과를 모두 작성해주어야 하기 때문이다.

호출스택

호출스택의 특징

스택이란 밑이 막힌 상자와 같아, FILO(First In Last Out, 선입후출) 구조를 갖고 있다.

호출스택이란 메서드 작업에 필요한 메모리 공간을 제공하는 스택이다. 메서드가 호출되면 스택에 메서드가 올라가고, 해당 메서드가 작업을 마치면 스택에서 제거되며, 스택이 완전히 비워지면 프로그램이 종료된다.

호출스택의 맨 위에 있는 메서드가 현재 실행중인 메서드이며, 나머지는 대기 상태가 된다.

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

메서드의 매개변수가 기본형일 경우, 변수의 값을 읽기만 할 수 있을 뿐, 변경하지 못한다. 반면, 참조형 매개변수의 경우, 값이 저장된 주소를 제공하기 때문에 변수의 값을 읽고, 변경할 수 있다.

참조형 반환타입

메서드의 반환타입이 참조형일 경우, 객체의 주소가 반환된다.

static 메서드와 인스턴스 메서드

static 메서드(클래스 메서드)인스턴스 메서드
객체 생성 없이 호출 가능객체 생성 후 호출 가능
메서드 내에서 인스턴스 멤버 사용 불가메서드 내에서 static 메서드, 인스턴스 멤버 모두 사용 가능

static 메서드에서는 인스턴스 멤버를 사용할 수 없다. static 메서드는 객체 생성 없이도 호출이 가능하기 때문에 static 메서드를 호출했을 때 객체가 생성되어 있는지 알 수 없기 때문이다.

static을 언제 붙여야 할까?

  1. 모든 인스턴스의 공통 속성에 붙인다.
  2. 메서드 내에서 인스턴스 멤버를 사용하지 않을 때 붙인다.

8. 오버로딩

오버로딩 : 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것.

오버로딩의 조건

  1. 메서드명이 같아야 한다.
  2. 매개변수의 개수 또는 타입이 달라야 한다.
  3. 메서드 반환타입은 영향을 미치지 않는다.
// ①
int add(int a, int b) { return a + b; }
int add(int x, int y) { return x + y; }

// ②
int add(int a, int b) { return a + b; }
long add(int a, int b) { (long) return a + b; }

// ③
long add(int a, long b) { return a + b; }
long add(long a, int b) { return a + b; }

① : 오버로딩 X. 매개변수명만 다를 뿐, 중복정의된 메서드이다.

② : 오버로딩 X. 메서드 반환타입은 영향을 미치지 않는다.

③ : 오버로딩 O.


9. 생성자

생성자 : 인스턴스 초기화 메서드

생성자는 인스턴스를 "초기화"하는 것이지, 인스턴스를 "생성"하지 않는다.
인스턴스 생성은 연산자 new가 하는 것이다.

생성자의 조건

  1. 클래스의 이름과 같아야 한다.
  2. 리턴값이 없다.(void 안 붙임)

기본 생성자

기본 생성자 : 매개변수가 하나도 없는 생성자.

인스턴스에 생성자가 하나도 없을 경우, 컴파일러가 자동으로 추가해준다. 인스턴스에 생성자가 있을 경우에는 기본 생성자를 자동으로 추가하지 않기 때문에, 기본 생성자를 사용하기 위해서는 직접 기본 생성자를 넣어주어야 한다.

생성자 this()와 참조변수 this

class Car {
  String color;
  int door;
  
  Car() {
    this("white", 4);
    // color = "white";
    // door = 4;
  }
  
  Car(String color, int door) {
    this.color = color;
    this.door = door;
  }
}

(1) 생성자 this()

생성자 내에서 다른 생성자를 호출할 때 사용한다. 코드의 중복을 제거하기 위함.

반드시 첫 줄에 작성해야 한다.

(2) 참조변수 this

인스턴스 자신을 가리키는 참조변수이다. 생성자 this()와 완전히 다른 것이다.

인스턴스 변수와 지역 변수를 구분하기 위해 사용한다.

변수의 초기화

인스턴스 변수는 객체가 생성될 때 자동으로 초기화되지만, 지역변수는 반드시 직접 초기화해주어야 한다.

멤버변수의 초기화는 크게 3가지 단계로 나뉜다.

  1. 자동 초기화
  2. 간단한 초기화(명시적 초기화)
  3. 복잡한 초기화

(1) 자동 초기화

인스턴스가 생성될 때 인스턴스 변수는 자동으로 초기화된다.

(2) 명시적 초기화(간단한 초기화)

"=" 을 사용하여 초기화하는 것을 명시적 초기화라 한다. 참조형 변수의 경우, null이 아니라 그 객체의 주소로 초기화하는 것이 바람직하다.

(3) 초기화 블럭(복잡한 초기화)

인스턴스 변수는 { }, 클래스 변수는 static { } 를 사용하여 초기화하는 것이다. 여러 문장을 넣어 초기화해야 할 때 사용한다.

(4) 생성자(복잡한 초기화)


클래스 변수의 초기화 시점은 클래스가 메모리에 올라갈 때이고, 인스턴스 변수의 초기화 시점은 인스턴스가 생성되었을 때이다.

초기화의 순서는 다음과 같다.

  1. 클래스 변수 -> 인스턴스 변수
  2. 자동 초기화 -> 간단한 초기화 -> 복잡한 초기화

0개의 댓글