[java] 9. 오버로딩과생성자


오버로딩



메서드도 변수와 마찬가지로 같은 클래스 내에서 서로 구별될 수 있어야 하기 때문에 각기 다른 이름을 가져야합니다.

그러나 자바에서는 한 클래스 내에 이미 사용하려는 이름과 같은 이름을 가진 메서드가 있더라도

매개변수의 개수 또는 타입이 다르면, 같은 이름을 사용해서 메서드를 정의할 수 있습니다.

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



오버로딩의 조건



같은 이름의 메서드를 정의한다고 해서 무조건 오버로딩이 되는 것은 아닙니다.

오버로딩이 성립하기 위해서는 다음 조건을 만족해야합니다.


1. 메서드 이름이 같아야 한다.


2. 매개변수의 개수 또는 타입이 달라야 한다.



비록 메서드의 이름이 같다 하더라도 매개변수가 다르면 서로 구별될 수 있기 때문에 오버로딩이 가능한 것입니다.

반환타입은 오버로딩을 구현하는데 아무런 영향을 주지 못합니다.



오버로딩의 예



오버로딩의 예로 가장 대표적인 것은 println 메서드입니다.

우리는 println의 매개변수 값으로 정수, 실수, 문자, 문자열을 사용했는데,

따로 형식을 지정해주지 않아도 화면에 출력하는데 문제가 없었습니다.

실제로 println 메서드는 10개 오버로딩이 되어있습니다.


void println()

void println(boolean x)

void println(char x)

void println(char[] x)

void println(double x)

void println(float x)

void println(String x)

...

등등



println 메서드를 호출할 때 매개변수로 넘겨주는 값의 타입에 따라서

위의 오버로딩된 메서드들 중의 하나가 선택되어 실행되는 것입니다.

몇 가지 예를 들어 오버로딩에 대해 자세히 알아보겠습니다.


//예제 1

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

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



위의 두 메서드는 매개변수의 이름만 다를 뿐 매개변수의 타입이 같기 때문에 오버로딩이 성립하지 않습니다.


//예제 2

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

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



위의 경우는 리턴타입만 다른 경우인데, 매개변수의 타입과 개수가 일치하기에 오버로딩이 성립하지 않습니다.


//예제 3

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

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



두 메서드 모두 int 형과 long 형 매개변수가 하나씩 선언되어 있지만,

서로 순서가 다른 경우이기에 오버로딩이 성립합니다.

하지만, add(3,3)과 같이는 호출할 수 없습니다.


//예제 4

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

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

long add(int[] a){
	long result=0;
	
	for(int i=0;i<a.length; i++){
		result+=a[i];
	}
	return result;
}



위의 메서드들은 모두 바르게 오버로딩되어 있습니다.

그리고 모두 덧셈을 해서 반환하는 기능을 합니다.

이처럼 같은 일을 하지만 매개변수를 달리해야하는 경우에,

이름은 같고 매개변수를 다르게 하여 오버로딩을 구현합니다.

다음은 오버로딩에 대한 예제입니다.


image



위의 코드에서 신기한 점은 println에 의해 화면에 출력되기 전에 add 메소드가 먼저 호출되어 출력되는 점입니다.



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



기존에는 메서드의 매개변수 개수가 고정적이었으나 JDK 1.5부터 동적으로 지정해 줄 수 있게 되었으며,

이 기능을 가변인자라고 합니다.

가변인자는 다음과 같은 형식으로 선언합니다.

타입... 변수명

ex)String concatenate(String... str)



주의 할 것이 가변인자 말고도 다른 매개변수가 있다면,

가변인자는 항상 마지막 매개변수이어야 합니다.


public PrintStream printf(String format,Object... args){...} //OK


public PrintStream printf(Object... args, String format){...} //컴파일 에러



만약 여러 문자열을 하나로 결합하여 반환하는 concatenate 메서드를 작성한다면,

아래와 같이 매개변수의 개수를 다르게 해서 여러 개의 메서드를 작성해야합니다.


String concatenate(String s1,String s2){...}

String concatenate(String s1,String s2,String s3){...}

String concatenate(String s1,String s2,String s3,String s4){...}



이럴 때, 가변인자를 사용하면 메서드 하나로 간단히 대체할 수 있습니다.


String concatenate(String... str){...}



이 메서드를 호출할 때는 아래와 같이 인자의 개수를 가변적으로 할 수 있습니다.

인자가 없어도 되고, 배열도 인자가 될 수 있습니다.


System.out.println(concatenate()); //인자가 없음

System.out.println(concatenate("a")); //인자가 하나

System.out.prinltn(concatenate("a","b")); //인자가 둘

System.out.println(concatenate(new String[]{"A","B"})); //배열도 가능



다음과 같이 매개변수를 배열로 할 때는 반드시 인자를 지정해줘야하기에 인자를 생략할 수 없습니다.


