LANGUAGE/Java, Spring

Java의 정석 정리 (06장)

axelrod입니다 2024. 12. 2. 18:41

1. 객체지향언어

더보기

1.1 역사

* 객체지향이론 기본 개념 : 실제 세계는 사물(객체)로 이루어져 있으며, 발생하는 모든 사건들은 사물간의 상호작용이다.

* 객체지향이론은 상속, 캡슐화, 추상화 개념을 중심으로 구체적으로 발전되어 왔다.

 

1.2 특징

1. 코드의 재사용성이 높다
2. 코드 관리 용이 (코드간의 관계를 이용해서 쉽게 코드 변경 가능)
3. 높은 신뢰성 (제어자와 메서드를 이용해 데이터 보호, 코드 중복 제거로 코드 불일치로 인한 오동작 방지)

 


 

2. 클래스와 객체

더보기
객체의 정의 : 실제로 존재하는 것, 사물 또는 개념
객체의 용도 : 객체가 갖고 있는 기능과 속성에 따라 다름

유형의 객체 : 책상, 의자, 자동차 ..
무형의 객체 : 수학공식, 프로그램 에러와 같은 논리나 개념

 

* 프로그래밍에서의 객체는 클래스에 정의된 내용대로 메모리에 생성된 것을 의미


 

2.2 객체와 인스턴스

* 클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화(instantiate)라고 하며, 클래스로부터 만들어진 객체를 그 클래스의 인스턴스라고 한다.


 

2.3 객체의 구성요소 - 속성과 기능

* 객체는 속성과 기능의 집합. 속성과 기능을 그 객체의 멤버(구성원)라 한다.

속성(property) : 멤버변수(member variable), 특성(attribute), 필드(field), 상태(state)
기능(function) : 메서드(method), 함수(function), 행위(behavior)

 

ex)
TV의 속성 : 크기, 길이, 높이, 색상, 볼륨, 채널 등
TV의 기능 : 켜기/끄기, 볼륨 높이기/낮추기, 채널 변경하기 등
class Tv {
    String color;
    boolean power;
    int channel;
    
    void power()        { power = !power; } // power가 true면 false
    void channelUp()    { channel++; }
    void channelDown()  { channel--; }
}

 

2.4 인스턴스의 생성과 사용

Tv 클래스를 선언한 것은 설계도를 작성한 것에 불과하므로, Tv인스턴스를 생성해야 제품을 사용할 수 있다.

클래스명 변수명;           // 클래스의 객체를 참조학 위한 참조변수 선언
변수명 = new 클래스명();   // 클래스의 객체를 생성 후, 객체의 주소를 참조변수에 저장

Tv t;                  // Tv클래스 타입의 참조변수 t 선언
t = new Tv();          // Tv인스턴스 생성 후, 생성된 인스턴스의 주소를 t에 저장

 

ex)

class Tv {
    // Tv의 속성 (멤버변수)
    String color;
    boolean power;
    int channel;

    // Tv의 기능 (메서드)
    void power()    { power = !power; }
    void channelUp()    { ++channel; }
    void channelDown()  { --channel; }
}

public class TvTest {
    public static void main(String[] args) {
        Tv t;               // Tv인스턴스를 참조하기 위한 변수 선언
        t = new Tv();       // Tv인스턴스 생성
        t.channel = 7;      // Tv인스턴스의 멤버변수 channel의 값 설정
        t.channelDown();    // Tv인스턴스의 메서드 호출
        System.out.println("현재 채널은 " + t.channel + "입니다.");
    }
}

class TvTest2 {
    public static void main(String[] args) {
        Tv t1 = new Tv();  // Tv t; t1 = new Tv();를 한문장으로 생략
        Tv t2 = new Tv();
        System.out.println("t1의 channel 값은 " + t1.channel + "입니다.");
        System.out.println("t2의 channel 값은 " + t2.channel + "입니다.");

        t1.channel = 7;
        System.out.println("t1의 channel값을 7로 변경했습니다.");

        System.out.println("t1의 channel 값은 " + t1.channel + "입니다.");
        System.out.println("t2의 channel 값은 " + t2.channel + "입니다.");
    }
}

 

