카테고리 없음

[자바의 정석] 6장 객체지향 언어 1. 4~6 변수의 초기화

정찡이 2023. 3. 12. 16:42
728x90

아래 내용은 자바의 정석 강의&책을 보면서 정리한 내용입니다.

들어가기 전에

자바의 정석 6장 내용 리스트 

이번 시간에는 아래 내용 중 오버로딩~변수의 초기화 부분을 정리하겠습니다. 

 

4. 오버로딩

4.1 오버로딩이란?

오버로딩? 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것

 

4.2 오버로딩 조건

⭐  1. 메서드 이름이 같다.
      2. 매개변수의 개수 또는 타입이 다르다.
      주의! 반환 타입은 오버로딩 구현 시 아무런 영향을 주지 못함

 

 

4.3 오버로딩의 예

가장 대표적인 것은 println 메서드이다. 아래와 같이 매개변수 타입에 따라서 오버로딩 되어 있다.

 

1) 리턴 타입만 다른 경우 - 오버로딩이 아님

아래 add() 메서드에서 리턴값만 다른 경우 아래와 같은 에러가 발생한다. 이는 오버로딩이 아니다!!

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

 

2) 매개변수 타입 순서만 다른 경우 - 오버로딩

아래는 두 메서드 모두 int형과 long형 매개변수가 하나씩 선언되어 있지만 서로 순서가 다른 경우이다. 이 경우 구분하여 호출이 가능하므로 오버로딩으로 간주한다. 하지만 이렇게 선언하는 것은 순서를 외워야 하기 때문에 단점이 된다.

아래 m.add(1, 2)와 같은 호출은 불가능하다. 왜냐하면 어떤 메서드를 호출해야할지 알 수가 없다. 따라서 java: reference to add is ambiguous라는 컴파일 에러가 발생한다.

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

public class Test {
    public static void main(String[] args) {
        Math m = new Math();
        System.out.println(m.add(1, 2L));
        System.out.println(m.add(1, 2));    // 에러 발생:reference to add is ambiguous
    }
}

 

4.4 오버로딩의 장점

⭐  모두 근본적으로 같은 기능을 하는 메서드지만 서로 다른 이름을 가져야하면 이름 짓기도 어렵고 메서드를 사용하는 쪽에서 이름을 일일이 구분해서 기억해야 한다. 하지만 오버로딩을 하면 한 개만 기억하면 되고 메서드 명만 보고도 예측이 쉽다.
  • 오버로딩을 사용 안하는 경우, 아래와 같이 정의해야 한다.

 

4.5 가변인자와 오버로딩

매개변수 개수가 고정이 아닌 동적으로 지정할 수 있다. 가변 인자 선언을 진행하면 가능하다.

  • 가변인자 선언 방식: 타입… 변수명
  • 주의: 가변인자는 마지막에 선언

 

1) 가변인자 vs 배열 차이점

  • 배열을 매개변수로 하는 경우, 아래와 같이 빈 값으로 넣을 수 없고 반드시 null을 넣어줘야 한다.

2) 가변인자 오버로딩 시, 주의점

아래 예시를 보면 concatenate 메서드를 오버로딩했다. delim이 없는 경우를 오버로딩하였지만, 실제 컴퓨터는 이 두 메서드를 구분할 수 없기 때문에 에러가 발생한다. 따라서 가변인자 메서드는 오버로딩하지 않는 것을 추천한다.

public class Test {
    static String concatenate(String delim, String ... args){
        String result = "";

        for(String str: args){
            result += str + delim;
        }
        return result;
    }
    // 아래 오버로딩 시, 에러 발생!!
    static String concatenate(String ... args){
        return concatenate("", args);
    }
    public static void main(String[] args) {
        String[] strArr = {"100", "200", "300", "400"};
        System.out.println(concatenate("", "100", "200", "300", "400"));           // 100200300400
        System.out.println(concatenate("-", strArr));                                    // 100-200-300-400-
        System.out.println(concatenate(",", new String[]{"100", "200", "300", "400"}));  // 100,200,300,400,
        System.out.println("[" + concatenate(",", new String[0]) + "]");                 // []
        System.out.println("[" + concatenate(",") +  "]");                               // []
    }
}

 

5. 생성자

5.1 생성자란?

⭐  생성자란? 인스턴스가 생성될 때 호출되는 인스턴스(iv) 초기화 메서드이다. 따라서 인스턴스 변수의 초기화 작업에 주로 사용되며, 인스턴스 생성 시에 실행되어야 할 작업을 위해서 사용된다. ⇒ 주의! 생성자는 인스턴스를 생성하는 것은 아님!

생성자 조건

  1. 생성자의 이름은 클래스의 이름과 같다.
  2. 생성자는 리턴값이 없다. ⇒ void 생략

 

특징

  • 오버로딩이 가능하여 하나의 클래스에 여러 개의 생성자 존재

 

인스턴스 생성 코드 순서

