Programming Language/Java

[Java] 06. OOP - 클래스와 객체

lumana 2024. 3. 12. 22:18

클래스와 객체

클래스와 객체의 정의

  • 클래스의 정의 : 객체를 정의해 놓은 것
  • 클래스의 용도 : 객체를 생성하는데 사용된다

객체와 인스턴스

  • 클래스의 인스턴스화(instantiate) : 클래스로부터 객체를 만드는 과정
  • 인스턴스 : 어떤 클래스로부터 만들어진 객체
  • ex) Tv클래스로부터 만들어진 객체를 Tv클래스의 인스턴스라고 함

인스턴스의 생성과 사용

클래스명 변수명; // 참조변수 선언
변수명 = new 클래스명();

Tv t;
t = new Tv();

객체 배열

  1. 객체 배열을 다루기 위한 참조 변수 배열을 생성
  2. 객체를 생성해서 배열의 각 요소에 저장
Tv[] tvArr = new Tv[3]; // 참조변수 배열을 생성
// 객체를 생성해서 배열의 각 요소에 저장
tvArr[0] = new Tv();
tvArr[1] = new Tv();
tvArr[2] = new Tv();

// 다음과 같이 배열의 초기화 블럭을 사용하면 한 줄로 간단히 할 수 있다.
Tv[] tvArr = { new Tv(), new Tv(), new Tv() };

// 객체의 수가 많으면 for문을 사용하자

변수와 메서드

  • 인스턴스 변수
    • 클래스 영역에서 선언됨
    • 클래스의 인스턴스를 생성할 때 만들어짐
  • 클래스 변수
    • 인스턴스 변수 앞에 static을 붙여 선언
    • 클래스 변수는 모든 인스턴스가 공통된 저장공간(변수)을 공유하게 된다.
    • 클래스 변수는 인스턴스변수와 달리 인스턴스를 생성하지 않고도 언제라도 바로 사용할 수 있음
    • 클래스이름.클래스변수와 같은 형식으로 사용
      • 참조변수를 통해 클래스변수를 사용할 수 있지만, 인스턴스 변수로 오해하기 쉬우므로 클래스이름.클래스변수 형식을 사용하자
    • 클래스가 메모리에 로딩될 때 생성되어 프로그램이 종료될 때 까지 유지됨
    • public을 앞에 붙이면 같은 프로그램 내에서 어디서나 접근할 수 있는 전역변수의 성격을 가짐

메서드

  • 같은 클래스 내의 메서드끼리는 참조변수를 사용하지 않고도 서로 호출이 가능하지만, static 메서드는 같은 클래스 내의 인스턴스 메서드를 호출할 수 없다.

JVM의 메모리구조

  • 메서드 영역(method area)
    • 프로그램 실행 중 어떤 클래스가 사용되면, JVM은 해당 클래스의 클래스파일(*.class)을 읽고 분석하여 클래스에 대한 정보를 클래스변수와 함께 이곳에 저장
  • 힙(heap)
    • 인스턴스가 생성되는 공간. 프로그램 실행 중 생성되는 인스턴스는 모두 이곳에 생성된다.
  • 호출 스택(call stack)
    • 메서드의 작업에 필요한 메모리 공간을 제공
    • 메서드가 호출되면 호출스택에 호출된 메서드를 위한 메모리가 할당됨
    • 이 메모리는 작업을 수행하는 동안 지역변수(매개변수 포함)들과 연산의 중간결과 등을 저장하는데 사용된다.
    • 메서드가 작업을 마치면 할당되었던 메모리 공간은 반환되어 비워진다.
    • 스택 구조로 함수들이 실행된다

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

  • 매개변수의 타입이 기본형(primitive type)일 때는 기본형 값이 복사됨
    • 변수의 값을 읽기만 할 수 있다.
  • 매개변수의 타입이 참조형(reference type)일 때는 인스턴스의 주소가 복사됨
    • 변수의 값을 읽고 변경할 수 있음