* 인스턴스는 오직 참조변수를 통해서만 다룰 수 있으며, 참조변수의 타입은 인스턴스의 타입과 일치해야 한다.

 

* 같은 클래스로부터 생성되어도 각 인스턴스의 속성(멤버변수)은 서로 다른 값을 유지할 수 있으며, 메서드의 내용은 모든 인스턴스에 대해 동일하다.


 

2.5 객체 배열

* 많은 수의 객체를 다뤄야 할 때, 배열로 다루면 편리하다.

 

* 객체 배열 안에 객체가 저장되는 것이 아니라 객체의 주소가 저장된다. 즉, 객체 배열은 참조변수들을 하나로 묶은 참조 변수 배열이다.

Tv t1, tv2, tv3;   ->   Tv[] tvArr = new Tv[3];

위의 'tvArr'는 3개의 객체(객체 주소)를 저장할 수 있다. 참조변수들이 만들어진 것일 뿐, 실제 데이터는 저장되지 않았다.

 

 

* 배열의 초기화 블럭을 사용하면, 한줄로 정의할 수 있다.

Tv[] = tvArr = { new Tv(), new Tv(), newTv() };

 

* 다뤄야할 객체가 많을 때는 for문을 사용하면 된다.

Tv[] tvArr = new Tv[100];

for (int i=0; i<tvArr.length; i++) {
	tvArr[i] = new Tv();
}

 

2.6 클래스의 또 다른 정의

 

1. 클래스 - 데이터와 함수의 결합

<프로그래밍 언어에서 데이터 처리를 위한 데이터 저장형태의 발전과정>

1. 변수 : 하나의 데이터를 저장할 수 있는 공간
2. 배열 : 같은 종류의 여러 데이터를 하나의 집합으로 저장할 수 있는 공간
3. 구조체 : 서로 관련된 여러 데이터를 종류에 관계없이 하나의 집합으로 저장할 수 있는 공간
4. 클래스 : 데이터와 함수의 결합 (구조체 + 함수)

 

2. 클래스 - 사용자 정의 타입 (user-defined-type)

* 프로그래밍 언어에서 제공하는 자료형 외에 프로그래머가 서로 관련된 변수들을 묶어서 하나의 타입으로 새로 추가하는 것을 'user-defined-type'이라고 한다.

 

* 예를 들어 시간을 처리할 때 hour와 minute은 int로 second는 float으로 정의할 수 있는데, 클래스로 묶어서 세 변수를 멤버변수로 갖는 Time 클래스로 편하게 만들 수 있다.


 

3. 변수와 메서드

더보기

3.1 선언위치에 따른 변수의 종류

* 변수는 클래스변수, 인스턴스변수, 지역변수 모두 세 종류가 있다.

* 변수의 종류를 결정짓는 요소는 '변수의 선언 위치'.

* 멤버변수를 제외한 나머지 변수들은 모두 지역변수.

* 멤버변수 중 static이 붙은 것은 클래스변수, 붙지 않은 것은 인스턴스변수.

class Variables
{
    int iv;           // 인스턴스 변수
    static int cv;    // 클래스 변수(static변수, 공유변수)
    
    void method()
    {
    	int lv = 0;   // 지역변수
    }
}

 

3.2 클래스변수와 인스턴스변수

* 인스턴스마다 독립적인 저장공간을 갖는 인스턴스변수와 달리, 클래스변수는 모든 인스턴스가 공통된 저장공간(변수)을 공유.


 

3.3 메서드

* 메서드(method)는 특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것. 수학의 함수와 유사.

 

* 메서드가 내부적으로 어떤 과정을 거쳐 결과를 만들어내는지 전혀 몰라도 된다. (Black Box)

 

* 메서드 사용 이유

    1. 높은 재사용성(reusability)

    2. 중복된 코드 제거

    3. 프로그램의 구조화


 

3.4 메서드의 선언과 구현

* 메서드는 '선언부(header)'와 '구현부(body)'로 이루어져 있다.

 

* 선언부는 '메서드의 이름'과 '매개변수 선언', '반환타입'으로 구성되어 있으며, 작업을 수행하기 위해 어떤 값이 필요하고 어떤 타입으로 값을 반환하는지에 대한 정보를 제공.

* 선언부는 후에 변경사항이 발생하지 않도록 작성.

