Java 재활 훈련 3일차 - Class

0

java

목록 보기
3/18

클래스

클래스는 하나의 틀이고, 클래스를 통해 만든 것들이 '객체(instance)'이다. 객체는 속성과 동작으로 구성되는데, '속성'은 객체의 상태를 나타내고, '동작'은 객체의 행동을 나타내준다.

객체는 단독으로 존재할 수 있지만 객체들끼리의 관계를 통해서 더 다양한 현실 세계의 관계들을 표현할 수 있다. 객체 간의 관계의 종류는 '집합 관계', '사용 관계', '상송 관계'가 있다.

  1. 집합 관계: 완성품과 부품의 관계로 자동차는 엔진, 타이어, 핸들 등으로 구성되므로 자동차와 부품들은 집합 관계라고 볼 수 있다.
  2. 사용 관계: 다른 객체의 속성을 읽고 변경하거나, 메서드를 호출하는 관계를 말한다. 가령 사람이 자동차를 사용하여, '달린다' , '멈춘다' 등의 메서드를 호출하면 자동차는 사용 관계라고 볼 수 있다.
  3. 상속 관계: 부모와 자식의 관계로, 부모의 특질을 물려받아 자식이 해당 특질을 사용할 수 있다. 또한, 자식은 해당 특질을 자식만의 특질로 변형하거나 새로운 특질을 형성할 수 있다.

객체 지향 프로그래밍의 특징

  1. 캡슐화: 객체의 데이터(속성), 메서드(동작)을 하나로 묶고 실제 구현 내용을 외부에 감추는 것을 말한다. 객체 내부의 구조를 알지 못하게 하며, 객체에서 노출이 필요한 데이터들에 대해서는 메서드를 통해 제한된 접근을 가능하게 한다.
  2. 상속: 상속은 부모 객체의 메서드와 데이터(속성)을 자식에게 물려주는 것을 말한다. 이를 통해서 코드의 재사용성을 높이고, 유지 보수 시간을 최소화시켜 줄 수 있다.
  3. 다형성: 다형성이란 사용 방법은 동일하지만, 실행 결과가 다양하게 나오는 성질을 말한다. 가령, 동일한 자동차이고 운전 방법도 동일하지만, 내부적으로 타이어나 엔진을 바꾸어도 그 사용방법은 달라지지 않는 것을 말한다.
  4. 추상화: 객체의 복잡한 세부 구현은 감추고, 사용자에게 어떤 기능을 사용할 수 있는 지에 대해서만 알려주는 것이다. 이는 복잡한 시스템을 '추상화'하여 구체적인 특성에 대해서만 외부에 노출시키는 방법으로, '어떤' 것을 사용자에게 제공할 지가 중심이다. 반면에 캡슐화는 데이터와 메서드를 하나로 묶어서 '어떻게' 데이터를 보호하고 무결성을 유지할 지가 중심이다.

클래스 사용

Title.java라는 class file을 하나 만들어보자.

다음과 같이 하나의 file에 두 개의 java class를 만들 수 있다.

public class Title {
}

class SubTitle {
    
}

'public class'는 파일 이름과 동일한 class이름으로 정의해야하며, 하나만 존재할 수 있다. 즉, 파일 하나 당 하나의 public class만 허용하고, 이 클래스는 파일 이름과 동일해야한다는 것이다.

  • 객체의 생성
Title title = new Title();

클래스의 요소

클래스 내부의 구성 요소는 크게 3가지로 구성된다. 하나는 객체의 상태를 나타내는 'field' 또는 '맴버 변수'이고 두번째는 객체의 동작을 나타내는 'method'이다. 마지막 하나는 객체의 초기 동작을 지시하는 생성자, 'constructor'이다.

public class Title {
    // field, member
    String titleName;

    // 생성자, constructor
    Title(String titleName) {
        this.titleName = titleName;
    }

    // method
    String getTitleName() {
        return this.titleName;
    }
}

