2023. 11. 27. 18:32ㆍ프로그래밍 언어/Java
1. 오버로딩
한 클래스 내에 이미 사용하는 이름과 같은 이름을 가진 메서드가 있더라도 매개변수의 개수, 타입이 다르면 같은 이름으로 메서드를 정의할 수 있다. 이를 오버로딩이라 한다.
오버로딩이 성립되기 위해선 다음의 조건을 만족해야 한다.
- 메서드 이름이 같아야 한다.
- 매개변수의 개수 또는 타입이 달라야 한다.
반환 타입은 오버로딩을 구현하는데 아무런 영향을 주지 않는다.
오버로딩이 되는 예시와 오버로딩에 실패한 예시를 잘 봐야 한다.
int add(int a, int b);
int add(int x, int y);
이 두 메서드는 매개변수의 이름만 다를 뿐 두 메서드를 전혀 구분할 수 없다. 따라서 오버로딩에 실패한 예시이다.
int add(int a, long b);
int add(long a, int b);
그렇다면 이 두 메서드는 어떨까? 놀랍게도 오버로딩으로 간주한다. 예를 들어 add(3L, 3)과 같이 호출하면 두 번째 메서드가 호출된다. 이 경우에는 add(3, 3)과 같이 호출할 수 없다. 오버로딩의 핵심은 모호하지만 않으면 된다는 것이다.
그렇다면 오버로딩은 왜 쓸까? 만약 메서드도 변수처럼 이름만으로 구별된다면, println메서드들은 매개변수의 종류에 따라 아주 많은 종류의 메서드가 생기고, 사용자는 모든 메서드를 기억해야 할 것이다. 하지만 오버로딩을 통해 여러 메서드들이 println이라는 하나의 이름으로 정의되어 간편하게 사용할 수 있다.
2. 가변인자와 오버로딩
자바에서는 메서드의 매개변수 개수를 동적으로 지정해 줄 수 있다. 이 기능을 가변인자라 한다. 가변인자는 '타입... 변수명'과 같은 형식으로 선언하며, printf()도 가변인자로 정의되어 있다.
public PrintStream printf(String format, Object... args) {...}
위와 같이 가변인자를 매개변수 중에서 가장 마지막에 선언해야 한다.
이 가변인자를 오버로딩하는 상황이 꽤 많을 것이다. 이때 주의해야 할 점이 있다.
public class AmbiguousOverloadingExample {
public static void printValues(String delim, String... args) {
System.out.print("Printing values with delimiter '" + delim + "': ");
for (String arg : args) {
System.out.print(arg + delim);
}
System.out.println();
}
public static void printValues(String... args) {
System.out.print("Printing values: ");
for (String arg : args) {
System.out.print(arg + " ");
}
System.out.println();
}
public static void main(String[] args) {
printValues(",", "a", "b", "c"); // Error: The method printValues is ambiguous
}
}
이처럼 concatenate 메서드는 서로 구분되지 않아서 오류가 발생한다. 오버로딩을 구현할 땐 항상 모호한지 아닌지 잘 체크하자.
3. 생성자
생성자는 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드이다. 클래스 생성자, static 생성자 이런 건 존재하지 않고 오직 인스턴스에 대해서만 존재한다. 생성자 역시 메서드처럼 클래스 내에 선언되며, 구조도 메서드와 유사하지만 리턴값이 없다는 점이 다르다. 생성자의 조건은 다음과 같다.
- 생성자의 이름은 클래스의 이름과 같아야 한다.
- 생성자는 리턴 값이 없다.
생성자는 오버로딩이 가능하며 주로 아래와 같이 사용한다.
class Card{
Card(){
...
}
Card(String k, int num){
...
}
}
생성자라는 이름 때문에 헷갈릴 수 있는데, 생성자는 인스턴스 변수들의 초기화에 사용되는 메서드일 뿐 실제 인스턴스 생성은 연산자 new에 의해 이루어진다.
지금까지는 생성자를 모르고도 프로그래밍을 해 왔지만, 사실 모든 클래스에는 반드시 하나 이상의 생성자가 있어야 한다. 지금까지 문제가 없었던 이유는 기본 생성자 덕분이다. 기본 생성자는 매개변수도 없고 아무런 내용도 없는 생성자로, 클래스에 생성자가 없다면 컴파일러가 자동으로 추가해 준다. 여기서 주의할 점이 하나 있다.
public class ConstructorErrorExample {
private int value;
// 명시적으로 다른 생성자를 정의한 경우
public ConstructorErrorExample(int value) {
this.value = value;
}
public static void main(String[] args) {
// Error: The constructor ConstructorErrorExample() is undefined
ConstructorErrorExample instance = new ConstructorErrorExample();
}
}
위 예시처럼 매개변수가 있는 생성자를 정의했지만, 그 생성자를 호출하지 않았다. 이렇게 되면 이미 생성자가 있으므로 기본 생성자가 추가되지 않고 에러가 발생한다.
보통 생성자는 아래와 같이 사용한다.
public class ConstructorExample {
private int value;
private String name;
// 기본 생성자
public ConstructorExample() {
this(0, "default");
}
// 인자를 받는 생성자
public ConstructorExample(int value, String name) {
this.value = value;
this.name = name;
}
public void display() {
System.out.println("Value: " + value + ", Name: " + name);
}
public static void main(String[] args) {
// 기본 생성자 호출
ConstructorExample defaultInstance = new ConstructorExample();
defaultInstance.display();
// 인자를 받는 생성자 호출
ConstructorExample parameterizedInstance = new ConstructorExample(42, "John");
parameterizedInstance.display();
}
}
여기서 this와 this()는 뭘까? 알아보자.
4. 생성자에서 다른 생성자 호출하기 - this(), this
생성자 간에 서로 호출이 가능하다. 단, 다음의 두 조건을 만족시켜야 한다.
- 생성자 이름으로 클래스이름 대신 this()를 사용한다.
- 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 가능하다.
두 조건을 만족시키기 못한 예시를 보자.
Car(String color){
door = 5;
Car(color, "auto", 4); //에러1. 생성자의 두 번째 줄에서 다른 생성자를 호출함
//에러2. this()로 호출해야 함
}
this()는 다른 생성자 호출을 의미한다는 걸 알았다. 하지만 this는 this()와 전혀 다른 것이다. this는 참조변수로 인스턴스 자신을 가리킨다. 따라서 static메서드에서는 this를 이용할 수 없다. this를 통해 인스턴스변수와 생성자의 매개변수를 구별하여 사용할 수 있고, 간결하게 작성이 가능하다.
class Car{
String color;
int door;
Car(){
this("white", 4);
}
Car(String color, int door){
this.color = color;
this.door = door;
}
}
또한 Car() 생성자에서 바로 this.color = "white";로 작성할 수 있지만 this()를 통해 작성하는 편이 유지보수 측면에서 강력하다.
또, 생성자를 통해 인스턴스를 복사할 수 있다.
public class CopyConstructorExample {
private int value;
private String name;
// 일반 생성자
public CopyConstructorExample(int value, String name) {
this.value = value;
this.name = name;
}
// 복사 생성자
public CopyConstructorExample(CopyConstructorExample other) {
this.value = other.value;
this.name = other.name;
}
public void display() {
System.out.println("Value: " + value + ", Name: " + name);
}
public static void main(String[] args) {
// 일반 생성자를 이용한 인스턴스 생성
CopyConstructorExample originalInstance = new CopyConstructorExample(42, "John");
originalInstance.display();
// 복사 생성자를 이용한 복사
CopyConstructorExample copiedInstance = new CopyConstructorExample(originalInstance);
copiedInstance.display();
}
}
이런 식으로 인스턴스를 복사할 수 있다. 인스턴스 copiedInstance는 originalInstance와 같은 상태를 가지지만 서로 독립적으로 메모리공간에 존재하는 별도의 인스턴스이므로 한 인스턴스의 값을 변경해도 다른 인스턴스에 영향을 줄 수 없다.
'프로그래밍 언어 > Java' 카테고리의 다른 글
[JAVA] 객체지향 프로그래밍 - 상속과 포함 (0) | 2023.11.28 |
---|---|
[JAVA] 객체지향언어 - 변수의 초기화와 초기화 블록 (0) | 2023.11.27 |
[JAVA] 객체지향언어 - 클래스와 객체, 클래스와 인스턴스를 잘 구분하자. (2) | 2023.11.24 |
[JAVA] 배열의 생성부터 다차원 가변 배열까지 (1) | 2023.11.23 |
[JAVA] 연산자 활용과 난수 구하기, 반복문 지정하여 탈출하기 (0) | 2023.11.23 |