* 일반적인 변수선언과 달리 변수선언과 두 변수의 타입이 같아도 변수의 타입을 생략할 수 없다.

* 반환값이 없는 경우 반환타입으로 'void'를 적는다.


 

3.5 메서드의 호출

* 메서드를 정의해도 호출되지 않으면 아무 일도 일어나지 않는다.

 

* 메서드 호출 방법

print99danAll ();        // void print99danAll()을 호출
int result = add(3, 5);  // int add(int x, int y)를 호출하고, 결과를 result에 저장

 

* 메서드를 호출할 때 괄호 안에 지정해준 값들을 '인자(argument)' 또는 '인수'라고 한다. 인자의 개수와 순서는 호출된 메서드에 선언된 매개변수와 일치해야 한다.

 

* 인자는 메서드가 호출되면서 매개변수에 대입되므로, 인자의 타입은 매개변수의 타입과 일치하거나 자동 형변환이 가능해야 한다.

* 같은 클래스 내의 메서드끼리는 참조변수를 사용하지 않고도 서로 호출 가능

 

* static 메서드는 같은 클래스 내의 인스턴스 메서드를 호출할 수 없다.


 

3.6 return문 

* 반환값의 유무에 관계없이 모든 메서드는 적어도 하나의 return문이 있어야 한다.

 

* 반환타입이 void인 경우, 컴파일러가 메서드의 마지막에 'return;'을 자동적으로 추가한다.

 

* 반환타입이 void가 아닌 경우, 반드시 return문이 있어야 한다.

 

* 유효성 검사 : 타입만 맞으면 어떤 값도 매개변수를 통해 넘어올 수 있기 때문에, 가능한 모든 경우의 수에 대해 고민하고 대비해야 한다. 적절하지 않은 값이 넘어온다면 매개변수의 값을 보정하던가, 보정하는 것이 불가능하면 return문을 사용해서 작업을 중단하고 호출한 메서드로 되돌아가야 한다.

float divide(int x, int y) {
            // 작업을 하기 전에 나누는 수(y)가 0인지 확인
            if (y == 0) {
                System.out.println("0으로 나눌 수 없습니다.");
                return 0;
            }
            return x / (float) y;
        }

 

3.7 JVM의 메모리 구조

* 응용프로그램이 실행되면, JVM은 시스템으로부터 메모리를 할당받고, 용도에 따라 여러 영역으로 나누어 관리.

 

* 주요 영역으로는 method area, call stack, heap이 있다.

 

* 호출스택의 제일 상위에 위치하는 메서드가 현재 실행 중인 메서드이며, 나머지는 대기상태에 있게 된다.

public class CallStackTest2 {
    public static void main(String[] args) {
        System.out.println("main(String[] args)이 시작되었음");
        firstMethod();
        System.out.println("main(String[] args)이 끝났음");
    }

    static void firstMethod() {
        System.out.println("firstMethod()이 시작되었음");
        secondMethod();
        System.out.println("firstMethod()이 끝났음");
    }

    static void secondMethod() {
        System.out.println("secondMethod()이 시작되었음");
        System.out.println("secondMethod()이 끝났음");
    }
}

 

3.8 기본형 매개변수와 참조형 매개변수

 

* 자바에서 메서드를 호출할 때 매개변수로 지정한 값을 메서드의 매개변수에 복사해서 넘겨준다. 

 

* 매개변수 타입이 기본형(primitive type)일 때는 기본형 값을 복사하지만, 참조형(reference type)이면 인스턴스의 주소가 복사된다.

 

기본형 매개변수 :  변수의 값을 읽기만 할 수 있다. (read only) 
참조형 매개변수 :  변수의 값을 읽고 변경할 수 있다. (read & write)

 

class Data { int x; }
	public class PrimitiveParamEx {
    	public static void main(String[] args) {
        	Data d = new Data();
	        d.x = 10;
    	    System.out.println("main() : x = " + d.x);

        	change(d.x);
	        System.out.println("After change(d.x)");
    	    System.out.println("main() : x = " + d.x);
	    }
	    static void change(int x) {
    	    x = 1000;
        	System.out.println("change() : x = " + x);
    }
}

