오버로딩(overloading)
4.1 오버로딩이란?
- 메서드도 변수와 마찬가지로 같은 클래스 내에서 구별될 수 있어야 하기에 다른 이름을 가져야 한다.

4.2 오버로딩의 조건

- 매서드의 이름이 같아도 매개변수가 다르면 서로 구별될 수 있다는 특징
- 반환 타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다
- 이유: 우리가 코딩을 하고 입력을 하면 컴파일러가 매개변수의 타입, 개수로 함수를 구분하는데, 리턴 타입으로는 우리가 어떤 메서드를 불러오는지 컴퓨터는 알지 못한다.
4.3 오버로딩의 예
- 가장 대표적인 것은 println 메서드이다. 괄호 안에 값만 지정해주면 출력에 문제가 없어지만, 매개변수로 지정하는 값의 타입에 따라서 호출되는 println 메서드가 달라진다.
void println()
void println(boolean x)
void println(char x)
void println(char[] x)
void println(double x)
void println(float x)
void println(int x)
void println(long x)
void println(Object 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] return 타입만 다른 경우
int add(int a, int b) { return a + b; }
long add(int a, int b) { return (long) a + b; }
- 매개변수의 타입과 개수가 일치하기 때문에 add(3,3) 처럼 호출한다면 어떤 메서드가 호출된 것인지 구분하지 못한다
[보기 3] 매개변수 타입의 순서가 다른 경우
long add(long a, int b) { return a + b; }
long add(int a, long b) { return a + b; }
- 매개변수 타입이 다르고 순서가 다르기 때문에, 매개변수 값에 의해 순서가 구분될 수 있으므로 오버로딩으로 간주한다
[보기 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;
}
- 위 메서드들은 모두 바르게 오버로딩 되어있다. 같은 일을 하지만 매개변수를 달리해야하는 경우에, 이름은 같고 매개변수를 다르게 하여 오버로딩을 구현한다
4.4 오버로딩의 장점
void println()
void printlnBoolean(boolean x)
void printlnChar(char x)
void printlnDouble(char[] x)
void printlnString(double x)
- 모두 근본적으로 같은 기능을 하는 메서드지만 서로 다른 이름을 가져야 한다면 명명하기도 어렵고, 사용자도 일일히 구분하여 사용하기 까다롭다. 이 문제를 오버로딩이 해결해준다
- 메서드의 이름을 절약할 수 있다.
예제
class OverloadingTest {
public static void main(String args[]){
MyMath mm = new MyMath();
System.out.println("mm.add(3, 3) 결과:" + mm.add(3,3));
}
}
class MyMath{
int add(int a, int b){
System.out.print("int add(int a, int b) - ");
return a + b;
}
}
출력 결과 : int add(int a, int b) - mm.add(3, 3) 결과:6
→ 어떻게 int add(int a, int b)가 먼저 출력될까???
실행흐름
- println 메서드가 결과를 출력하려면, add메서드의 결과가 먼저 계산되어야 하기 때문이다.
int result = mm.add(3,3);
System.out.println("mm.add(3,3) 결과:" + result);
- 위의 두 문장이 합쳐진 것이라고 생각하면 이해가 쉽다
4.5 가변인자(varargs)와 오버로딩
- 기존에는 메서드의 매개변수 개수가 고정적이었으나 JDK1.5부터 동적으로 지정이 가능하며 이를 “가변인자” 라고 한다.
public PrintStream printf(String format, Object... args){...}
- 위와 같이 가변인자 외에도 매개변수가 더 있다면 가변인자를 매개변수 중에서 제일 마지막에 선언해야 한다.
- 마지막에 넣지 않으면 가변인자인지 아닌지 구부할 방법이 없기 때문이다.
활용법
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.println(concatenate("a", "b"));
System.out.println(concatenate(new String[]{"A", "B"}));
- 인자가 없어도 되고, 배열도 인자가 될 수 있다.
- 가변인자는 내부적으로 배열을 이용한다. 가변인자가 선언된 메서드를 호출할 떄마다 배열이 새로 생생된다
→ 따라서 편리하지만 배열을 선언하는 비효율이 숨어있으므로 필요한 경우에만 사용하자
- 가변인자 VS 매개변수의 타입을 배열로 하는 것과의 차이점
String concatenate(String[] str){...}
String result = concatenate(new String[0]); // 인자를 배열로 지정
String result = concatenate(null); // 인자를 null로 지정
String result = concatenate(); // 에러. 인자가 필요하다
- 매개변수의 타입을 배열로 하면, 반드시 인자를 지정해줘야 하기 때문에, 위의 코드처럼 인자를 생략할 수 없다.
- 그래서 null이나 길이가 0인 배열을 인자로 지정해줘야 하는 불편함이 있다
5. 생성자
5.1 생성자란?
- 생성자는 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드이다. 따라서 인스턴스 변수의 초기화 작업에 주로 사용되며, 인스턴스 생성 시에 실행되어야 할 작업을 위해서 사용되기도 한다.


