Backend/Java

[Java] 객체지향 프로그래밍 1(Object-oriented Programming 1) - TIL 221215

짜잉이 2022. 12. 15. 09:30

📖 자바의 정석 Chapter 06 참고


4. 오버로딩(overloading)

4.1 오버로딩이란?

  • 오버로딩(overloading): 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것. (=메서드 오버로딩(method overloading))
  • 오버로딩(overloading)의 사전적 의미는 '과적하다'. 즉, 많이 싣는 것을 뜻한다.

 

4.2 오버로딩의 조건

1. 메서드 이름같아야 한다.
2. 매개변수의 개수 또는 타입달라야 한다.
  • 오버로딩된 메서드들은 매개변수에 의해서만 구별될 수 있다.
  • 반환 타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다.

 

4.3 오버로딩의 예

// 오버로딩 X
// 두 메서드는 매개변수의 이름만 다를 뿐 매개변수의 타입이 같기 때문에 오버로딩이 성립하지 않는다.
int add(int a, int b) { return a+b; }
int add(int x, int y) { return x+y; }


// 오버로딩 X
// 리턴타입만 다른 경우. 매개변수의 타입과 개수가 일치하기 때문에 오버로딩으로 간주되지 않는다.
int add(int a, int b) { return a+b; }
long add(int a, int b) { return (long)(a + b); }


// 오버로딩 O
// 두 메서드 모두 int형과 long형 매개변수가 하나씩 선언되어 있지만, 서로 순서가 다른 경우.
// 매개변수의 값에 의해 호출될 메서드를 구분할 수 있으므로 오버로딩으로 간주한다.
// 매개변수의 순서만을 다르게 하여 오버로딩을 구현하면 오히려 단점이 될 수도 있다.
long add(int a, long b) { return a+b; }
long add(long a, int b) { return a+b; }


// 오버로딩 O
// 위 메서드들은 모두 바르게 오버로딩 되었다.
// 같은 일을 하지만 매개변수를 달리해야하는 경우. 이와 같이 이름은 같고 매개변수를 다르게 하여 오버로딩을 구현한다.
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 오버로딩의 장점

오버로딩의 예시

e.g. PrintStream 클래스에는 10개의 오버로딩된 println 메서드를 정의해놓고 있다.

println 메서드를 호출할 때 매개변수로 지정하는 값의 타입에 따라서 호출되는 println메서드가 달라진다.

 

오버로딩의 장점

오버로딩을 통해 여러 메서드들이 println이라는 하나의 이름으로 정의될 수 있다면,

1.  이름을 기억하기 쉽고 이름을 짧게 쓸 수 있어서 오류의 가능성을 많이 줄일 수 있다.

그리고 같은 이름의 메서드들을 보고 같은 기능을 할 것이라 쉽게 예측할 수 있다.

2. 메서드 이름을 절약할 수 있다. 메서드 이름을 짓는데 고민을 덜 수 있고, 사용되었어야 할 메서드 이름을 다른 메서드 이름으로 사용할 수 있다.

 

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

// Object... args <-가변인자는 항상 마지막 매개변수이어야 한다.
// 가변인자 외에도 매개변수가 더 있다면, 가변인자를 매개변수 중에서 제일 마지막에 선언해야 한다.
// 그렇지 않으면 컴파일 에러가 발생한다.
public PrintStream printf(String format, Object... args) {
    ...
}
  • 가변인자(variable arguments): 메서드의 매개변수 개수 동적으로 지정하는 기능.
  • 가변인자는 '타입... 변수명'과 같은 형식으로 선언하며, PrintStream클래스의 printf()가 대표적인 예이다.
  • 가변인자를 선언한 메서드를 오버로딩하면, 메서드를 호출했을 때 구별되지 못하는 경우가 발생하기 쉽기 때문에 가능하면 가변인자를 사용한 메서드는 오버로딩 하지 않는 것이 좋다.
// 여러 문자열을 하나로 결합하여 반환하는 메서드. (오버로딩)
// 매개변수 개수를 다르게 해서 여러 개의 메서드 작성해야 한다.
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"})); // 배열도 가능


// 매개변수 타입이 배열
String concatenate(String[] str) { ... }

// 매개변수의 타입을 배열로 하면, 반드시 인자를 지정해줘야 하기 때문에 가변인자처럼 인자를 생략할 수 없다. 
// 그래서 null이나 길이가 0인 배열을 인자로 지정해줘야 하는 불편함이 있다.
String result = concatenate(new String[0]); // 인자로 배열을 지정
String result = concatenate(null);          // 인자로 null을 지정
String result = concatenate();              // 에러. 인자가 필요함.

 