* 위의 코드를 보면 'd.x'의 값이 변경된 것이 아니라, change 메서드의 매개변수 x의 값이 변경된 것이다. 원본이 아닌 복사본이 변경된 것이라 원본에는 아무런 영향을 미치지 못한다. 

 

public class ReferenceParmEx2 {
    public static void main(String[] args) {
        int[] x = {10}; // 크기가 1인 배열. x[0] = 10;
        System.out.println("main() : x = " + x[0]);

        change(x);
        System.out.println("After change(x)");
        System.out.println("main() : x = " + x[0]);
    }
    static void change(int[] x) {
        x[0] = 1000;
        System.out.println("change() : x = " + x[0]);
    }
}

* 배열도 객체와 같이 참조변수를 통해 데이터가 저장된 공간에 접근한다.

* 위의 코드에서 변수 x도 int배열타입의 참조변수이기 때문에 같은 결과를 얻는다.

 

* 반환값이 없어도 참조형 매개변수를 이용하면 메서드의 실행결과를 얻을 수 있다. 메서드는 단 하나의 값만을 리턴할 수 있지만 아래의 방법을 응용하면 여러 값을 리턴할 수 있다.

 


 

3.9 참조형 반환타입

* 매개변수 뿐 아니라 반환타입도 참조형이 될 수 있다. 

 

ex)

public class ReferenceReturnEx {
    public static void main(String[] args) {
        Data d = new Data();
        d.x = 10;

        Data d2 = copy(d);
        System.out.println("d.x = " + d.x);
        System.out.println("d2.x = " + d2.x);

    }
    static Data copy(Data d) {
        Data tmp = new Data();
        tmp.x = d.x;
        return tmp;
    }
}

 

3.10 재귀호출

* 메서드의 내부에서 메서드 자신을 다시 호출하는 것을 '재귀호출(recursive call)'이라고 한다.

void method() {
	method();
}

 

* 호출된 메서드는 '값에 의한 호출(call by value)'을 통해, 원래의 값이 아닌 복사된 값으로 작업하기 때문에 호출한 메서드와 독립된 작업이 가능.

 

* 무한반복에 빠지지 않도록, 탈출 조건이 필요.

void method(int n) {
	if (n == 0) {
    	return;
    }
    System.out.println(n);
}

 

* factorial

public class FactorialTest {
    public static void main(String[] args) {
        int result = factorial(4);
        System.out.println(result);

    }
    static int factorial(int n) {
        int result = 0;

        if (n == 0) {
            result = 1;
        } else {
            result = n * factorial(n - 1);
        }
        return result;
    }
}
// 조건 개선

public class FactorialTest2 {
    public static void main(String[] args) {
        int n = 21;
        long result = 0;

        for (int i=1; i <= n; i++) {
            result = factorial(i);

            if (result == -1) {
                System.out.printf("유효하지 않은 값입니다. (0<n<=20) : %d%n", n);
                break;
            }
            System.out.printf("%2d != %20d%n", i, result);
        }
    }

    static long factorial(int n) {
        if (n <= 0 || n >= 12) return -1;
        if (n <= 1) return 1;
        return n * factorial(n - 1);
    }
}

 

3.11 클래스 메서드(static method)와 인스턴스 메서드

* 클래스 메서드도 클래스변수처럼, 객체를 생성하지 않고 '클래스이름.메서드이름(매개변수)'와 같은 식으로 호출이 가능.

 

* 반면, 인스턴스 메서드는 반드시 객체를 생성해야 호출 가능.

 

* 클래스는 '데이터(변수)와 메서드의 집합'이므로, 같은 클래스 내에 있는 메서드와 멤버변수는 아주 밀접한 관계가 있다.

 

* 인스턴스 메서드는 인스턴스 변수와 관련된 작업을 하는, 즉 메서드의 작업을 수행하는데 인스턴스 변수를 필요로 하는 메서드이다. 그런데 인스턴스 변수는 인스턴스(객체)를 생성해야만 만들어지므로 인스턴스 메서드 역시 인스턴스를 생성해야만 호출할 수 있다.

 

* 메서드 중에서 인스턴스와 관계없는 메서드를 클래스 메서드(static method)로 정의한다. 인스턴스 변수를 사용하지 않는다고 해서 반드시 클래스 메서드로 정의해야하는 것은 아니지만 일반적인 경우에 정의한다.

 

 