참고로 this는 객체 자기 자신을 말한다. 이는 객체 내부에서 객체 자기 자신이 가진 요소들에 접근할 때 사용한다.

  1. field(member 변수): 객체의 상태를 나타내는 값으로 데이터를 저장하는 역할을 한다. 선언 형태는 변수 선언과 비슷하지만 쓰임새는 다르다.
  2. constructor(생성자): new 연산자로 객체를 생성할 때 객체의 초기화 역할을 담당한다. 선언 형태는 메서드와 비슷하지만 리턴 타입도 없고 이름은 클래스 이름과 동일하다.
  3. method: 메서드는 객체가 수행할 동작을 말한다. 함수처럼 보이지만, 객체 내부에 종속된 함수이기 때문에 메서드라고 불린다. 메서드를 통해서 객체와 객체 간의 상호작용이 가능한 것이다.

참고로 field명과 method는 소문자로 카멜 케이스 작성이 관례이다.

모든 클래스는 생성자가 존재하며, 하나 이상을 가질 수 없다. 클래스에 생성자 선언이 없으면 다음과 같이 기본 생성자를 바이트 코드 파일에 자동으로 추가시킨다.

[public] 클래스명() {}

'클래스명'이 'public'으로 선언되면 'public'이 붙고, 아니면 안 붙는다.

참고로 생성자는 여러 생성자 함수들을 정의할 수 있다. 즉, 오버로딩이 가능하다는 것이다. 오버로딩이란 매개변수의 타입과 갯수가 다른 생성자 함수라면 여러 개를 선언할 수 있다는 것이다.

public class Title {
    // field, member
    String titleName;

    // constructor1
    Title() {
        this.titleName = "title";
    }

    // constructor2
    Title(String titleName) {
        this.titleName = titleName;
    }

    // constructor3
    Title(int titleName) {
        this.titleName = String.valueOf(titleName);
    }

    // constructor4
    Title(String titleName, String subTitle) {
        this.titleName = titleName + " " + subTitle;
    }
}

다음과 같이 여러 생성자를 생성할 수 있는데 매개변수의 갯수와 타입이 다르면 여러 생성자들을 생성할 수 있다. 이를 오버로딩이라고 한다.

생성자 오버로딩을 사용하다보면, 반복되는 코드들이 있는데 이를 this를 사용해서 다음과 같이 최적화할 수 있다.

public class Title {
    // field, member
    String titleName;
    String author;

    // constructor2
    Title(String titleName) {
        this.titleName = titleName;
    }

    // constructor4
    Title(String titleName, String author) {
        this(titleName);
        this.author = author;
    }
}

this를 통해서 다른 생성자를 호출할 수 있는 것이다. 이를 통해서 반복되는 코드를 효율적으로 관리할 수 있다.

메서드를 호출할 때에는 매개변수의 개수에 맞게 매개값을 제공해야하는데, 만약 메서드가 가변 길이 매개변수를 가지고 있다면 매개변수의 개수와 상관없이 매개값을 줄 수 있다. 가변길이 매개변수는 다음과 같이 선언한다.

int sum(int ...value) {
    
}

가변길이 매개변수는 메서드 호출 시 매개값을 쉼표로 구분해서 개수와 상관없이 제공할 수 있다.

int res = sum(1,2,3);
int res = sum(1,2,3,4,5);

가변 길이 매개값들은 자동으로 배열 항목으로 변환되어 메서드에서 사용되는 것이다. 따라서, 배열에서 사용하는 메서드와 사용 방식들이 똑같이 적용된다.

public class Computer {
    int sum(int ...values) {  // int[] values와 같다.
        int sum = 0;
        for (int v: values) {
            sum += v;
        }
        return sum;
    }
}

생성자와 마찬가지로 메서드도 오버로딩이 가능하다. 오버로딩은 매개변수의 갯수, 타입과 관련이 있는 것이지 리턴값과는 관련이 없다는 것에 유념하자.

인스턴스 맴버와 정적 맴버 변수

field와 method는 선언 방법에 따라 인스턴스 맴버와 정적 맴버로 분류할 수 있다. 인스턴스 맴버로 선언되면 객체 생성 후 사용할 수 있고, 정적 맴버로 선언되면 객체 생성없이도 클래스를 통해 사용이 가능하다.

  1. 인스턴스 맴버: 객체에 소속된 맴버
  2. 정적(static) 맴버: 클래스에 고정된 맴버

인스턴스 맴버는 객체에 소속된 맴버를 말한다. 따라서 객체가 있어야만 사용할 수 있다.

public class Car {
    int gas;
}

class TestCar {
    void test() {
        Car car1 = new Car();
        Car car2 = new Car();
        car1.gas = 100;
        car2.gas = 200;
    }
}

