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

오버로딩

오버로딩과 오버라이딩.. 많이 들어봤을것이다.
오버로딩의 사전적 의미는 '과적하다'. 즉 많이 싣는 것을 뜻한다.
보통 하나의 메서드 이름하나의 기능을 구현해야하는데, 하나의 메서드 이름으로 여러 기능을 구현하는 것이다.

즉, 똑같은 이름의 메서드가 여러개 존재하여, 다른 기능을 구현하고 있는 것이다.

오버로딩의 조건

같은 이름의 메서드를 정의한다고 해서 무조건 오버로딩인 것은 아니다.
오버로딩이 성립하기 위해서는 다음과 같은 조건을 만족해야한다.

  1. 메서드의 이름이 같아야 한다.
  2. 매개변수의 개수 또는 타입이 달라야 한다.

메서드의 이름이 같아도, 매개변수가 달라서 서로 구별될 수 있는 것이다.
위 조건이 만족되지 않으면 중복 정의로 간주되 에러가 발생한다.
또, 반환 타입이나, 매개변수의 이름은 오버로딩 구현에 아무런 영향이 없다.

가변인자(varargs)와 오버로딩

기존에는 메서드의 매개변수 개수가 고정적이었으나 JDK1.5부터 동적으로 지정해 줄 수 있게 되었으며, 이 기능을 '가변인자(variable arguments)' 라고 한다.

가변인자는 '타입... 변수명'과 같은 형식으로 선언한다.

위와 같이 가변인자 외에도 매개변수가 더 있다면, 가변인자를 가장 마지막에 선언해야 한다.

가변인자를 사용한 메서드를 호출할 때는 인자의 개수를 가변적으로 할 수 있고, 심지어 인자가 없어되며, 배열도 가능하다.

이는 가변인자가 내부적으로 배열을 이용하기 때문이다.
그래서 가변인자가 선언된 메서드를 호출할 때마다 배열이 새로 생성되므로, 비효율적인 면도 있는 것이다.

생성자

생성자란?

생성자는 인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메서드' 이다.
인스턴스 변수의 초기화 작업에 주로 사용되며, 인스턴스 생성 시에 실행되어야 할 작업을 위해서도 사용된다.

생성자도 클래스 내에 선언되며, 구조도 메서드와 유사하지만 리턴값이 없다.
생성자의 조건은 다음과 같다.

  1. 생성자의 이름은 클래스의 이름과 같아야 한다.
  2. 생성자는 리턴 값이 없다.

하지만 생성자 자체가 인스턴스를 생성하는 것이 아니라, 연산자 new가 인스턴스를 생성하는 것이다.
생성자는 단순히 인스턴스 변수들의 초기화에 사용되는 조금 특별한 메서드일 뿐이다.

기본 생성자

모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다.
그래서 컴파일러가 '기본 생성자'를 제공하며, 이를 통해 생성자가 없어도 별 문제가 없었던 것이다.
컴파일 할 때, 소스파일의 클래스에 생성자가 하나도 없다면 컴파일러가 자동으로 클래스이름() {} 형태의 기본 생성자를 추가하여 컴파일 한다.

class Data1 {
	int value;
}

class Data2 {
	int value;
    
    Data2(int x) {
    	value = x;
    }
}

class ConstructorTest {
	public static void main(String[] args) {
    	Data1 d1 = new Data1();
        Data2 d2 = new Data2(); // 에러 발생
   	}
}

이 코드가 에러가 나는 이유는 무엇일까?
위에서 보았듯 Data1은 컴파일러가 기본 생성자를 자동으로 만들어준다.
하지만 코드에 생성자가 하나라도 있다면, 컴파일러는 기본 생성자를 만들지 않는다.

생성자에서 다른 생성자 호출하기 - this(), this

같은 클래스의 멤버들 간에 서로 호출할 수 있는 것처럼 생성자 간에도 서로 호출이 가능하다. 단, 다음의 조건을 만족해야한다.

  1. 생성자의 이름으로 클래스이름 대신 this를 사용한다.
  2. 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.

다음의 코드는 위 조건을 만족시키지 못해 에러가 발생한다.

Car(String color) {
	door = 5;
	Car(color, "auto", 4);
}

생성자 내에서 다른 생성자를 호출할 때 this를 사용하지 않았고, 첫 줄도 아니기 때문이다.