5. 생성자(Constructor)

5.1 생성자란?

  • 생성자: 인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메서드'.
  • 인스턴스변수의 초기화 작업에 주로 사용되며, 인스턴스 생성 시에 실행되어야 할 작업을 위해서도 사용된다.
  • 인스턴스 초기화: 인스턴스변수들을 초기화하는 것을 뜻한다.
  • 메서드처럼 클래스 내에 선언되며, 메서드와 유사하지만 리턴값이 없다.
  • 생성자 앞에 리턴값이 없음을 뜻하는 키워드 void를 사용하지는 않고, 단지 아무 것도 적지 않는다.
  • 생성자도 오버라이딩이 가능하므로 하나의 클래스 여러 개의 생성자가 존재할 수 있다.

생성자의 조건

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

 

생성자의 정의

클래스이름(타입 변수명, 타입 변수명, ... ) {
    // 인스턴스 생성시 수행될 코드
    // 주로 인스턴스 변수의 초기화 코드를 적는다.
}


// Card클래스의 인스턴스를 생성하는 코드
Card c = new Card();
1. 연산자 new에 의해서 메모리(heap)에 Card클래스의 인스턴스가 생성된다.
2. 생성자 Card()가 호출되어 수행된다.
3. 연산자 new의 결과로, 생성된 Card인스턴스의 주소가 반환되어 참조변수 c에 저장된다.
  • 연산자 new가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는 것이 아니다.
  • new 다음 오는 '클래스이름()'이 바로 생성자이다.
  • 인스턴스를 생성할 때는 반드시 클래스 내에 정의된 생성자 중 하나를 선택하여 지정해주어야 한다.

 

5.2 기본 생성자(default constructor)

  • 클래스이름() { } 의 형태를 가진다.
  • 모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다..
  • 컴파일러가 제공하는 '기본 생성자(default constructor)' 덕분에 클래스에 생성자를 정의하지 않고도 인스턴스를 생성할 수 있었다.
  • 기본 생성자가 컴파일러에 의해서 자동적으로 추가되는 경우는 클래스에 정의된 생성자가 하나도 없을 때 뿐이다.

 

5.3 매개변수가 있는 생성자

class Car {
    String color;
    String gearType;
    int door;
    
    Car() {}                         // 기본 생성자
    Car(String c, String g, int d) { // 매개변수 있는 생성자
        color = c;
        gearType = g;
        door = d;
    }
}

class CarTest {
    public static void main(String[] args) {
        // Car인스턴스를 생성할 때, 생성자 Car()를 사용한다면,
        // 인스턴스를 생성한 다음에 인스턴스변수들을 따로 초기화해줘야 한다.
        Car c1 = new Car();
        c1.color = "white";
        c1.gearType = "auto";
        c1.door = 4;

        // 매개변수가 있는 생성자를 사용한다면 
        // 인스턴스를 생성하는 동시에 원하는 값으로 초기화를 할 수 있다.
        // 이 코드가 더 간결하다. 더 바람직한 코드.
        Car c2 = new Car("white", "auto", 4);
   
    }
}

 

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

생성자를 작성할 때 지켜야하는 두 조건 (생성자 간에 서로 호출하기 위해)

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

 

class Car {
    String color;
    String gearType;
    int door;
    
    Car() {
        // 생성자 Car()에서 또 다른 생성자 Car(String color, String gearType, int door)를 호출
        // 생성자 간의 호출에는 생성자 이름 대신 this를 사용해야 한다.
        // 생성자 Car()의 첫째 줄에서 호출했다.
        this("white", "auto", 4); 
    }
    
    Car(String color) {
        this(color, "auto", 4);
    }
    
    // color은 생성자의 매개변수로 정의된 지역변수
    Car(String color, String gearType, int door) { 
        // this.color는 인스턴스변수
        // this는 참조변수로 인스턴스 자신을 가리킨다.'this'로 인스턴스 변수에 접근할 수 있다.
        this.color = color;
        this.gearType = gearType;
        this.door = door;
    }
}