car1car2는 서로 다른 객체로 gas의 값도 서로 다른 값을 가진다. 이것이 객체에 소속된 인스턴스 맴버 변수이다.

자바는 클래스 로더(loader)를 이용해서 클래스를 메모리의 '메서드 영역'에 저장하고 사용한다. 정적(static) 맴버란 메서드 영역에 저장된 클래스에서 고정적으로 위치하는 맴버들을 말한다. 따라서, 정적으로 이미 만들어져있기 때문에 객체를 생성할 필요없이 클래스를 통해 바로 사용이 가능하다.

class file --bytecode 로딩--> class loader --bytecode 저장--> method 영역(static field, static method)

field와 method 모두 정적 맴버가 될 수 있다. 정적 필드와 정적 메서드로 선언하려면 다음과 같이 static 키워드를 추가하면 된다.

public class Car {
    static int gas;
    static void setGas(int gas) {
        Car.gas = gas;
    }
}

public class Main {
    public static void main(String[] args) {
        Car.setGas(10);
        System.out.println(Car.gas); // 10
    }
}

Car 클래스 객체를 생성하지 않고도 static field인 gas와 static method인 setGas에 접근할 수 있다.

static field와 static method는 다음과 같이 객체를 통해서 접근할 수도 있다.

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        car.setGas(10);
        System.out.println(car.gas); // 10
    }
}

그러나, 이는 별로 좋지 못한 방법이므로 static field, static method는 반드시 클래스 이름을 통해서 접근하는 것이 좋다.

정적 field는 일반적으로 선언과 동시에 초기화를 한다.

public class Car {
    static int capacity = 10;
    static int gas = capacity / 2;
    
    static void setGas(int gas) {
        Car.gas = gas;
    }
}

그렇지만, 상당히 긴 계산이 필요하다면 다음과 같이 static {} block을 만들 수 있다. 단, 정적 영역이기 때문에 this는 쓸 수 없다.

public class Car {
    static int capacity = 10;
    static int gas;
    static {
        String gasType = System.getenv("GAS_TYPE");
        switch (gasType) {
            case "TYPE1":
                gas = 100 * capacity;
            case "TYPE2":
                gas = 200 * capacity;
            default:
                gas = 10 * capacity;
        }
    }

    static void setGas(int gas) {
        Car.gas = gas;
    }
}

참고로 static field들은 위에서부터 순서대로 평가된다고 생각하면 된다.

main함수도 정적 메서드이기 때문에, 클래스 자체에 저장되고 실행되는 코드이다. 따라서, Main 클래스 내부의 맴버 변수를 선언해도 main에서 접근하지 못한다.

final

인스턴스 필드와 정적 필드는 언제든지 값을 변경할 수 있다. 그러나, 경우에 따라서는 읽기 전용으로 만들고 싶을 때도 있다. 이때는 final을 사용해야 한다.

final 타입 필드; 

또는

final 타입 필드 = 초기값; 

final 필드에 초기값을 줄 수 있는 방법은 다음 두 가지 방법 밖에 없다.
1. field 선언 시에 초기값 대입
2. 생성자에서 초기값 대입

계산이 필요없는 값이라면 선언 시에 바로 주는 것이 가장 좋다. 하지만 복잡한 초기화 코드가 필요하거나 객체 생성 시 외부에서 전달된 값을 의존하여 초기화되어야 한다면 생성자에서 해야한다. 만약 final을 그 어디에서도 값을 넣어주지 않으면 컴파일 에러가 발생한다.

public class Car {
    final int gas = 10;
    final int capacity;

    Car(int capacity) {
        this.capacity = capacity;
    }
}

gascapacity는 둘 다 final이므로 선언과 동시에 초기화되어야하거나 생성자에서 계산되어야 한다.

이제 해당 맴버 변수들은 수정이 불가능하다.

final은 객체에 속하는 맴버 변수로 쓰이기 때문에 객체가 있어야만 사용할 수 있다. 그런데, 선언과 동시에 초기화되는 값은 일반적으로 고정된 값으로 모든 객체들이 공유해서 쓰이는 요소이다. 따라서, 클래스에서 사용하는 정적 변수로 선언되는 것이 좋다. 이렇게 되면 static final이 되며, 이는 마치 다른 언어에서 상수를 선언하는 const와 일맥상통하다.