String concatenate(String[] str){...}

String result=concatenate(new String[0]); //인자를 배열로 지정

String result=concatenate(null); //인자로 null을 지정

String result=concatenate(); //에러. 인자가 필요함



다음은 가변인자를 이용한 예제입니다.


image



위의 주석을 풀면 컴파일 오류가 발생하는데, 이는 두 오버로딩된 메서드가 구분되지 않아서

발생하는 것입니다.

가능하면 가변인자를 사용한 메서드는 오버로딩하지 않는 것이 좋습니다.



생성자



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

따라서 인스턴스 변수의 초기화 작업에 주로 사용되며,

인스턴스 생성 시에 실행되어야 할 작업을 위해서도 사용됩니다.

생성자 역시 메서드처럼 클래스 내에 선언되며, 구조도 메서드와 유사하지만 리턴 값이 없다는 점이 다릅니다.

그렇다고 해서 생성자 앞에 리턴값이 없음을 뜻하는 void를 사용하지 않고,

단지 아무 것도 적지 않습니다.


<생성자의 조건>

1. 생성자의 이름은 클래스의 이름과 같아야 한다.

2. 생성자는 리턴 값이 없다.



생성자는 다음과 같이 정의합니다.

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


class Card{
	Card(){ //매개변수가 없는 생성자
		...
	}
	
	Card(String k,int num){ //매개변수가 있는 생성자
		...
	}
}



이름이 생성자라고 해서 연산자 new가 인스턴스를 생성하는 것이지

생성자가 인스턴스를 생성하는 것이 아닙니다.

생성자는 생성될 때 호출되는 메서드라고 생각하시면 됩니다.

Card 클래스의 인스턴스를 생성하는 코드를 예로 들어, 수행되는 과정을 단계별로 나누어보면 다음과 같습니다.


Card c=new Card();

1. 연산자 new에 의해서 메모리(heap)에 Card 클래스의 인스턴스가 생성된다.

2. 생성자 Card()가 호출되어 수행된다.

3. 연산자 new의 결과로, 생성된 Card 인스턴스의 주소가 반환되어 참조변수 c에 저장된다.



기본 생성자



지금까지는 생성자를 모르고도 프로그래밍을 해왔지만,

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

그러나 지금까지 클래스에 생성자를 정의하지 않고도 인스턴스를 생성할 수 있었던 이유는

컴파일러가 제공하는 기본 생성자 덕분이였습니다.

컴파일 할때, 소스파일(*.java)의 클래스에 생성자가 하나도 정의되지 않은 경우

컴파일러는 자동적으로 아래와 같은 내용의 기본 생성자를 추가하여 컴파일합니다.


Class Card{}

->Class Card{
	Card(){
		//컴파일러가 자동적으로 기본 생성자 추가
	}
}



하지만 다음과 같이 생성자가 하나라도 정의된 경우


Class Card{
	Card(int x){
		...
	}
}



컴파일러가 자동적으로 기본생성자를 추가시키지 않습니다.

따라서 다음과 같이 인스턴스를 생성하는 것이 불가능합니다.


Card c=new Card(); //에러 

Card c=new Card(3); //OK



매개변수가 있는 생성자



생성자도 메서드처럼 매개변수를 선언하여 호출 시 값을 넘겨받아서

인스턴스의 초기화 작업에 사용할 수 있습니다.

아래의 코드는 자동차를 클래스로 정의한 것인데,

단순히 color, gearType, door 세 개의 인스턴스변수와 두 개의 생성자만을 가지고 있습니다.


class Car{
	String color; //색상
	String gearType; //변속기 종류
	int door; //문의 개수
	
	Car(){} //생성자
	
	Car(String c,String g, int d){
		color=c;
		gearType=g;
		door=d;
	}
}



생성자 Car()를 이용하면 일일이 인스턴스 변수들을 따로 초기화 해줘야합니다.


Car c=new Car();

c.color="white";

c.gearType="auto";

c.door=4;



생성자 Car(String c, String g, int d)를 이용하면 생성과 동시에 초기화 합니다.


Car c=new Car("white","auto",4);



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



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

단, 두 조건을 만족시켜야합니다.


생성자의 이름으로 클래스이름 대신 this를 사용한다.

한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.



따라서 다음과 같은 코드는 에러를 발생시킵니다.


Car(String color){
	door=5;
	Car(color,"auto",4); 
	
	//에러 1. 생성자의 두번째 줄에서 다른 생성
	//에러 2. this(color,"auto",4) 라고 해야함
}



다음은 생성자에서 다른 생성자를 호출하는 예제입니다.


image



그리고 this는 참조변수로 인스턴스 자신을 가리키는데,

자기 자신의 인스턴스 멤버에도 접근이 가능합니다.

이를 이용하여 매개변수를 받아 초기화할 때 변수명을 달리하지 않고,

같게 하여 더 좋은 코드를 얻을 수도 있습니다.