<정리>


 

3.12 클래스 멤버와 인스턴스 멤버간의 참조와 호출

* 같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능.

 

* 단, 클래스멤버가 인스턴스멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야 한다.

 

* 인스턴스 멤버가 존재하는 시점에 클래스 멤버는 항상 존재하지만, 클래스멤버가 존재하는 시점에 인스턴스멤버가 존재하지 않을 수도 있기 때문.

 

더보기

ex)

class MemberCall {
    int iv = 10;  // 인스턴스변수
    static int cv = 20;  // 클래스변수

    int iv2 = cv;
    // static int cv2 = iv;  // 에러. 클래스변수는 인스턴스변수를 사용할 수 없음
    static int cv2 = new MemberCall().iv; // 이처럼 객체를 생성해야 가능.

    static void staticMethod1() {
        System.out.println(cv);
        // System.out.println(iv); // 에러. 클래스메서드에서 인스턴스변수 사용불가
        MemberCall c = new MemberCall();
        System.out.println(c.iv); // 객체를 생성한 후 인스턴스 변수 참조가능.
    }

    void instanceMethod1() {
        System.out.println(cv);
        System.out.println(iv); // 인스턴스메서드에서는 인스턴스변수 바로 사용가능
    }

    static void staticMethod2() {
        staticMethod1();
        // instanceMethod1(); // 에러. 클래스메서드에서는 인스턴스메서드 호출 불가.
        MemberCall c = new MemberCall();
        c.instanceMethod1(); // 인스턴스 생성한 후 호출가능.
    }

    void instanceMethod2() { // 인스턴스메서드에서는 모두 바로 호출가능.
        staticMethod1();
        instanceMethod1();
    }
}

 

* 같은 클래스 내에서 클래스멤버가 인스턴스멤버를 참조 또는 호출하는 경우는 드물다. 만일 그런 경우가 발생한다면, 인스턴스메서드로 작성해야할 메서드를 클래스메서드로 작성한 것은 아닌지 한 번 더 생각해봐야 한다.


 

tip)

MemberCall c = new MemberCall();
int result = c.instanceMethod1();

위의 두 줄을 아래의 한 줄로 줄일 수 있다.

int result = new MemberCall().instanceMethod1();

   


 

4. 오버로딩 (overloading)

더보기

4.1 오버로딩이란?

* 메서드도 변수와 같이 같은 클래스 내에서 서로 구별되어야 하기에 각기 다른 이름을 가져야 한다.

 

* 그러나 자바에서는 한 클래스 내에 이미 사용하려는 이름과 같은 메서드가 있더라도 매개변수의 개수 또는 타입이 다르면, 같은 이름을 사용해서 메서드를 정의 가능.

 

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

 

* 하나의 메서드 이름으로 여러 기능을 구현하기 때문에 붙여진 이름이라 생각할 수 있다.


 

4.2 오버로딩의 조건

 

1. 메서드 이름이 같아야 한다.
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)

 

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

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

 

* 아래는 리턴타입만 다른 경우다. 매개변수의 타입과 개수가 일치하기 때문에 add(3,3)과 같이 호출했을 때 어떤 메서드가 호출된 것인지 결정할 수 없기 때문에 오버로딩으로 간주하지 않는다. 컴파일하면 'add(int,int) is already defined' 메시지가 뜬다.

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

 

* 아래는 리턴타입까지 같지만 순서가 다른 경우다. 호출 시 매개변수 값에 의에 호출될 메서드가 구분될 수 있으므로 중복된 메서드가 아닌, 오버로딩으로 간주한다.

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

 

4.4 오버로딩의 장점

* 메서드의 이름만 보고도 '이 메서드들은 이름이 같으니, 같은 기능을 하겠구나.' 라고 쉽게 예측 가능.

 

* 메서드의 이름 절약.


 

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

* 기존에는 메서드의 매개변수 개수가 고정적이었으나 JDK1.5부터 동적으로 지정할 수 있다.

 

* 이 기능을 가변인자라고 한다. '타입... 변수명'과 같은 형식으로 선언. printf()가 대표적인 예이다.

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

 