class Data { int x; }

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

        change(d);
        System.out.println("After change(d)");
        System.out.println("main() : x = " + d.x);
    }

    static void change(Data d) {    // 참조형 매개변수
        d.x = 1000;
        System.out.println("change() : x = " + d.x);
    }
}
  • 배열 또한 매개변수로 참조형 변수를 사용하면 값을 변경할 수 있다.
class ReferenceParam2 {
    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]);
    }
}

참조형 반환타입

  • 매개변수 뿐만 아니라 반환타입도 참조형이 될 수 있다. (객체의 주소를 반환)
class Data { int x; }

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 + 10;

        return tmp;
    }
}

클래스 메서드와 인스턴스 메서드

  • 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통적으로 사용해야 하는 것에 static을 붙인다
  • 클래스 변수는 인스턴스를 생성하지 않아도 사용할 수 있다
  • 클래스 메서드는 인스턴스 변수를 사용할 수 없다
  • 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다
    • 메서드 호출시간이 짧아지므로 성능이 향상된다

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

  • 같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다
  • 단, 클래스 멤버가 인스턴스 멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야 한다.
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.instanceMethod();

// 위 두 줄을 아래와 같이 한 줄로 할 수 있다.

int result = new MemberCall().instanceMethod();
// 다만 참조변수를 선언하지 않았기 때문에 생성된 MemberCall인스턴스는 더 이상 사용할 수 없다 

오버로딩

  • 기본적인 내용은 cpp와 동일

가변인자와 오버로딩

  • JDK 1.5부터 메서드의 매개변수 개수를 동적으로 지정해 줄 수 있게 됨
  • 이 기능을 가변인자 라고 한다
  • 가변인자는 '타입... 변수명'과 같은 형식으로 선언하며, printf()가 대표적인 예이다
  • 가변인자 이외에도 매개변수가 더 있다면, 가변인자를 매개변수 중에서 제일 마지막에 선언해야 한다
public PrintStream printf(String format, Object... args) { ... }
  • 가변인자는 내부적으로 배열을 이용함
    • 꼭 필요한 경우에만 가변인자를 사용하자
    • 가변인자 대신 배열을 사용하는 경우, 반드시 인자를 지정해 줘야하는 불편함이 있음
class VarArgsEx {
    public static void main(String[] args) {
        String[] strArr = { "100", "200", "300" };

        System.out.println(concatenate("", "100", "200", "300"));
        System.out.println(concatenate("-", strArr));
        System.out.println(concatenate(",", new String[]{"1", "2", "3"}));
        // 다음은 비허용
        // System.out.println(concatenate(",", {"1", "2", "3"}));
        System.out.println("[" + concatenate(",", new String[0]) + "]");
        System.out.println("[" + concatenate(",") + "]");
    }

    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);
    }
*/
}    // class

생성자

  • 인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메서드'
  • Cpp와 유사
  • 연산자 new가 인스턴스를 생성하는거지, 생성자가 인스턴스를 생성하는 것이 아님
  • 기본 생성자가 컴파일러에 의해서 추가되는 경우는 클래스에 정의된 생성자가 하나도 없을 때 뿐임

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

  • 생성자 간에도 서로 호출이 가능함
  • 조건
    • 생성자의 이름으로 클래스이름 대신 this를 사용한다
    • 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.
  • 잘못된 예시
Car(String color) {
    door = 5; // 첫 번째 줄
    Car(color, "auto", 4); // 에러1. 생성자의 두 번째 줄에서 다른 생성자 호출
                            // 에러2. this(color, "auto", 4);로 해야함
}

Car(String color, String gearType, int door) {
    this.color = color;
    this.gearType = gearType;
    this.door = door;
}
  • this 는 참조변수로 인스턴스 자신을 가리키고, 이를 통해 인스턴스 변수에 접근할 수 있다.
    • this : 인스턴스 자신을 가리키는 참조변수(주소가 저장되어 있음)
    • this(), this(argument) : 생성자, 같은 클래스의 다른 생성자를 호출할 때 사용함
  • static 메서드에서는 this 역시 사용할 수 없다

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

  • 생성자를 이용해 현재 사용하고 있는 인스턴스와 같은 상태를 갖는 인스턴스를 하나 더 만들고자 할 때 생성자를 이용할 수 있다.