class CarTest2 {
    public static void main(String[] args) {
        Car c1 = new Car();
        Car c2 = new Car("red");
   
    }
}
  • 'this'를 사용할 수 있는 것은 인스턴스멤버뿐이다. static메서드는 인스턴스를 생성하지 않고도 호출될 수 있으므로 static메서드가 호출된 시점에 인스턴스가 존재하지 않을 수도 있다.
  • 생성자를 포함한 모든 인스턴스메서드에는 자신이 관련된 인스턴스를 가리키는 참조변수 'this'가 지역변수로 숨겨진 채로 존재한다.
this: 인스턴스 자신을 가리키는 참조변수. 인스턴스의 주소가 저장되어 있다.
this(), this(매개변수): 생성자. 같은 클래스의 다른 생성자를 호출할 때 사용한다.

 

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

class Car {
    String color;
    String gearType;
    int door;
    
    Car() {
        this("white", "auto", 4); 
    }
    
    Car(Car c) { // 인스턴스의 복사를 위한 생성자.
        // color = c.color;
        // gearTeyp = c.gearType;
        // door = c.door;
        // Car(String color, String gearType, int door)
        
        // 생성자 'Car(Car c)'는 다른 생성자인 
        // 'Car(String color, String gearType, int door)'를 호출하는것이 바람직하다.
        this(c.color, c.gearType, 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 변수의 초기화

  • 변수의 초기화: 변수를 선언하고 처음으로 값을 저장하는 것
  • 멤버변수는 초기화 하지 않아도 자동적으로 변수의 자료형에 맞는 기본값으로 초기화가 이루어진다.
  • 지역변수사용하기 전에 반드시 초기화해야 한다.
멤버변수(클래스변수와 인스턴스변수)와 배열의 초기화는 선택적이지만,
지역변수의 초기화는 필수적이다.

 

멤버변수의 초기화 방법

1. 명시적 초기화(explicit initialization)
2. 생성자(constructor)
3. 초기화 블럭(initialization block)
     - 인스턴스 초기화 블럭: 인스턴스변수를 초기화 하는데 사용
     - 클래스 초기화 블럭: 클래스변수를 초기화 하는데 사용

 

6.2 명시적 초기화(explicit initialization)

  • 명시적 초기화: 변수를 선언과 동시에 초기화하는 것. 여러 초기화 방법 중에서 가장 우선적으로 고려되어야 한다.
  • 보다 복잡한 초기화 작업이 필요할 때는 '초기화 블럭(initialization block)' 또는 생성자를 사용해야 한다.
class Car {
    int door = 4;            // 기본형(primitive type) 변수의 초기화
    Engine e = new Engine(); // 참조형(reference type) 변수의 초기화
    
    //...
}

 

6.3 초기화 블럭(initialization block)

  • 클래스 초기화 블럭: 클래스변수의 복잡한 초기화에 사용된다.
  • 인스턴스 초기화 블럭: 인스턴스변수의 복잡한 초기화에 사용된다.
class InitBlock {
    static { /* 클래스 초기화블럭입니다 */ }
    
    { /* 인스턴스 초기화블럭입니다. */ }

    // ...
}
  • 초기화 작업이 복잡하여 명시적 초기화만으로는 부족한 경우 초기화 블럭을 사용한다.
  • 클래스 초기화 블럭클래스가 메모리에 처음 로딩될 때 한번만 수행되며, 인스턴스 초기화 블럭생성자와 같이 인스턴스를 생성할 때마다 수행된다.
  • 생성자보다 인스턴스 초기화 블럭이 먼저 수행된다.
  • 실행순서: 클래스 초기화 블럭 -> main메서드 -> 인스턴스 초기화 블럭 -> 생성자
  • 인스턴스 변수의 초기화는 주로 생성자를 사용하고, 인스턴스 초기화 블럭모든 생성자에서 공통으로 수행돼야 하는 코드를 넣는데 사용한다.
  • 클래스의 모든 생성자에 공통으로 수행돼야 하는 문장들이 있을 때, 이 문장들을 각 생성자마다 써주기보다는 인스턴스 블럭에 넣어 코드를 간결하게 만들 수 있다.
  • 배열이나 예외처리가 필요한 초기화에서는 추가적으로 클래스 초기화 블럭을 사용하도록 한다.
  • 인스턴스 변수의 복잡한 초기화생성자 또는 인스턴스 초기화 블럭을 사용한다.

 

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

클래스변수초기화시점: 클래스가 처음 로딩될 때 단 한번 초기화 된다.
인스턴스변수초기화시점: 인스턴스가 생성될 때마다 각 인스턴스별로 초기화가 이루어진다.

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