* 가변인자 외에도 매개변수가 더 있다면, 가변인자를 제일 마지막에 선언. 가변인자인지 아닌지 구별할 방법이 없기 때문.

 

* 가변인자는 내부적으로 배열을 이용하는 방식. 가변인자가 선언된 메서드를 호출할 때마다 배열이 새로 생성(비효율).


 

5. 생성자(Constructor)

더보기

5.1 생성자란?

* 인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메서드'이며 변수의 초기화 작업에 주로 사용

 

* 메서드처럼 클래스 내에 선언되며, 구조도 메서드와 유사하지만 리턴값이 없다는 점이 다르다.

 

* 생성자 조건

   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에 저장

 

* 즉, 지금까지 인스턴스를 생성하기 위해 사용했던 '클래스이름()'이 바로 생성자.


 

5.2 기본 생성자 (default constructor)

* 모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다. 생성자를 정의하지 않고도 인스턴스를 사용할 수 있었던 이유는 컴파일러가 제공하는 '기본 생성자' 덕분.

 

* 컴파일 할 때, 소스파일(.java)의 클래스에 생성자가 없어도 자동적으로 아래와 같은 내용의 기본 생성자를 추가.

클래스이름() { }
Card() { }

 

class Data1 {
    int value;
}

class Data2 {
    int value;
    
    Data2(int x) {  // 매개변수가 있는 생성자
        value = x;
    }
}
public class ConstructorTest {
    public static void main(String[] args) {
        Data1 d1 = new Data1();
        // Data2 d2 = new Data2(); // compile error
    }
}

 

* 위의 코드에서 Data1에는 정의되어 있는 생성자가 없기 때문에 컴파일러가 기본 생성자를 추가했지만, Data2에는 이미 생성자 Data2(int x)가 정의되어 있으므로 기본 생성자가 추가되지 않기 때문에 에러 발생.


 

5.3 매개변수가 있는 생성자

* 생성자도 메서드처럼 매개변수를 선언하여 호출 시 값을 넘겨받아서 인스턴스의 초기화 작업에 사용 가능.

 

* 매개변수가 있는 생성자 Car(St~~)를 사용한다면 인스턴스를 생성하는 동시에 원하는 값으로 초기화할 수 있다.

class Car {
    String color;
    String gearType;
    int door;

    Car() {}
    Car(String c, String g, int d) {
        color = c;
        gearType = g;
        door = d;
    }
}
public class CarTest {
    public static void main(String[] args) {
        Car c1 = new Car();
        c1.color = "white";
        c1.gearType = "auto";
        c1.door = 4;
        
        Car c2 = new Car("white", "auto", 4);
        
        System.out.println("c1의 color=" + c1.color + ", gearType=" + c1.gearType+ ", door="+c1.door);
        System.out.println("c2의 color=" + c2.color + ", gearType=" + c2.gearType+ ", door="+c2.door);
    }
}

 

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

* 같은 클래스 멤버들 간에 서로 호출할 수 있는 것처럼 생성자 간에도 서로 호출이 가능. 다음 두 조건을 만족해야 한다.

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

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

 

* 첫 줄에만 호출이 가능한 이유는 생성자 내에서 초기화 작업도중에 다른 생성자를 호출하면, 호출된 다른 생성자 내에서도 멤버변수들의 값을 초기화할 것이므로 다른 생성자를 호출하기 이전의 초기화 작업이 무의미해질 수 있기 때문.

더보기

* 'this'를 사용할 수 있는 것은 인스턴스멤버뿐이다. static메서드에서는 인스턴스 멤버들을 사용할 수 없는 것처럼 this 역시 사용할 수 없다. 생성 시기가 다를 수 있기 때문.

 

* this와 this()는 비슷하게 생겼을 뿐 완전히 다르다. this는 '참조변수'이고, this()는 '생성자'이다.


 

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

* 현재 사용 중인 인스턴스와 같은 상태를 갖는 인스턴스를 하나 더 만들고 싶을 때 생성자를 이용할 수 있다.

 

* 두 인스턴스가 같은 상태를 갖는다는 것은 두 인스턴스의 모든 인스턴스 변수가 동일한 값을 갖고 있다는 의미다.

 

* 인스턴스간의 차이는, 인스턴스마다 각기 다른 값을 가질 수 있는 인스턴스 변수 뿐이다.

 