//this 사용 전

//매개변수와 멤버변수의 이름을 달리함

Car(String c,String g,int d){
	color=c;
	gearType=g;
	door=d;
}




//this 사용 

//매개변수와 멤버변수의 이름을 같게함

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



여기서 조심해야 할 것은 this를 사용할 수 있는 것은 인스턴스멤버뿐이라는 것입니다.

static 메서드에서는 인스턴스 멤버들을 사용할 수 없는 것처럼, this 역시

사용할 수 없습니다.

왜냐하면, static 메서드는 인스턴스를 생성하지 않고도 호출될 수 있으므로

static메서드가 호출된 시점에 인스턴스가 존재하지 않을 수도 있기 때문입니다.


this 는 인스턴스 자신을 가리키는 참조변수로 인스턴스의 주소가 저장되어 있다.



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



생성자를 이용해서 같은 클래스의 인스턴스의 값은 같게 하지만,

주소는 다르게 복사를 할 수 있습니다.

즉, 서로 독립적인 공간을 가지면서 값만 같게끔 할 수 있습니다.

예제를 보시죠~


image




image



변수의 초기화



변수를 선언하고 처음으로 값을 저장하는 것을 변수의 초기화라고 합니다.

멤버변수는 초기화를 하지 않아도 자동적으로 변수의 자료형에 맞는 기본값으로 초기화가 이루어지므로

초기화하지 않고 사용해도 되지만, 지역변수는 사용하기 전에 반드시 초기화해야합니다.


class InitTest{
	int x; //인스턴스 변수
	int y=x; //인스턴스 변수 
	
	void method1(){
		int i; //지역 변수
		int j=i; // 에러. 지역변수를 초기화하지 않고 사용
	}
}




멤버변수(클래스변수와 인스턴스변수)와 배열의 초기화는 선택적이지만,

지역변수의 초기화는 필수적이다.



멤버변수의 초기화는 지역변수와 달리 여러 가지 방법이 있는데 다음과 같습니다.


1.명시적 초기화

2.생성자

3.초기화 블럭

	-인스턴스 초기화 블럭: 인스턴스변수를 초기화 하는데 사용

	-클래스 초기화 블럭 : 클래스변수를 초기화 하는데 사용




<명시적 초기화>


변수를 선언과 동시에 초기화하는 것을 명시적 초기화라고 한다.


가장 기본적이면서도 간단한 초기화 방법이므로 여러 초기화 방법 중에서 가장 우선적으로 고려되어야 한다.


ex) class Car{
	int door=4; //기본형 변수의 초기화
	Engine e=new Engine(); //참조형 변수의 초기화
}



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

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

클래스 초기화 블럭은 클래스변수의 초기화에 사용되고, 인스턴스 초기화 블럭은 인스턴스 변수의 초기화에 사용됩니다.

초기화블록은 다음과 같이 작성합니다.


//인스턴스 초기화 블럭


class Ex{
	{
		//초기화 내용
	}
}


//클래스 초기화 블럭


class Ex{
	static{
		//초기화 내용
	}
}



초기화 작업이 복잡하여 명시적 초기화만으로는 부족한 경우, 즉 반복문, 조건문 등을 사용하여

초기화 작업을 할 경우 초기화 블럭을 이용합니다.

인스턴스 변수의 초기화는 주로 생성자를 사용하고,

인스턴스 초기화 블럭은 모든 생성자에서 공통으로 수행돼야 하는 코드를 넣는데 사용합니다.

예를 들어, 다음과 같이 생성자 마다 공통으로 수행돼야하는 코드가 있을 때,


Car(){
	count++; //중복
	serialNo=count; //중복
	color="White";
	gearType="Auto"; 
}


Car(String color, String gearType){
	count++; //중복
	serialNo=count; //중복
	color="White";
	gearType="Auot";
}



다음과 같이 초기화블록을 사용할 수 있습니다.


{ //초기화 블록
	count++;
	serialNo=count; 
}

Car(){
	color="White";
	gearType="Auto";
}

Car(String color, String gearType){
	color="White";
	gearType="Auot";
}



이처럼 코드의 중복을 제거하는 것은 코드의 신뢰성을 높여주고, 오류의 발생가능성을 줄여 준다는 장점이있습니다.



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



초기화가 수행되는 시기와 순서는 다음과 같습니다.


클래스변수의 초기화 시점 : 클래스가 처음 로딩될 때 단 한번 초기화 된다.


인스턴스 변수의 초기화 시점: 인스턴스가 생성될 때마다 각 인스턴스별로 초기화가 이루어진다.



클래스변수의 초기화 순서: 기본값->명시적초기화->클래스 초기화 블럭

인스턴스변수의 초기화 순서: 기본값->명시적초기화->인스턴스 초기화 블럭->생성자




다음은 초기화 순서에 관한 예제입니다.


image