- 연산자 new가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는 것이 아니다.
- 생성자도 오버로딩이 가능하므로 하나의 클래스에 여러 개의 생성자가 존재할 수 있다.

5.2 기본 생성자(default constructor)
- 모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다.
- 지금까지 클래스에 생성자를 정의하지 않고도 인스턴스를 생성할 수 있었던 이유는 컴파일러가 제공하는 ‘기본 생성자’ 덕분이다.
- 컴파일 할 때, 소스파일 클래스에 생성자가 하나도 정의되지 않은 경우 컴파일러는 자동적으로 아래와 같은 내용의 기본 생성자를 추가하여 컴파일 한다.
클래스이름() {}
Card(){}

5.3 매개변수가 있는 생성자
class Car {
String color;
String gearType;
int door;
Car(){};
Car(String c, String g, int d){
color = c;
gearType = g;
door = d;
}
}
Car c = new Car();
c.color = "white";
c.geartype = "auto";
c.door = 4;
//기본 생성자가 아닌 매개변수가 있는 생성자를 사용하면 다음과 같이 초기화할 수 있다.
Car c = new Car("white", "auto", 4);
5.4 생성자에서 다른 생성자 호출하기 - this(), this

에러
Car(String color){
door = 5;
Car(color, "auto", 4); //에러 1. 생성자의 두 번째 줄에서 다른 생성자 호출
//에러 2. this(color, "auto", 4); 로 해야함
→ 첫번 째 줄에 선언하는 이유 : 생성자 내 초기화 작업 도중 다른 생성자를 호출하면, 그 호출된 생성자 내에서 초기화 작업을 진행할 것이므로, 호출 이전의 초기화 작업은 무의미해진다.
5.5 생성자를 이용한 인스턴스의 복사
Car (Car c){
color = c.color;
gearType = c.gearType;
door = c.door;
- 위의 코드는 Car클래스의 참조변수를 매개변수로 선언한 생성자이다.
class Car {
String color;
String gearType;
int door;
Car(){
this("white". "auto", 4);
}
Car(Car c){ //인스턴스의 복사를 위한 생성자
color = c.color;
gearType = c.gearType;
door = c.door;
}
Car(String color, String gearType, int door){
this.color = color;
this.gearType = gearType;
this.door = door;
}
}
class CarTest3{
public static void main(String[] args) {
Car c1 = new Car();
Car c2 = new Car(c1); // C1의 복사본 c2를 생성한다.
}
}
- 인스턴스 c2는 c1을 복사하여 생성된 것이므로 서로 같은 상태를 갖지만, 서로 독립적으로 메모리 공간에 존재하는 별도의 인스턴스이므로 c1의 값들이 변경되어도 c2에는 영향을 미치지 못한다.

6. 변수의 초기화
6.1 변수의 초기화

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

6.2 명시적 초기화(explict initialization)
- 변수를 선언과 동시에 초기화하는 것을 명시적 초기화라고 한다.
class Car {
int door = 4;
Engine e = new Engine();
///...
}
6.3 초기화 블럭(initialization block)

class InitBlock{
static { /* 클래스 초기화 블럭입니다. */}
{ /* 인스턴스 초기화 블럭입니다. */}
// ...
}
- 클래스 초기화 블럭은 클래스가 메모리에 처음 로딩될 때 한번만 수행되며, 인스턴스 초기화 블럭은 생성자와 같이 인스턴스를 생성할 때마다 수행된다.
- 생성자보다 인스턴스 초기화 블럭이 먼저 수행된다
Car c1 = new Car(); // 클래스 코기화 블럭 -> 인스턴스블럭 초기화 블럭 -> 생성자 호출
- 인스턴스 변수의 초기화 - 생성자 사용
- 인스턴스 초기화 블럭 - 모든 생성자에서 공통으로 수행돼야 하는 코드 넣음
class Car {
int count;
int serialNo;
String color;
String gearType;
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;
}
- 코드의 중복을 제거해 코드의 신뢰성을 높이고 오류 발생가능성을 줄여준다.
class StaticBlockTest {
static int[] arr = new int[10];
static {
for(int i = 0 ;i < 10; i++){
arr[i] = (int)(Math.random()*10) + 1;
}
} // 1과 1ㅔ 사이의 임의의 값을 배열 arr에 저장한다
public static void main(String[] args) {
for(int i = 0 ; i < arr.length; i++){
System.out.println("arr["+i+"]" + arr[i]);
}
}
}
- 이처럼 배열이나 예외처리가 필요한 초기화에서는 명시적 초기화만으로는 복잡한 초기화 작업을 할 수 없다.
6.4 멤버변수의 초기화 시기와 순서

class InitTest {
static int cv = 1; //명시적 초기화
int iv = 1;
static { cv = 2; } // 클래스 초기화 블럭
{ iv = 2; } // 인스턴스 초기화 블럭
InitTest (){
iv = 3; // 생성자
}
}
cv 와 iv의 초기화 순서
클래스변수 초기화 (1~3) : 클래스가 처음 메모리에 로딩될 때 차례대로 수행
인스턴스변수 초기화(4~7) : 인스턴스를 생성할 때 차례대로 수행
- cv 가 메모리에 생성되고, cv는 int형의 기본값인 0이 cv에 저장된다.
- 그 다음에는 명시적 초기화(int cv =1)에 의해서 cv에 1이 저장된다
- 마지막으로 클래스 초기화 블럭(cv = 2)가 수행되어 cv에 2가 저장된다
- InitTest 클래스의 인스턴스가 생성되면서 iv 가 메모리(heap)에 존재하게된다. iv 역시 int형 변수이므로 기본값 0이 저장된다.
- 명시적 초기화에 의해 iv가 1에 저장되고
- 인스턴스 초기화 블럭이 수행되어 iv에 2가 저장된다.
- 마지막으로 생성자가 수행되어 iv에는 3이 저장된다.
예제
class Product {
static int count = 0; // 생성된 인스턴스의 수를 저장하기 위한 변수
int serialNo; // 인스턴스 고유의 번호
{
++count;
serialNo = count; // Product 인스턴스가 생성될 때마다 count의 값을 1씩 증가시켜 serialNo에 저장한다
}
public Product() {}
}
→ 만일 count를 인스턴스 변수로 선언했다면, 인스턴스가 생성될 때마다 0으로 초기화될 것이므로, static으로 선언해주어야 한다.
'Java > Java 스터디' 카테고리의 다른 글
| Chapter 07. 객체지향 프로그래밍 II (part 1 상속) (0) | 2023.06.30 |
|---|---|
| Chapter 06. 객체지향 프로그래밍 I (part 3) (1) | 2023.02.22 |
| Chapter 06. 객체지향 프로그래밍 I (part 1~2) (0) | 2023.02.21 |
| Chapter 05. 배열 (0) | 2023.02.15 |
| Chapter 04. 조건문과 반복문 (0) | 2023.02.15 |