Car(Car c) { 
	color = c.color;
    gearType = c.gearType;
    door = c.door;
}

 

* 위의 코드는 Car클래스의 참조변수를 매개변수로 선언한 생성자이다. Car인스턴스의 인스턴스 변수들을 자신으로 복사하는 것이다.

 

* 복사 이후, 서로 독립적으로 메모리 공간에 존재.

 

* 인스턴스를 생성할 때는 다음의 2가지 사항을 결정해야 한다.

    1. 클래스 - 어떤 클래스의 인스턴스를 생성할 것인가?

    2. 생성자 - 선택한 클래스의 어떤 생성자로 인스턴스를 생성할 것인가?

 


 

6. 변수의 초기화

더보기

 

6.1 변수의 초기화

* 가능한 선언과 동시에 적절한 값으로 초기화 하는 것이 바람직

 

* 멤버변수는 초기화를 하지 않아도 자동적으로 자료형에 맞는 기본값으로 초기화.

 

* 지역변수는 사용하기 전에 반드시 초기화를 해야한다.

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

 

* 멤버변수의 초기화 방법

    1. 명시적 초기화 (explicit initialization)

    2. 생성자 (constructor)

    3. 초기화 블럭 (initialization blocK)

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

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


 

6.2 명시적 초기화

* 변수를 선언과 동시에 초기화하는 것. 가장 기본적이면서 간단한 방법 

 

 6.3 초기화 블럭

* 인스턴스 초기화 블럭은 단순히 클래스 내에 블럭{}을 만들고 그 안에 코드를 작성하면 된다.

 

* 클래스 초기화 블럭은 인스턴스 초기화 블럭 앞에 static을 덧붙이기만 하면 된다.

class InitBlock {
	static { /* 클래스 초기화 블럭 */}
    
    { /* 인스턴스 초기화 블럭 */ }
    // ...
}

 

* 클래스 초기화 블럭은 클래스가 메모리에 처음 로딩될 때 한번만 수행되며, 인스턴스 초기화 블럭은 생성자와 같이 인스턴스를 생성할 때마다 수행.

 

* 생성자보다 인스턴스 초기화 블럭이 먼저 수행.

 

* 인스턴스 변수의 초기화는 주로 생성자를 사용하고, 인스턴스 초기화 블럭은 모든 생성자에서 공통으로 수행해야 하는 코드를 넣는데 사용.

 

* 코드의 중복을 제거하는 것은 신뢰성을 높여주고, 오류의 발생가능성을 줄인다. 즉, 재사용성을 높이고 중복을 제거.



 

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

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

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

 

* 아래의 코드는 제품을 생산할 때 제품마다 일련번호를 자동적으로 부여하는 프로그램이다.

class Product {
    static int count = 0;  // 생성된 인스턴스의 수를 저장하기 위한 변수
    int serailNo; // 인스턴스의 고유 번호

    {
        ++count;
        serailNo = count;  // Product인스턴스가 생성될 때마다 count의 값을 1씩 증가시켜서 serailNo에 저장
    }
    public Product() {} // 기본 생성자, 생략 가능
}
public class ProductTest {
    public static void main(String[] args) {
        Product p1 = new Product();
        Product p2 = new Product();
        Product p3 = new Product();

        System.out.println("p1 serial no is " + p1.serailNo);
        System.out.println("p2 serial no is " + p2.serailNo);
        System.out.println("p3 serial no is " + p3.serailNo);
        System.out.println("Total number of product is " + Product.count);
    }
}

 

* 아래의 코드는 이전의 일련번호 예제를 응용한 것으로, 문서의 이름을 지정하면 그 이름의 문서가 생성되지만, 이름을 지정하지 않을 경우 일정한 규칙에 의해 자동적으로 이름을 생성하는 프로그램이다.

class Document {
    static int count = 0;
    String name;

    Document() {
        this("None Title " + ++count);
    }

    Document(String name) {
        this.name = name;
        System.out.println("Document " + this.name + " is created.");
    }
}

public class DocumentTest {
    public static void main(String[] args) {
        Document d1 = new Document();
        Document d2 = new Document("Java.txt");
        Document d3 = new Document();
        Document d4 = new Document();

    }
}