public class Car {
    static final int gas = 10;
    final int capacity;

    Car(int capacity) {
        this.capacity = capacity;
    }
}

public class Main {
    public static void main(String[] args) {
        Car car = new Car(20);
        System.out.println(Car.gas); // 10
        System.out.println(car.capacity); // 20
    }
}

static final을 하나의 상수처럼 쓸 수 있다는 사실을 알도록 하자.

더불어 finalstatic과 함께 쓰이면 두 가지 방법으로 대입이 가능하다는 사실을 알도록 하자.
1. 선언과 동시에 초기화
2. static block 사용

static block 사용은 다음과 같다.

public class Car {
    // 정적 변수
    static final int gas;
    static {
        String gasType = System.getenv("GAS_TYPE");
        if (gasType.equals("TYPE1")) {
            gas = 100;
        } else {
            gas = 10;
        }
    }

    // 인스턴스 변수
    final int capacity;

    Car(int capacity) {
        this.capacity = capacity;
    }
}

패키지

자바의 패키지는 단순히 디렉토리만을 의미하지 않는다. 패키지는 클래스의 일부분이먀, 클래스를 식별하는 용도로 사용된다.

패키지는 주로 개발 회사의 도메인 이름의 역순으로 만든다. 가령, mycompany.com 회사의 패키지는 com.mycompany로 만든다. 이렇게 하면 두 회사에서 개발한 Car class가 있을 경우 다음과 같이 관리할 수 있다.

com
 |
 |------mycompany
 |        |
 |        ------ Car.class
 |
 |------yourcompany
 |        |
 |        ------ Car.class

패키지는 상위 패키지와 하위 패키지를 .로 구분한다. .는 물리적으로 하위 디렉토리임을 뜻한다. com.mycompany에서 com은 상위 디렉토리, mycompany는 하위 디렉터리이다.

패키지는 클래스를 식별하는 용도이기 때문에 클래스의 전체 이름에 포함된다. 가령, Car class의 경우 com.mycompany.Car이 된다. 따라서, com.yourcompany.Car과는 서로 다른 클래스임을 알 수 있다.

패키지에 속한 바이트코드 파일(~.class)은 따로 떼어내서 다른 디렉토리로 이동시킬 수 없다.

패캐지 디렉터리는 클래스를 컴파일하는 과정에서 자동으로 생성된다. 컴파일러는 클래스의 패키지 선언을 보고 디렉터리를 자동 생성시킨다. 패키지 선언은 package 키워드와 함께 패키지 이름을 기술한 것으로 항상 소스 파일 최상단 위에 위치해야한다.

package 상위패키지.하위패키지;

public class 클래스명 {...}

패키지 이름은 모두 소문자로 작성하는 것이 관례이다. 패키지 이름을 적을 때 마지막은 프로젝트 이름을 붙여주는 것이 일반적이다.

coms.samsung.projectname
coms.lg.projectname
org.apache.projectname

같은 패키지에 있는 클래스는 아무런 조건없이 사용할 수 있지만, 다른 패키지에 있는 클래스를 사용하려면 import문을 사용하여 어떤 패키지의 클래스를 사용하는 지 명시해야한다.

다음은 com.mycompany 패키지의 Car 클래스의 com.hankook패캐지의 Tier 클래스를 사용하기 위해 import문을 사용한 것이다.

package com.mycompany;

import com.hankook.Tire;

public class Car {
    Tier tier = new Tier();
}

만역 동일한 패키지에 포함된 다수의 클래스를 사용해야한다면 클래스 이름을 생략하고 *을 사용할 수 있다.

import com.hankook.*;

import문은 하위 패키지를 포함하지 않는다. 따라서, com.hankook 패키지에 있는 클래스도 사용해야 하고, com.hankook.project 패키지에 있는 클래스도 사용해야 한다면 다음과 같이 두 개의 import문이 필요하다.

import com.hankook.*;
import com.hankook.project.*;

단, 위와 같이 *을 쓸 경우 두 패키지에 동일한 클래스 명이 있을 수 있다. 이때는 컴파일에러가 발생하므로 명확히 하나를 지정해주어야 한다.

접근 제한자

경우에 따라서 객체의 필드를 외부에서 변경하거나 메서드를 호출할 수 없도록 막아야 할 필요가 있다. 중요한 필드와 메서드는 외부로 노출되지 않도록 해서 객체의 무결성을 유지하기 위함이다.