class CarTest3 {
    public static void main(String[] args) {
        Car c1 = new Car();
        Car c2 = new Car(c1);    // c1의 복사본 c2 생성
        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);
        c1.door = 100;            // c1의 인스턴스변수 door의 값 변경
        System.out.println("c1.door = 100; 수행 후");
        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);
    }
}

class Car {
    String color;        // 색상
    String gearType;    // 변속기 종류 - auto(자동), manual(수동)
    int door;            // 문의 개수

    Car() {
        this("white", "auto", 4);
    }

    Car(String color, String gearType, int door) {
        this.color = color;
        this.gearType = gearType;
        this.door = door;
    }

    Car(Car c) {        // 인스턴스의 복사를 위한 생성자
//        this.color = c.color;    // this 붙여도 되고 안붙여도 됨
//        gearType = c.gearType;
//        door = c.door;
        /* 아래와 같이 기존의 코드를 활용하는 게 바람직 */
        this(c.color, c.gearType, c.door);
    }
}

변수의 초기화

  • 멤버변수는 초기화를 하지 않아도 자동적으로 변수의 자료형에 맞는 기본값으로 초기화가 이루어짐
  • 지역변수는 사용하기 전에 반드시 초기화해야 한다
class InitTest {
    int x;
    int y = x;

    void method() {
        int i; // 지역 변수
        int j = i; // 에러, 지역변수를 초기화 하지 않고 사용
    }
}
  • 멤버변수의 초기화 방법에는 여러 가지가 있음
  1. 명시적 초기화
  2. 생성자
  3. 초기화 블럭 (인스턴스 초기화 블럭, 클래스 초기화 블럭)

명시적 초기화

  • 변수를 선언과 동시에 초기화 하는 것을 명시적 초기화라고 함
class Car {
    int door = 4; // 기본형 변수의 초기화
    Engine e = new Engine(); // 참조형 변수의 초기화
}
  • 복잡한 초기화 작업이 필요할 때는 '초기화 블럭' 또는 생성자를 사용해야 함

초기화 블럭

  • 인스턴스 초기화 블럭은 단순히 클래스 내에 블럭{}을 만들고 그 안에 코드를 작성하기만 하면 된다
  • 클래스 초기화 블럭은 인스턴스 초기화 블럭 앞에 단순히 static만 덧붙이자
  • 초기화 블럭 내에서는 메서드 내에서와 같이 조건문, 반복문, 예외처리구문 등을 자유롭게 사용할 수 있음
class InitBlock {
    static { /* 클래스 초기화 블럭 */}

    { /* 인스턴스 초기화 블럭*/ }

    //...
}
class StaticBlockTest {
    static int[] arr = new int[10];

    static {
        for (int i=0; i<arr.length; i++) {
            // 1과 10 사이의 임의의 값을 배열 arr에 저장
            arr[i] = (int)(Math.random()*10) + 1;
        }
    }

    public static void main(String args[]) {
        for (int i=0; i<arr.length; i++)
            System.out.println("arr["+i+"] : " + arr[i]);
    }
}
  • 인스턴스 초기화 블럭에서는 모든 생성장에서 공통으로 수행되야 하는 코드를 넣는데 사용한다

참조) Java의 정석 3rd edition(남궁성, 도우출판)

'Programming Language > Java' 카테고리의 다른 글

[Java] 08. OOP - 다형성(polymorphism)  (0) 2024.06.24
[Java] 07. OOP - 상속과 접근제어자  (0) 2024.03.12
[Java] 05. 배열  (0) 2024.03.12
[Java] 04. 변수  (0) 2024.03.12
[Java] 03. 화면에서 입력받기  (0) 2024.03.12