프로그래밍 언어/Java

[JAVA] 03. 객체지향 프로그래밍 (1)

ssung.k 2020. 12. 27. 17:29

1. 클래스에서의 변수

변수는 클래스변수, 인스턴스변수, 지역변수 모두 세 종류가 있습니다.

변수의 종류를 결정짓는 중요한 요소는 변수의 선언된 위치입니다.

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

class Variables
{
  int iv; // 인스턴스변수
  static int cv; // 클래스변수
  
  void method()
  {
    int lv = 0; // 지역변수
  }
}
  • 인스턴스 변수
    • 생성 시기 : 인스턴스가 생성되었을 때
    • 인스턴스 변수는 인스턴스가 생성될 때마다 생성되므로 인스턴스마다 다른 값을 유지 할 수 있습니다.
    • 선언 위치 : 클래스 영역
  • 클래스 변수
    • 생성 시기 : 클래스가 메모리에 로딩될 때
      Variables.cv
      
    • 클래스 변수는 모든 인스턴스에 대해 범용적으로 적용되는 변수로 별도의 인스턴스 생성없이 접근이 가능합니다.
    • 선언 위치 : 클래스 영역
  • 지역 변수
    • 생성 시기 : 변수가 선언되었을 때
    • 선언 위치 : 메서드 내부

 

 

2. JVM의 메모리 구조

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

그 중 3가지 주요 영역(method area, call stack, heap)에 대해서 알아봅시다.

  • 메서드 영역(method area)
    • 이 때 그 클래스의 클래스 변수도 이 영역에 함께 생성됩니다.
    • 프로그램 실행 중 어떤 클래스가 사용되면, JVM은 해당 클래스의 클래스파일(*.class)을 읽어서 분석하여 클래스에 대한 정보(클래스 데이터)를 이곳에 저장합니다.
  • 호출스택(call stack)
    • 메서드가 호출되면 호출스택에 호출된 메서드를 위한 메모리가 할당되며 이 메모리는 메서드가 작업을 수행하는 동안 지역변수들과 연산의 중간결과 등을 저장하는데 사용합니다.
    • 그리고 메서드가 작업을 마치면 할당되었던 메모리공간은 반환되어 비어집니다.
    • 메서드의 작업에 필요한 메모리 공간을 제공합니다.
  • 힙(heap)
    • 인스턴스가 생성되는 공간입니다.

 

 

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

자바에서는 메서드를 호출 할 떄 매개변수로 지정된 값을 메서드의 매개변수에 복사해서 넘겨줍니다.

매개변수의 타입이 기본형일 때는 기본형 값이 복사되겠지만, 참조형이면 인스턴스의 주소가 복사됩니다.

그렇기 때문에 기본형은 값을 얻어오고 수정은 불가능하지만(read only) 참조형으로 선언하면 값을 얻어오고 수정(read & write)까지 가능합니다.

 

 

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

변수에서와 마찬가지로 메서드 앞에 static이 붙어 있으면 클래스 메서드이고 static이 없으면 인스턴스 메서드입니다.

클래스 메서드는 객체를 생성하지 않고도 클래스이름.메서드이름() 다음과 같이 호출이 가능하고 인스턴스 메서드는 반드시 객체를 생성해야 합니다.

 

클래스 설계시, 클래스 메서드와 인스턴스 메서드를 나누는 기준은 다음과 같습니다.

메서드가 인스턴스 변수나 인스턴스 메서드를 사용하고 있지 않다면 static을 붙여 클래스 메서드로 만들어줍니다.

 

아래 예시를 보며 클래스 메서드와 인스턴스 메서드 간의 호출 가능 여부를 확인해봅시다.

public class HelloWorld {
	void instanceMethod() {}
	static void classMethod() {}
	
	void instanceMethod2 () {
		instanceMethod();
		classMethod();
	}
	
	static void classMethod2() {
		instanceMethod(); // 에러
		classMethod();
	}
}

 

인스턴스 메서드에서는 다른 인스턴스 메서드나 클래스 메서드 호출이 자유롭습니다.

하지만 클래스 메서드 내에서는 클래스 메서드 호출은 가능하지만 인스턴스 메서드의 호출은 불가능합니다.

그 이유는 클래스 메서드가 존재하는 순간에는 인스턴스가 존재하지 않을 수도 있기 때문입니다.

 

 

5. 가변 인자 오버로딩

자바는 함수의 오버로딩을 지원합니다.

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

각 메서드 간의 구분은 매개변수로 하게 됩니다.

매개변수의 개수가 다르거나, 타입이 다르면 이를 통해 함수를 구분하게 됩니다.

 

JDK 1.5부터 메서드의 매개변수를 동적으로 지정해 줄 수 있습니다.

이 기능을 가변인자라고 하며, 타입... 변수명 다음과 같이 사용합니다.

가변인자는 매개변수들 가장 마지막에 위치해야 합니다.

public class HelloWorld {
	void function1(int a, String... args) {}
	void function2(String... args, int a) {} // 에러
}

 

아래는 가변인자 메서드를 사용하는 예제입니다.

가변인자에는 인자가 없을수도, 여러 개 일수도, 심지어 배열일 수도 있습니다.

public class HelloWorld {
	public static void main(String[] args) {
		function1(0);
		// 0
		function1(2, "a", "b");
		/*
		2
		a
		b
		*/
		function1(3, new String[] {"a", "b", "c"});
		/*
		3
		a
		b
		c
		*/
	}
	
