6장 클래스
Updated:
객체지향 프로그래밍
소프트웨어를 개발할 때 부품에 해당하는 객체들을 먼저 만들고, 이 객체들을 하나씩 조립해서 완성된 프로그램을 만드는 기법을 객체지향 프로그래밍(Object Oriented Programming, OOP)이라고 한다.
- 객체(Object): 물리적으로 존재하거나 개념적인 것 중에서 다른 것과 식별 가능한 것
- 필드(field): 속성
- 메소드(method): 동작
- 객체 모델링(Object modeling): 현실 세계의 객체를 소프트웨어 객체로 설계하는 것
객체는 단독으로 존재할 수 있지만 대부분 다른 객체와 관계를 맺고 있다.
- 집합 관계: 완성품과 부품의 관계
- 사용 관계: 다른 객체의 필드를 읽고 변경하거나 메소드를 호출하는 관계
- 상속 관계: 부모와 자식 관계
객체지향 프로그래밍의 특징
- 캡슐화(Encapsulation)
- 객체의 데이터(필드), 동작(메소드)을 하나로 묶고 실제 구현 내용을 외부에 감추는 것
- 외부 객체는 객체 내부의 구조를 알지 못하며 객체가 노출해서 제공하는 필드와 메소드만 이용 가능
- 상속(Inheritance)
- 부모 객체가 자기가 가지고 있는 필드와 메소드를 자식 객체에게 물려주어 자식 객체가 사용할 수 있도록 하는 것
- 코드의 재사용성을 높여 줌: 중복 코딩 방지
- 유지 보수 시간을 최소화: 부모 객체의 필드와 메소드를 수정 가능
- 다형성(Polymorphism)
- 사용 방법은 동일하지만 실행 결과가 다양하게 나오는 성질
- 자동 타입 변환과 재정의 기술 필요
객체와 클래스
객체지향 프로그래밍에서 객체를 생성하려면 클래스(Class)가 필요하다. 클래스로부터 생성된 객체를 해당 클래스의 인스턴스(Instance)라고 부른다. 그리고 클래스로부터 객체를 만드느 과정을 인스턴스화라고 한다.
클래스 선언
- 클래스 선언: 어떻게 객체를 생성(생성자)하고, 객체가 가져야 할 데이터(필드)가 무엇이고, 객체의 동작(메소드)은 무엇이지를 정의하는 내용이 포함
- 클래스 선언은 소스 파일명과 동일하게 작성
// [클래스명.java] public class 클래스명 { }
- public class: 공개 클래스 선언
- 클래스명은 첫 문자를 대문자로 하고 캐멀 스타일로 작성
- 하나의 소스파일은 다음과 같이 복수 개의 클래스 선언을 포함할 수 있다.
public class SportsCar { } class Tire { }
- 복수 개의 클래스 선언이 포함된 소스파일을 컴파일하면 바이트코드 파일은 클래스 선언 수만큼 생긴다.
- 복수 개의 클래스를 선언할 때 주의할 점은 소스 파일명과 동일한 클래스만 공개 클래스로 선언 가능능
객체 생성과 클래스 변수
- 클래스로부터 객체를 생성하려면 객체 생성 연산자인 new가 필요
- new 클래스()
- 클래스 변수 = new 클래스();
클래스의 구성과 멤버
클래스 선언에는 객체 초기화 역할을 담당하는 생성자와 객체에 포함될 필드와 메소드를 선언하는 코드가 포함된다. 그래서 생성자, 필드, 메소드를 구성 멤버라고 한다.
public class ClassName {
// 필드 선언
int fieldName;
// 생성자 선언
ClassName() {
// ...
}
// 메소드 선언
int methodName() {
// ...
}
}
- 필드(Field)
- 객체의 데이터를 저장
- 생성자(Constructor)
- new 연산자로 객체를 생성할 때 객체의 초기화 역할
- 선언 형태는 메소드와 비슷하지만, 리턴 타입이 없고 이름은 클래스 이름과 동일
- 메소드(Method)
- 객체가 수행할 동작작
필드 선언과 사용
- 필드를 선언하는 방법은 변수를 선언하는 방법과 동일(단, 반드시 클래스 블록에서 선언)
- 타입 필드명 [ = 초기값];
- 타입은 기본 타입과 참조 타입이 모두 가능
- 초기값을 제공하지 않을 경우 필드는 객체 생성 시 자동으로 기본값으로 초기화
- 필드 사용: 필드값을 읽고 변경하는 것
- 클래스로부터 객체가 생성된 후에 필드를 사용할 수 있다.
- 필드는 객체 내부의 생성자와 메소드 내부에서 사용할 수 있고, 객체 외부에서도 접근해서 사용할 수 있다.
- 외부 객체에서는 참조 변수와 도트 연산자를 이용해 필드를 읽고 변경해야 한다.
// [Car.java]
public class Car {
int speed;
}
// [CarExample.java]
public class CarExample {
public static void main(String[] args) {
Car myCar = new Car();
System.out.println("현재속도: " + myCar.speed); // 현재속도: 0
myCar.speed = 60;
System.out.println("현재속도: " + myCar.speed); // 현재 속도: 60
}
}
생성자 선언과 호출
- new 연산자는 객체를 생성한 후 연이어 생성자(Constructor)를 호출해서 객체를 초기화하는 역할을 한다.
- 클래스 변수 = new 클래스();
- 생성자가 성공적으로 실행이 끝나면 new 연산자는 객체의 주소를 리턴
- 기본 생성자
- 모든 클래스는 생성자가 존재하며, 하나 이상을 가질 수 있다.
- 클래스에 생성자 선언이 없으며 컴파일러는 다음과 같은 기본 생성자(Default Constructor)를 바이트코드 파일에 자동으로 추가시킨다.
- 생성자 선언
- 생성자는 메소드와 비슷한 모양을 가지고 있으나, 리턴 타입이 없고 클래스 이름과 동일한다.
- 매개변수는 new 연산자로 생성자로 호출할 때 매개값을 생성자 블록 내부로 전달하는 역할을 한다.
// [Car.java] public class Car { // 생성자 선언 Car(String model, String color, int maxSpeed) { } } // [CarExample.jaca] public class CarExample { public static void main(String[] args) { Car myCar = new Car("그랜저", "검정", 250); // Car myCar = new Car(); // 기본 생정자 호출 못함 } }
- 필드 초기화
- 객체마다 동일한 값을 갖고 있다면 필드 선언 시 초기값을 대입하는 것이 좋고, 객체마다 다른 값을 가져야 한다면 생성자에서 필드를 초기화하는 것이 좋다.
public class Korean { String nation = "대한민국"; String name; String ssn; public Korean(String name, String ssn) { this.name = name; // this는 현재 객체의 데이터를 의미 this.ssn = ssn; } }
- 객체마다 동일한 값을 갖고 있다면 필드 선언 시 초기값을 대입하는 것이 좋고, 객체마다 다른 값을 가져야 한다면 생성자에서 필드를 초기화하는 것이 좋다.
- 생성자 오버로딩(Overloading): 매개변수를 달리하는 생성자를 여러 개 선언하는 것
- 매개값으로 객체의 필드를 다양하게 초기화하기 위해 필요
public class Car { Car() { ... } Car(String model) { ... } Car(String model, String color) { ... } }
- 매개변수의 타입과 개수 그리고 선언된 순서가 똑같을 경우 매개변수 이름만 바꾸는 것은 생성자 오버로딩이 아니다.
- 매개값으로 객체의 필드를 다양하게 초기화하기 위해 필요
- 다른 생성자 호출
- 생성자 오버로딩이 많아질 경우 생성자 간의 중복된 코드가 발생할 수 있다.
// 중복 코드 Car(String model) { this.model = model; this.color = "은색"; this.masSpeed = 250; } // 중복 코드 Car(String model, String color) { this.model = model; this.color = color; this.masSpeed = 250; } // 중복 코드 Car(String model, String color, int maxSpeed) { this.model = model; this.color = color; this.masSpeed = maxSpeed; }
- 이 경우에는 공동 코드를 한 생성자에만 집중적으로 작성하고, 나머지 생성자는 this(…)를 사용하여 공동 코드를 가지고 있는 생성자를 호출하는 방법으로 개선할 수 있음
Car(String model) { this(model, "은색", 250); } Car(String model, String color) { this(model, color, 250); } // 공동 초기화 코드 Car(String model, String color, int maxSpeed) { this.model = model; this.color = color; this.masSpeed = maxSpeed; }
- this(매개값, …)는 생성자의 첫 줄에 작성되며 다른 생성자를 호출하는 역할을 한다.
- this() 다음에는 추가적인 실행문을 작성할 수 있는데, 호출되는 생성자의 실행이 끝나면 원래 생성자로 돌아와서 다음 실행문을 실행
- 생성자 오버로딩이 많아질 경우 생성자 간의 중복된 코드가 발생할 수 있다.
메소드 선언과 호출
- 메소드 선언: 객체의 동작을 실행 블록으로 정의하는 것
리턴타입 메소드명(매개변수, ...) { 실행블록 }
- 메소드 호출: 실행 블록을 실제로 실행하는 것
- 메소드는 객체의 동작이므로 객체가 존재하지 않으면 메소드를 호출할 수 없다.
- 객체 내부에서는 단순히 메소드명으로 호출
- 외부 객체에서는 참조 변수와 도트 연산자를 이용해서 호출
- 메소드가 매개변수를 가지고 있을 때는 호출할 때 매개변수의 타입과 수에 맞게 매개값을 제공해야 한다.
가변길이 매개변수
메소드가 가변길이 매개변수를 가지고 있다면 매개변수의 개수와 상관없이 매개값을 줄 수 있다. 가변길이 매개변수는 다음과 같이 선언하고 사용한다.
int sum(int ... values){
}
int result = sum(1, 2, 3);
int result2 = sum(1, 2, 3, 4);
매개값들은 자동으로 배열 항목으로 변환되어 메소드에서 사용된다. 그렇기 때문에 메소드 호출 시 직접 배열을 매개값으로 제공해도 된다.
int[] values = { 1, 2, 3 };
int result = sum(values);
int result2 = sum(new int[] { 1, 2, 3 });
return 문
return 문은 메소드의 실행을 강제 종료하고 호출한 곳으로 돌아간다는 의미이다. 메소드 선언에 리턴 타입이 있을 경우에는 return 문 뒤에 리턴값을 추가로 지정해야 한다. return 문 이후에 실행문을 작성하면 ‘Unreachable code’라는 컴파일 에러가 발생한다.
int plus(int x, int y) {
int result = x + y;
return result;
System.out.println(result); // Unreachable code
}
메소드 오버로딩
메소드 오버로딩(Method Overloading)은 메소드 이름은 같되 메개변수의 타입, 개수, 순서가 다른 메소드를 여러개 선언하는 것을 말한다. 메소드 오버로딩의 목적은 다양한 매개값을 처리하기 위해서이다.
int plus(int x, int y) {
int result = x + y;
return result;
}
double plus(double x, double y) {
double result = x + y;
return result;
}
인스턴스 멤버
필드와 메소드는 선언 방법에 따라 인스턴스 멤버와 정적 멤버로 분류할 수 있다.
- 인스턴스(instance) 멤버: 객체에 소속된 멤버(객체를 생성해야만 사용 가능)
- 정적(static) 멤버: 클래스에 고정된 멤버
인스턴스 멤버 선언 및 사용
- 인스턴스 멤버: 객체에 소속된 멤버
public class Car { // 인스턴스 필드 선언 int gas; // 인스턴스 메소드 선언 void setSpeed(int speed) { // ... } }
- 외부 클래스에서 gas 필드와 setSpeed 메소드를 사용하기 위해서는 Car 객체를 먼저 선언하고 참조 변수로 접근해서 사용해야 한다.
- myCar와 yourCar로 두 Car 객체를 생성하면 gas 필드는 객체마다 존재하지만, setSpeen() 메소드는 각 객체마다 존재하지 않고 메소드 영역에서 저장되고 공유된다.
- 메소드는 코드의 덩어리이므로 객체마다 저장하면 중복 저장으로 인해 메모리 효율이 떨어지므로 메소드 영역에 두되 공유해서 사용한다.
- this 키워드: 객체 내부에서 인스턴스 멤버에 접근하기 위해 사용
정적 멤버
자바는 클래스 로더(loader)를 이용해서 클래스를 메소드 영역에 저장하고 사용한다. 정적 멤버란 메소드 영역의 클래스에 고정적으로 위치하는 멤버를 말한다. 그렇기 때문에 정적 멤버는 객체를 생성할 필요 없이 클래스를 통해 바로 접근이 가능하다.
정적 멤버 선언
필드와 메소드는 모두 정적 멤버가 될 수 있다. 정적 필드와 정적 메소드로 선언하려면 static 키워드를 추가하면 된다.
public class 클래스 {
// 정적 필드 선언
static 타입 필드 [= 초기값];
// 정적 메소드
static 리턴타입 메소드 ( 매개변수, ... ) {
...
}
}
- 객체마다 가지고 있을 필요성이 없는 공용적인 필드는 정적 필드로 선언하는 것이 좋다.
- 인스턴스 필드를 이용하지 않는 메소드는 정적 메소드로 선언하는 것이 좋다.
정적 멤버 사용
클래스가 메모리로 로딩되면 정적 멤버를 바로 사용할 수 있는데, 클래스 이름과 함께 도트 연산자로 접근하거나 객체 참조 변수로도 접근이 가능하다. 하지만 정적 요소는 클래스 이름으로 접근하는 것이 정석이다.
public class Calculator {
static double pi = 3.14159;
static int plus(int x, int y) { ... }
static int minus(int x, int y) { ... }
}
// ...
double result1 = 10 * 10 * Calculator.pi;
int result2 = Calculator.plus(10, 5);
int result3 = Calculator.minus(10, 5);
Calculator myCalcu = new Calculator();
double result4 = 10 * 10 * myCalcu.pi;
int result5 = myCalcu.plus(10, 5);
int result6 = myCalcu.minus(10, 5);
정적 블록
정적 필드는 “static double pi = 3.14159;”처럼 필드 선언과 동시에 초기값을 주는 것이 일반적이다. 하지만 복잡한 초기화 작업이 필요하다면 정적 블록(static block)을 이용해야 한다.
public class Television {
static String company = "MyCompany";
static String model = "LCD";
static String info;
static {
info = company + "-" + model;
}
}
인스턴스 사용 불가
정적 메소드와 정적 블록은 객체가 없어도 실행된다는 특징 때문에 인스턴스 필드나 인스턴스 메소드를 사용할 수 없다. 또한 객체 가신의 참조인 this도 사용할 수 없다.
public class ClassName {
int field1;
void method1() { ... }
static int field2;
static void method2() { ... }
static {
field1 = 10; // 컴파일 에러
method1(); // 컴파일 에러
field2 = 10;
method2();
}
static void method3() {
this.field1 = 10; // 컴파일 에러
this.method1(); // 컴파일 에러
this.field2 = 10;
this.method2();
}
}
정적 메소드와 정적 블록에서 인스턴스 멤버를 사용하고 싶다면 다음과 같이 객체를 먼저 생성하고 참조 변수로 접근해야 한다.
static void method3() {
ClassName obj = new ClassName();
obj.field1 = 10;
obj.method1();
}
main() 메소드도 동일한 규칙이 적용된다.
final 필드와 상수
인스턴스 필드와 정적 필드는 언제든지 값을 변경할 수 있다. 그러나 경우에 따라서는 값을 변경하는 것을 막고 읽기만 허용해야 할 때가 있다. 이때 final 필드와 상수를 선언해서 사용한다.
- final 타입 필드 [= 초기값]
final 필드에 초기값을 줄 수 있는 방법은 두 가지밖에 없다. 이 두 방법을 사용하지 않고 final 필드를 그대로 남겨 두면 컴파일 에러가 발생한다.
- 필드 선언 시에 초기값 대입: 고정된 값일 때 사용
- 생성자에서 초기값 대입: 복잡한 초기화 코드가 필요하거나 객체 생성 시에 외부에서 전달된 값으로 초기화해야 할 때
다음은 Korean 클래스에서 nation과 ssn 필드를 final 필드로 선언했다.
public class Korean {
final String nation = "대한민국";
final String ssn;
String name;
public Korean(String ssn, String name) {
this.ssn = ssn;
this.name = name;
}
}
상수 선언
상수(constant)는 불변의 값을 저장하는 필드이다. 상수는 객체마다 저장할 필요가 없고, 여러 개의 값을 가져도 안되기 때문에 static이면서 final인 특성을 가져야 한다.
- static final 타입 상수 [= 초기값];
- 상수 이름은 모두 대문자로 작성하는 것이 관례
- 서로 다른 단어가 혼합된 이름이라면 언더바로 단어들을 연결
초기값은 선언 시에 주는 것이 일반적이지만, 복잡한 초기화가 필요한 경우에는 정적 블록에서 초기화할 수도 있다.
static final 타입 상수;
static {
상수 = 초기값;
}
public class Earth {
static final double EARTH_RADIUS = 6400;
static final double EARTH_SURFACE_AREA;
// 정적 변수에서 상수 초기화
static {
EARTH_SURFACE_AREA = 4 * Math.PI * EARTH_RADIUS * EARTH_RADIUS;
} // Math 클래스의 PI
}
패키지
자바의 패키지(package)는 단순히 디렉토리만을 의미하지 않으며, 클래스의 일부분이며, 클래스를 식별하는 용도로 사용된다.
- 패키지는 주로 회사 도메인 이름의 역순으로 만든다.
- mycompany.com 회사의 패키지는 com.mycompany로 만든다.
- 패키지는 클래스를 식별하는 용도로 사용되기 때문에 클래스의 전체 이름에 포함된다.
- com.mycompany 패키지에 속해 있는 Car 클래스의 전체 이름은 com.mycompany.Car가 된다.
- 패키지에 속한 바이트코드 파일(*. class)은 따로 떼어 내어 다른 디렉토리로 이동할 수 없다.
- Car 클래스가 com.company 패키지에 소속되어 있다면 다른 디렉토리에 Car.class를 옮겨 저장할 경우, Car 클래스를 사용할 수 없게 된다.
패키지 선언
패키지 디렉토리는 클래스를 컴파일하는 과정에서 자동으로 생성된다. 컴파일러는 클래스의 패키지 선언을 보고 디렉토리를 자동 생성시킨다. 패키지 선언은 package 키워드와 함께 패키지 이름을 기술한 것으로, 항상 소스 파일 최상단에 위치해야 한다.
package 상위패키지.하위패키지;
public class 클래스명 { ... }
- 패키지 이름은 모두 소문자로 작성하는 것이 관례
- 패키지 이름이 서로 중복되지 않도록 회사 도메인 이름의 역순으로 작성하고, 마지막에는 프로젝트 이름을 붙여 주는 것이 일반적
같은 패키지에 있는 클래스는 아무런 조건 없이 사용할 수 있지만, 다른 패키지에 있는 클래스를 사용하려면 import 문을 이용해서 어떤 패키지의 클래스를 사용하는지 명시해야 한다.
- import 문이 작성되는 위치는 패키지 선언과 클래스 선언 사이
- import 키워드 뒤에는 사용하고자 하는 클래스의 전체 이름을 기술
- 동일한 패키지에 포함된 다수의 클래스를 사용해야 한다면 클래스 이름을 생략하고 *를 사용
import 문은 하위 패키지를 포함하지 않는다. 따라서 com.hankook 패키지에 있는 클래스도 사용해야 하고 com.hankook.project 패키지에 있는 클래스도 사용해야 하다면 두 개의 import 문이 필요하다.
import com.hankook.*;
import com.hankook.project.*;
서로 다른 패키지에 동일한 클래스 이름이 존재하는 경우, 두 패키지를 모두 import 하고 Tire 클래스를 사용하면, 컴파일러는 어떤 패키지의 클래스를 사용할 지 결정할 수 없기 때문에 컴파일 에러를 발생시킨다. 이 경우에는 클래스의 전체 이름을 사용해서 정확이 어떤 패키지의 클래스를 사용하는지 알려야 한다. 클래스 전체 이름을 사용할 경우 import 문은 필요 없다.
com.hankook.Tire tire = new com.hankook.Tire();
접근 제한자
중요한 필드와 메소드가 외부로 노출되지 않도록 객체의 무결성(결점이 없는 성질)을 유지하기 위해 객체의 필드를 외부에서 변경하거나 메소드를 호출할 수 있도록 막아야 할 필요가 있다.
자바는 이러한 기능을 구현하기 위해 접근 제한자(Access Modifier)를 사용한다. 아래로 내려갈 수록 접근 제한이 강화된다.
- public
- 제한 대상: 클래스, 필드, 생성자, 메소드
- 제한 범위: 없음
- protected
- 제한 대상: 필드, 생성자, 메소드
- 제한 범위: 같은 패키지이거나, 자식 객체만 사용 가능
- (default)
- 제한 대상: 클래스, 필드, 생성자, 메소드
- 제한 범위: 같은 패키지
- private
- 제한 대상: 필드, 생성자, 메소드
- 제한 범위: 객체 내부
- 클래스의 접근 제한: [public] class 클래스
- public을 생략했다면 클래스는 default 접근 제한: 클래스는 같은 패키지에서는 아무런 제한 없이 사용할 수 있지만 다른 패키지에서는 사용 불가능
- public 접근 제한자를 붙였다면 클래스는 같은 패키지 뿐만 아니라 다른 패키지에서도 사용 가능
class A { // default 접근 제한 } public class B { }
- 생성자의 접근 제한
- 생성자가 어떤 접근 제한을 갖느냐에 따라 호출 가능 여부가 결정되므로 객체를 생성하기 위해 생성자를 어디에서나 호출할 수 있는 것은 아니다.
- public: 모든 패키지에서 생성자를 호출할 수 있다.
- default: 같은 패키지에서만 생성자를 호출할 수 있다.
- private: 클래스 내부에서만 생성자를 호출할 수 있다.
public class A { A a1 = new A(true); A a2 = new A(1); A a3 = new A("문자열"); public A(boolean b){ // public 접근 제한 생성자 선언 } A(int b) { // default 접근 제한 생성자 선언 } private A(String s) { // private 접근 제한 생성자 선언 } }
- 필드와 메소드의 접근 제한
- 필드와 메소드도 어디에서나 읽고 호출할 수 있는 것은 아니고, 어떤 접근 제한을 갖느냐에 따라 호출 여부가 결정된다.
- public: 모든 패키지에서 필드를 읽고 변경할 수 있다.
- default: 같은 패키지에서 필드를 읽고 변경할 수 있다.
- private: 클래스 내부에서만 필드를 읽고 변경할 수 있다.
Getter와 Setter
객체의 필드(데이터)를 외부에서 마음대로 읽고 변경할 경우 객체의 무결성이 깨질 수 있다. 이러한 문제점 때문에 객체지향 프로그래밍에서는 직접적인 외부에서의 필드 접근을 막고, 그 대신 메소드를 통해 필드에 접근하는 것을 선호한다. 이 이유는 메소드가 데이터를 검증해서 유효한 값만 필드에 저장할 수 있기 때문이다. 이러한 역할을 하는 메소드가 Setter이다.
private double speed;
public void setSpeed(double speed) {
if(speed < 0) {
this.speed = 0;
return;
} else {
this.speed = speed;
}
}
외부에서 객체의 필드를 얻을 때에도 메소드가 필요한 경우가 있다. 필드값이 객체 외부에서 사용하기에 부적절한 경우, 메소드로 적절한 값으로 변환해서 리턴할 수 있기 때문이다. 이러한 역할을 하는 메소드가 Getter이다.
private double speed; // speed 의 단위 마일
public double getSpeed() {
double km = speed* 1.6;
return km;
}
다음은 Getter와 Setter의 기본 작성 방법이다. 만약 타입이 boolean일 경우에 Getter는 get으로 시작하지 않고 is로 시작하는 것이 관례이다.
private 타입 fieldName;
// Getter
public 타입 getFieldName() {
return fieldName;
}
// Setter
public 타입 setFieldName(타입 fieldName) {
this.fieldName = fieldName;
}
싱글톤 패턴
애플리케이션 전체에서 단 한개의 객체만 생성해서 사용하고 싶다면 싱글톤(Singleton) 패턴을 적용할 수 있다. 싱글톤 패턴의 핵심은 생성자를 private 접근 제한해 외부에서 new 연산자로 생성자를 호출할 수 없도록 막는 것이다. 생성자를 호출할 수 없으니 외부에서 마음대로 객체를 생성하는 것이 불가능해진다. 대신 싱글톤 패턴이 제공하는 정적 메소드를 통해 간접적으로 객체를 얻을 수 있다.
public Class 클래스 {
// private 접근 제한을 갖는 정적 필드 선언과 초기화
private static 클래스 singleton = new 클래스(); // 자신의 타입으로 정적 필드를 선언하고 미리 객체를 생성해서 초기화
// private 접근 권한을 갖는 생성자 선언
private 클래스() { }
// public 접근 권한을 갖는 정적 메소드 선언
public static 클래스 getInstance() { // 정적 필드값을 리턴하는 getInstance() 정적 메소드를 public으로 선언
return singleton;
}
}
외부에서 객체를 얻는 유일한 방법은 getInstance() 메소드를 호출하는 것이다.
- 클래스 변수 = 클래스.getInstance();
댓글남기기