이러한 기능을 위해서 접근 제한자(access modifier)를 사용할 수 있다. 접근 제한자는 public, protected, private, default가 있다.

접근 제한이 강화되는 순서는 다음과 같다. 오른쪽으로 갈수록 더 접근 제한이 강해지는 것이다.

public < protected < default < private
접근 제한자제한 대상제한 범위
public클래스, 필드, 생성자, 메서드없음
protected필드, 생성자, 메서드같은 패키지 이거나, 자식 객체만 사용 가능
default클래스, 필드, 생성자, 메서드같은 패키지
private필드, 생성자, 메서드객체 내부

protected는 추후에 더 알아보도록 하고, publicdefault, private 접근 제한자를 class, 생성자, field, method` 순서로 어떻게 적용되는 지 알아보도록 하자.

class 접근 제한자

제일 먼저 class의 경우는 접근 제한자로 publicdefault만 쓸 수 있다. private 클래스와 protected 클래스는 없다.

public 접근 제한자가 붙은 class는 같은 패키지뿐만 아니라, 다른 패키지에서도 사용할 수 있다. 만약 어떠한 접근 제한자도 안붙이면 default 접근 제한자가 사용된다. 이 경우 클래스는 같은 패키지에서는 아무런 제한 없이 사용할 수 있지만, 다른 패키지에서는 사용할 수 없다.

package1을 만들고 B.java에 다음의 코드를 넣어보도록 하자.

package package1;

public class B {
    A a; // 접근 가능
}

class A {

}

A class는 default 접근자를 가지고 B class는 public 접근자를 갖는다. 따라서, B class는 다른 패키지에서도 접근 가능하지만, A class는 B.java가 있는 패키지 안에서만 접근 가능하고, 외부 패키지에서는 접근이 불가능하다.

import package1.B;

public class Main {
    public static void main(String[] args) {
        B b;
        A a; // error
    }
}

B class는 가지고 올 수 있지만, A는 못 가져온다.

생성자 접근 제한자

생성자에도 접근 제한자를 두어서, 호출 가능 여부를 따질 수 있다. 생성자는 public, default, private, protected 접근 제한자를 가질 수 있다. protected는 추후에 알아보자.

  1. public 생성자: 모든 패키지에서 생성자를 호출할 수 있다. 이는 모든 패키지에서 객체를 생성할 수 있다는 말이다.
  2. default 생성자: 같은 패키지에서만 생성자를 호출할 수 있다. 따라서 같은 패키지에서만 객체를 생성할 수 있다.
  3. private 생성자: 클래스 내부에서만 생성자를 호출할 수 있다. 이는 클래스 내부에서만 객체를 생성할 수 있다.

package1을 만들고, A.java에 다음의 코드를 입력하도록 하자.

package package1;

public class A {
    A a1 = new A(true);
    A a2 = new A(1);
    A a3 = new A("문자열");

    public A(boolean b) {

    }

    A(int b) {

    }

    private A(String s) {

    }
}

public, default, private 생성자 모두 클래스 내부에서는 호출이 가능한 것을 볼 수 있다. 그러나 동일한 패키지의 다른 클래스에서는 private 생성자는 호출이 불가능하다.

같은 pakcage1B.java파일을 만들고 다음의 코드를 입력하도록 하자.

package package1;

public class B {
    A a1 = new A(true);
    A a2 = new A(1);
    A a3 = new A("문자열"); // error
}

new A("문자열");은 호출이 안되는 것을 볼 수 있다. 이는 private 생성자의 경우 class 내부에서만 호출이 가능하기 때문이다. 반면에 publicdefault 생성자는 같은 패키지 내부에서는 호출이 가능하다.

그럼 이제 다른 패키지에서 A class의 생성자를 호출해보도록 하자.

import package1.A;

public class Main {
    public static void main(String[] args) {
        A a1 = new A(true);
        A a2 = new A(1); // error
        A a3 = new A("문자열"); // error
    }
}

public 생성자 이외의 default, private 생성자는 호출이 불가능한 것을 볼 수 있다.

field와 method 접근 제한자

field와 method는 public, default, private, protected 접근 제한자를 가질 수 있다. protected는 추후에 알아보자.

  1. public field or method: 모든 패키지에서 field에 읽고 변경할 수 있다. 모든 패키지에서 method를 호출할 수 있다.
  2. default field or method: 같은 패키지에서만 field를 읽고 변경할 수 있다. 같은 패키지에서만 method를 호출할 수 있다.
  3. private field or method: 클래스 내부에서만 field를 읽고 변경할 수 있다. 클래스 내부에서만 method를 호출할 수 있다.

package1를 만들고 A.java에 다음의 코드를 입력하자.

  • A.java
package package1;

public class A {
    // public 접근 제한을 갖는 필드 선언
    public int field1;
    // default 접근 제한을 갖는 필드 선언
    int field2;
    // private 접근 제한을 갖는 필드 선언
    private int field3;

    public void exec() {
        field1 = 1; // 접근 가능
        field2 = 2; // 접근 가능
        field3 = 3; // 접근 가능

        method1(); // 접근 가능
        method2(); // 접근 가능
        method3(); // 접근 가능
    }

    // public 접근 제한을 갖는 메서드 선언
    public void method1() {

    }
    // default 접근 제한을 갖는 메서드 선언
    void method2() {

    }
    // private 접근 제한을 갖는 메서드 선언
    private void method3() {

    }
}

클래스 코드 내부에서는 public, default, private 접근 제한자로 만든 fieldmethod들에 대해서 접근이 가능한 것을 볼 수 있다.

반면에 같은 패키지의 다른 클래스인 B.java에서는 private 접근 제한자가 붙은 field와 method에 대해서는 접근이 불가능하다.

package package1;

public class B {
    public void method() {
        A a = new A();

        a.field1 = 1; // 접근 가능
        a.field2 = 2; // 접근 가능
        a.field3 = 3; // 접근 불가능

        a.method1(); // 접근 가능
        a.method2(); // 접근 가능
        a.method3(); // 접근 불가능
    }
}

다른 패키지에서는 public빼고는 defaultprivate말고는 접근이 불가능하다.

import package1.A;

public class Main {
    public static void main(String[] args) {
        A a = new A();

        a.field1 = 1; // 접근 가능
        a.field2 = 2; // 접근 불가능
        a.field3 = 3; // 접근 불가능

        a.method1(); // 접근 가능
        a.method2(); // 접근 불가능
        a.method3(); // 접근 불가능
    }
}

정리하자면 생성자와 field, method의 접근 제한자 허용 범위는 다음과 같다.
1. 클래스 코드 내부: public, default, private
2. 패키지 내부: public, default
3. 패키지 외부: public

클래스의 경우는 public과 default만 가능하므로 다음과 같다.
1. 패키지 내부: default
2. 패키지 외부: public

싱글톤 패턴

private 접근 제한자를 생성자에 사용하면 클래스 코드 내부에서만 사용할 수 있었다. 이는 클래스 코드 내부에 생성자를 private 하나만 두게 될 경우, 같은 패키지나 외부 패키지에서 해당 클래스의 생성자를 호출하지 못하여 해당 객체의 생성이 불가능하다.

application 전체에서 단 한 개의 객체만을 생성해서 사용하고 싶다면 싱글톤 패턴을 사용할 수 있다. 싱글톤 패턴은 생성자를 private접근 제한자를 사용해서 new 연산자로 생성자를 호출할 수 없도록 하는 것이다.

생성자를 호출할 수 없으니 마음대로 객체를 생성할 수가 없다. 대신 싱글톤 패턴으로 정적 메서드를 통해서 객체를 얻도록 하는 것이다.

public class 클래스이름 {
    private static 클래스이름 instance = new 클래스이름();
    
    private 클래스이름() {
        
    }
    
    public static 클래스이름 getInstance() {
        return instance;
    }
}

클래스의 정적 맴버 변수로 해당 클래스 타입인 instance를 갖도록 하는 것이다. 이 instanceprivate 생성자를 통해 초기에 생성되는 것이다. 해당 클래스는 private 생성자가 정의되어 있기 때문에 같은 패키지나 외부 패키지에서 생성이 불가능하다. 즉, 처음 자바 바이트코드가 로팅되고 해당 클래스를 메서드 영역에 적재할 때, 생성된 정적 instance가 유일한 것이다.

instance에 접근하기 위해서는 정적 메서드인 getInstance로만 가능한 것이다.

0개의 댓글