Card c = new Card();
  1. 연산자 new에 의하여 메모리(heap)에 Card 클래스의 인스턴스가 생성된다.
  2. 생성자 Card()가 호출되어 수행된다.
  3. 연산자 new의 결과로 생성된 Card인스턴스의 주소가 반환되어 참조변수 c에 저장된다.

 

5.2 기본 생성자

⭐ 기본 생성자란? 컴파일러가 자동으로 생성해 주는 매개변수가 없고 아무런 내용 없는 생성자
주의: 기본 생성자는 생성자가 하나도 없을 때 컴파일러가 자동으로 생성되는 것!!

아래 예제를 통해 주의: 기본 생성자는 생성자가 하나도 없을 때 자동으로 생성되는 것!! 을 알 수 있다.

Data2 클래스는 매개변수가 있는 생성자를 정의하였다. 그러면 컴파일러는 자동으로 기본 생성자를 추가하지 않는다.

class Data1{
    int val;
}

class Data2{
    int val;
    // 매개변수가 존재하는 생성자 정의 
    Data2(int x){
        val = x;
    }
}

public class Test {
    public static void main(String[] args) {
        Data1 d1 = new Data1();
        Data2 d2 = new Data2();  // 컴파일 에러!!
        
    }
}

 

5.3 매개변수가 있는 생성자 - 초기화

생성자도 메서드와 같이 매개변수를 선언하여 호출 시 인스턴스의 초기화 작업을 사용할 수 있다.

class Car{
    String color; 
    String gearType;
    int door;
    
    Car(){}
    Car(String c, String g, int d){
        color = c;
        gearType = g;
        door = d;
    }
}
public class Test {
    public static void main(String[] args) {
        // 인스턴스 초기화 방법 1. 직접 
        Car c1 = new Car();
        c1.color = "white";
        c1.gearType = "auto";
        c1.door = 4;

        // 인스턴스 초기화 방법 2. 생성자 사용  
        Car c2 = new Car("white", "auto", 4);
    }
}

 

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

⭐  this: 인스턴스 자신을 가리키는 참조변수. 인스턴스 주소가 저장되어 있다.
                모든 인스턴스메서드에 지역변수로 숨겨진 채 존재.
    this(), this(매개변수): 생성자. 같은 클래스의 다른 생성자 호출 시 사용
        - 생성자의 이름으로 클래스 이름 대신 this를 사용한다.
        - 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.

주의: this는 static 메서드(클래스 메서드)에서 사용할 수 없다. this는 인스턴스를 의미하기 때문!: this는 static 메서드(클래스 메서드)에서 사용할 수 없다. this는 인스턴스를 의미하기 때문!
  • 첫 줄에서만 호출이 가능한 이유?
    • 다른 생성자 호출하기 이전 초기화 작업이 무의미해질 수 있다. → 7장에서 자세히 배움!
  • 결과적으로 아래 예시는 에러가 발생한다.
class Car{
    String color;
    String gearType;
    int door;

    Car(){
        this("white", "auto", 4);
    }
    Car(String color){
        door = 5;              // this가 첫번째줄에 존재하지 않음! 
        Car(color, "auto", 4); // this로 생성자를 호출해야한다. 
    }
    Car(String c, String g, int d){
        color = c;
        gearType = g;
        door = d;
    }
}
  • 수정된 올바른 예시
class Car{
    String color;
    String gearType;
    int door;

    Car(){
        this("white", "auto", 4);   // 아무것도 지정 안하는 경우, default값 설정 
    }
    Car(String color){
        this(color, "auto", 4);
    }
    Car(String c, String g, int d){
        color = c;
        gearType = g;
        door = d;
    }
}
public class Test {
    public static void main(String[] args) {
        // 인스턴스 초기화 방법 1. 직접
        Car c1 = new Car();
        c1.color = "white";
        c1.gearType = "auto";
        c1.door = 4;

        // 인스턴스 초기화 방법 2. 생성자 사용
        Car c2 = new Car("white", "auto", 4);
    }
}
  • this.인스턴스 사용
    • 아래와 같이 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;
    }
}

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

  • 아래 Car(Car c)를 통해 인스턴스를 복사하여 사용한 예시이다. c1.door = 100;로 값을 변경했지만 c2.door는 변경되지 않음을 알 수 있다.
  • this()를 호출하여 인스턴스 복사를 하였다.
class Car{
    String color;
    String gearType;
    int door;

    Car(){
        this("white", "auto", 4);
    }
    Car(Car c){
        this(c.color, c.gearType, c.door);   //this를 활용하여 복사 
    }
    Car(String color, String gearType, int door){
        this.color = color;
        this.gearType = gearType;
        this.door = door;
    }
}
public class Test {
    public static void main(String[] args) {
        // 인스턴스 초기화 방법 1. 직접
        Car c1 = new Car();
        Car c2 = new Car(c1);
        System.out.println("c1: " + c1.color + " " + c1.gearType + " " + c1.door);    // c1: white auto 4
        System.out.println("c2: " + c2.color + " " + c2.gearType + " " + c2.door);    // c2: white auto 4
        c1.door = 100;
        System.out.println("c1: " + c1.color + " " + c1.gearType + " " + c1.door);    // c1: white auto 100
        System.out.println("c2: " + c2.color + " " + c2.gearType + " " + c2.door);    // c2: white auto 4
    }
}

 

 