생성자를 첫 줄에서만 호출 가능한 이유는 생성자 내에서 초기화 작업 도중 다른 생성자를 호출하게 되면, 호출된 다른 생성자 내에서도 멤버변수들의 값을 초기화 할 것이므로 다른 생성자를 호출하기 이전의 초기화 작업이 무의미해질수 있기 때문이다.

같은 클래스 내의 생성자들은 일반적으로 서로 관계가 깊은 경우가 많아 서로 호출하도록 하여 유기적으로 연결해주면 좋은 코드를 얻을 수 있고, 유지보수도 수월해진다.

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

위 코드에서 Car("white"); 만 호출하여도, 유기적으로 기본 값으로 설정된 Car("white", "auto", 4); 의 인스턴스가 생성되는 것이다.

여기서 'this'란 참조변수로 인스턴스 자기 자신을 가리킨다.
참조변수를 통해 인스턴스의 멤버에 접근할 수 있는 것처럼, 'this'로 인스턴스변수에 접근할 수 있는 것이다.

하지만, 'this'를 사용할 수 있는 것은 인스턴스멤버 뿐이다. static 메서드는 인스턴스가 없는 시점에 호출될 수도 있으므로 사용할 수 없다.

변수의 초기화

멤버변수는 초기화를 하지 않아도 자동적으로 변수의 자료형에 맞는 기본값으로 초기화가 이루어지므로 초기화하지 않고 사용해도 되지만, 지역변수는 사용하기 전에 반드시 초기화해야 한다.

class InitTest {
	int x; // 인스턴스 변수
    int y = x; // 인스턴스 변수
    
    void method1() {
    	int i; // 지역 변수
        int j = i; // 지역변수, 에러
    }
}

x,y는 인스턴스 변수이기에 0으로 초기화되어 y=x; 가 가능한 것이다.
하지만 i,j는 지역 변수라 초기화되지 않아 j=i; 는 에러가 발생한다.

멤버변수와 배열의 초기화는 선택적이지만, 지역변수의 초기화는 필수이다.

멤버변수의 초기화는 여러 방법이 있다.

  1. 명시적 초기화
  2. 생성자
  3. 초기화 블럭
    1. 인스턴스 초기화 블럭 : 인스턴스 변수를 초기화
    2. 클래스 초기화 블럭 : 클래스 변수를 초기화

명시적 초기화

변수를 선언과 동시에 초기화하는 것을 명시적 초기화라고 한다.
가장 기본적이면서도 간단한 초기화 방법으로 여러 초기화 방법중에서 가장 우선적으로 고려되어야 한다.

int i = 3;
Car car1 = new Car();
char c = 'b';

명시적 초기화가 간단하고 명료하지만, 복잡한 초기화 작업이 필요할 때는 '초기화 블럭' 또는 생성자를 사용해야 한다.

초기화 블럭

초기화 블럭에는 '인스턴스 초기화 블럭'과 '클래스 초기화 블럭'이 있다.

초기화 블럭을 작성하려면, '인스턴스 초기화 블럭' 은 단순히 클래스 내에 블럭{} 을 만들고 그 안에 코드를 작성하면 된다.
'클래스 초기화 블럭' 은 인스턴스 초기화 블럭 앞에 단순히 static 을 붙히면 된다.

초기화 블럭 내에는 메서드와 같이 조건문, 반복문, 예외처리구문 등을 사용할 수 있으므로, 명시적 초기화만으로 부족한 경우 초기화 블럭을 사용한다.

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

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

인스턴스 변수의 초기화는 주로 생성자를 사용하고, 인스턴스 초기화 블럭은 모든 생성자에서 공통으로 수행되는 코드를 넣는데 사용한다.

Car() {
    count++;
    serialNo = count;
    color = "white";
    gearType = "auto";
}

Car(String color, String gearType) {
    count++;
    serialNo = count;
    this.color = color;
    this.gearType = gearType;
}

위를 아래와 같이 바꾼다

{
    count++;
    serialNo = count;
}

Car() {
    color = "white";
    gearType = "auto";
}

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

이처럼 코드의 중복을 제거하는 것이 코드의 신뢰성을 높혀주고, 오류 발생 가능성을 줄여준다. (재사용성 증가, 중복 제거)

멤버변수의 초기화 시기와 순서

좋은 웹페이지 즐겨찾기