	static void function1(int a, String... args) {
		System.out.println(a);
		for (String s : args) {
			System.out.println(s);
		}
	}
}

 

가변인자는 내부적으로 배열을 이용합니다.

따라서 가변인자가 선언된 메서드를 호출할 때마다 배열이 새로 생성됩니다.

가변인자가 편리하지만 이러한 비효율 때문에 필요한 경우에만 사용하도록 해야합니다.

 

6. 생성자(Constructor)

생성자는 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드입니다.

생성자는 메서드와 유사하게 클래스 내에 선언되지만 클래스이름과 같아야하며 리턴 값이 없어야 합니다.

생성자도 오버로딩이 가능하므로 하나의 클래스에 여러 개의 생성자가 존재할 수 있습니다.

 

기본 생성자

클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 합니다.

하지만 위의 예시에서는 한 번도 생성자를 정의한 적이 없는데 프로그램이 문제없이 동작하였습니다.

그 이유는 바로 기본 생성자 때문입니다.

컴파일 시, 소스파일에 클래스에 생성자가 하나도 정의되어 있지 않은 경우 컴파일러는 자동적으로 같은 내용의 기본 생성자를 추가하여 컴파일 합니다.

기본 생성자는 다음과 같이 매개변수도 없고 아무런 내용도 없습니다.

클래스이름() {}

 

생성자에서 다른 생성자 호출 - this

같은 클래스 멤버들 간에 서로 호출할 수 있는 것처럼 생성자 간에도 서로 호출이 가능합니다.

하지만 이 때는 아래의 두 조건을 만족해야 합니다.

  • 생성자의 이름으로 클래스이름 대신, this
  • 다른 생성자를 호출 할 때 첫 줄에서 호출

 

다른 생성자를 호출하는 예시입니다.

class Car{
	String color;
	String gearType;
	int door;
	
	Car() {
		this("white", "auto", 4);
	}

	Car(String color, String gearType, int door) {
		super();
		this.color = color;
		this.gearType = gearType;
		this.door = door;
	}
}

 

위 예시에서 인스턴스 변수를 초기화할 때도 this 키워드를 사용하였습니다.

그 이유는 매개변수의 이름과 인스턴스 변수의 이름이 같기 때문에 이를 구분해주기 위해 사용합니다.

여기서 this는 참조변수로 인스턴스 자신을 가르킵니다.

 

생성자를 이용한 인스턴스 복사

현재 인스턴스와 같은 상태를 갖는 인스턴스를 하나 만들기 위해 생성자를 사용할 수 있습니다.

이 경우 매개변수로 해당 클래스의 참조변수를 넘겨주게 됩니다.

아래 예시를 살펴봅시다.

class Car{
	String color;
	String gearType;
	int door;
	
	Car(Car c){
		this(c.color, c.gearType, c.door);
	}

	Car(String color, String gearType, int door) {
		super();
		this.color = color;
		this.gearType = gearType;
		this.door = door;
	}
}

 

6~9줄에 참조변수를 받은 후 다른 생성자를 이용하여 새로운 객체를 만들어줍니다.

 

 

7. 변수의 초기화

멤버변수(클래스변수와 인스턴스변수)는 초기화를 하지 않아도 자동적으로 변수의 자료형에 맞는 기본값으로 초기화가 이루어지고 지역변수는 초기화가 이뤄지지 않습니다.

따라서 지역변수는 사용하기 전에 반드시 초기화 해야합니다.

멤버변수는 자료형에 따라 초기화 되는 기본값이 다른데 각 값들은 아래와 같습니다.

자료형 기본값
boolean false
char '\u0000'
byte, short, int 0
long 0L
float 0.0f
double 0.0
참조형 변수 null

 

멤버변수를 초기화하는 방법은 크게 3가지로 나눌 수 있습니다.

각각에 대해서 알아봅시다.

  • 명시적 초기화
  • 생성자
  • 초기화 블럭

 

명시적 초기화

변수를 선엄함과 동시에 초기화하는 것을 명시적 초기화라고 합니다.

가장 기본적이고 간단한 방법입니다.

class Car{
	String color = "white";
  // 생략
}

 

 

초기화 블럭

초기화 블럭에는 클래스 초기화 블럭과 인스턴스 초기화 블럭 두 가지 종류가 있습니다.

이름 그대로 클래스 초기화 블럭은 클래스 변수를 초기화하는데, 인스턴스 초기화 블럭은 인스턴스 변수를 초기화하는데 사용됩니다.

초기화 작업이 복잡하여 명시적 초기화 만으로 부족한 경우 사용합니다.

class Car{
	static { /* 클래스 초기화 블럭 */ }
	{ /* 인스턴 초기화 블럭 */ }
}

 

클래스 초기화 블럭은 클래스가 메모리에 처음 로딩될 때 한 번만 수행되며, 인스턴스 초기화 블럭은 생성자와 같이 인스턴스를 생성할 때 마다 수행됩니다.

그리고 생성자보다 인스턴스 초기화 블럭이 먼저 수행됩니다.

 

초기화 순서를 정리하면

클래스 변수는 명시적 초기화 -> 클래스 초기화 블럭 순으로,

인스턴스 변수는 명시적 초기화 -> 인스턴스 초기화 블럭 -> 생성자 순으로 초기화됩니다.