6. 변수의 초기화

6.1 변수의 초기화

⭐ 멤버변수(클래스변수와 인스턴스변수)와 배열의 초기화: 선택적 ⇒ 기본값으로 자동 초기화됨 (대부분 0으로 초기화)
 지역변수의 초기화: 필수

멤버변수(cv, iv) 초기화 방법

  1. 자동 초기화: 0으로 기본 초기화
  2. 간단 초기화(=명시적 초기화) : =
  3. 복잡 초기화
    1. 생성자
    2. 초기화 블럭
    • 인스턴스 초기화 블럭: 인스턴스변수를 초기화 하는데 사용
    • 클래스 초기화 블럭: 클래스변수를 초기화 하는데 사용

 

6.2 멤버변수 초기화 방식 1. 명시적 초기화

변수를 선언과 동시에 초기화 하는 것

class Car {
	int door = 4;             // 기본형 초기화 
	Engine e = new Engine();  // 참조형 초기화. null이 아닌 객체 주소를 넣기 

}

 

6.3 멤버변수 초기화 방식 2. 초기화 블럭

⭐ - 클래스 초기화 블럭: 클래스변수의 복잡한 초기화에 사용된다.
    -  인스턴스 초기화 블럭: 인스턴스변수의 복잡한 초기화에 사용된다. 거의 사용 안 함!!

 

클래스 초기화 인스턴스 초기화 (거의 사용 안 함)→ 생성자 사용

방법 static { /* 클래스 초기화블럭 */} {/* 인스턴스 초기화블럭 */}
수행 시기 클래스가 메모리에 처음 로딩될 때 한 번 - 인스턴스 생성할 때 마다 수행

 

인스턴스 초기화 사용 목적 예시

Car 생성자 수행 시 count ++; serialNo = count; 해당 부분은 중복이 된다.

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;
    }
    {
        count ++;
        serialNo = count;
    }

클래스 초기화, 인스턴스 초기화 사용 예시

아래예시는 초기화 블럭 호출 시기를 알 수 있다.

class BlockTest {
    static {
        System.out.println("static {}");   // 클래스 초기화. 1번 호출. BlockTest가 메모리에 로딩 시 가장 먼저 수행
    }

    {
        System.out.println("{ }");        //  인스턴스 초기화. 3번 호출. 객체 생성 시 인스턴스 초기화 블록 수행
    }

    public BlockTest(){
        System.out.println("생성자");       // 4번 호출. 인스턴스 초기화 -> 생성자 호출 
    }
    public static void main(String[] args) {
        System.out.println("BlockTest bt = new BlockTest();");  // 2번 호출
        BlockTest bt = new BlockTest();
        System.out.println("BlockTest bt2 = new BlockTest();");
        BlockTest bt2 = new BlockTest();
    }
}
  • 결과
static {}
BlockTest bt = new BlockTest();
{ }
생성자
BlockTest bt2 = new BlockTest();
{ }
생성자

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

  클래스 변수  인스턴스 변수
초기화 시점 클래스가 처음 로딩될 때 한번 인스턴스가 생성될 때마다 각 인스턴스별로 초기화
초기화 순서 기본값 → 명시적초기화 → 클래스 초기화 블럭 기본값 → 명시적초기화 → 인스턴스 초기화 블럭 → 생성자

 

초기화 순서 예시

class InitTest {
 // 명시적 초기화 (간단 초기화) 
	static int cv = 1;
	int iv = 1;

	static {cv = 2;}   // 클래스 초기화 블럭 
	{iv = 2;}          // 인스턴스 초기화 블럭 

	InitTest() {       // 생성자 
		iv = 3;
	}

}

위 예시의 순서는 아래와 같이 이뤄진다.

  • cv 초기화 → iv 초기화 진행
  • 자동(0으로 초기화) → 간단(=) → 복잡 초기화(static {}, 생성자)

사용 예시

아래 예시는 초기화 사용 예시다.

예를 들어 공장에서 제품 생산 시 생산일련번호(serialNo)를 제품마다 부여한다. 이때 {} 블럭(초기화 블럭)을 사용한다.

 

class Product {
    static int count = 0;  // 인스턴스 수 저장
    int serialNo;

    {
        ++count;
        serialNo = count;
    }
    public Product(){}
}
public class Test {
    public static void main(String[] args) {
        Product p1 = new Product();
        Product p2 = new Product();
        Product p3 = new Product();

        System.out.println("p1: " + p1.serialNo );       // p1: 1
        System.out.println("p2: " + p2.serialNo );       // p2: 2
        System.out.println("p3: " + p3.serialNo );       // p3: 3
        System.out.println("count: " + Product.count );  // count: 3
    }
}

 

다음 시간에는 자바의 정석 7장 객체지향 언어 2에 대해서 정리하겠습니다. 😀