객체들을 하나씩 조립해서 완성된 프로그램을 만드는 기법
기존의 절차 중심으로는 우리의 생활 문제가 잘 표현이 되지 않는다. 이 문제점을 해결하기 위해서 객체 지향 프로그래밍 개념이 등장했다.
물리적으로 존재하거나 개념적인 것 중에서 다른 것과 식별 가능한 것
현실 세계의 객체를 소프트웨어 객체로 설계하는 것
현실 세계의 객체의 대표 속성과 동작을 추려 내어 소프트웨어 객체의 필드와 메소드로 정의하는 과정이다.
현실 세계에서 일어나는 모든 현상은 객체와 객체 간의 상호작용으로 이루어져 있다. 객체지향 프로그램에서도 객체들은 다른 객체와 메소드를 통해서 서로 상호작용하면서 동작한다.
메소드(매개값1, 매개값2, ...);
객체는 단독으로 존재할 수도 있지만, 대부분 다른 객체와 관계를 맺고 있다.
객체의 데이터(필드), 동작(메소드)을 하나로 묶고 실제 구현 내용을 외부에 감추는 것
외부 객체는 객체 내부의 구조를 알지 못하며 객체가 노출해서 제공하는 필드와 메소드만 이용할 수 있다. 이를 통해서 외부의 잘못된 사용으로 내부 객체가 손상되지 않도록 한다. 자바 언어는 캡슐화된 멤버를 노출시킬 것인지 숨길 것인지를 결정하기 위해 접근 제한자를 사용한다.
부모 객체는 자기가 가지고 있는 필드와 메소드를 자식 객체에게 물려주어 자식 객체가 사용할 수 있도록 함
이를 통해서 코드의 재사용성을 높여준다. 유지 보수 시간을 최소화 시켜주는 장점이 있어서 상속 개념을 사용한다.
사용 방법은 동일하지만 실행 결과가 다양하게 나오는 성질
다형성을 구현하기 위해서는 자동 타입 변환과 재정의 기술이 필요하다.
객체지향 프로그래밍에서 객체를 생성하려면 설계도에 해당하는 클래스가 필요하다.
클래스로부터 생성된 객체를 해당 클래스의 인스턴스(instance)라고 부른다. 클래스로부터 객체를 만드는 과정을 인스턴스화라고 한다. 동일한 클래스로부터 여러개의 인스턴스를 만들 수 있다.

객체 생성을 위한 설계도를 작성하는 작업
클래스 선언은 소스 파일명과 동일하게 작성한다. 클래스명은 첫 문자를 대문자로 하고 캐멀 스타일로 작성한다. 숫자를 포함해도 되지만 첫문자는 숫자가 될 수 없고, 특수문자 중 $,_를 포함할 수 있다.
public class: 공개 클래스를 선언한다. 어느 위치에 있든지 패키지와 상관없이 사용할 수 있는 클래스이다. 파일에 선언된 클래스 중에서 단 하나만 public일 수 있다.
외부에 있는 클래스를 가져올 때는 반드시 import를 사용해줘야 한다. 하지만 같은 패키지에 이미 있는 경우 import를 해주지 않아도 바로 사용할 수 있다.
자바에서 클래스를 사용하면 실제로는 클래스 자체가 메모리에 올라가는 것이 아니라, 해당 클래스로 생성된 객체가 힙 메모리에 할당되고, 그 객체를 가리키는 참조값이 4byte 크기의 스택 메모리에 저장된다.
//ClassName.java
public class ClassName{
//필드 선언
int fieldName;
// 생성자 선언
ClassName(){}
//메소드 선언
int methodName(){}
}
클래스 변수 = new 클래스();
클래스로부터 객체를 생성하려면 객체 생성 연산자인 new를 사용해서 생성한다. new 연산자는 객체를 생성시키고 객체의 주소를 리턴한다.

main() 메소드를 가지고 있는 실행 가능한 클래스이다.객체의 데이터를 저장하는 역할
- 고유 데이터
- 현재 상테 데이터
- 부품 데이터
타입 필드명 = 초기값;
필드는 클래스 블록 내에서 선언돼야 한다. 초기값을 제공하지 않을 경우 필드는 객체 생성 시 자동으로 기본값으로 초기화한다. 정수 타입 필드는 0, 실수 타입 필드는 0.0, 그리고 boolean 필드는 false로 초기화되는 것을 볼 수 있다. 참조 타입은 객체를 참조하고 있지 않은 상태인 null로 초기화된다.
package ch06.sec06_start.exam01;
public class Car {
//필드 선언
String model;
boolean start;
int speed;
}
| 구분 | 필드 | (로컬)변수 |
|---|---|---|
| 선언 위치 | 클래스 선언 블록 | 생성자, 메소드 선언 블록 |
| 존재 위치 | 객체 내부에 존재 | 생성자, 메소드 호출 시에만 존재 |
| 사용 위치 | 객체 내외부 어디든 사용 | 생성자, 메소드 블록 내부에서만 사용 |
필드값을 읽고 변경
클래스에서 필드를 선언했다고 해서 바로 사용할 수 있는 것은 아니다. 필드는 객체의 데이터이므로 객체가 존재하지 않으면, 필드도 존재하지 않는다. 클래스로부터 객체가 생성된 후에 필드를 사용할 수 있다.
필드는 객체 내외부에서 접근해서 사용할 수 있다.
단순히 필드명으로 읽고 변경한다.
참조변수.필드
package ch06.sec06_start.exam02;
public class CarExample {
public static void main(String[] args) {
//Car 객체 생성
Car myCar = new Car();
//Car 객체의 필드값 변경
myCar.speed = 60;
System.out.println("수정된 속도: " + myCar.speed);
}
}
클래스 변수 = new 클래스();
필드 초기화를 하거나 메소드를 호출해서 객체를 사용할 준비를 객체 초기화라고 한다.
리턴된 주소는 클래스 변수에 대입되어 객체의 필드나 메소드에 접근할 때 이용된다.
리턴 타입이 없는 특별한 메서드
모든 클래스는 생성자가 존재하며, 하나 이상 가질 수 있다. 클래스명과 동일하게 메소드명을 설정하면 생성자라고 부른다.
생성자를 선언하지 않아도 컴파일러는 기본 생성자를 바이트코드를 파일에 자동으로 추가해줌으로 생성자가 항상 존재하도록 보장해준다. 만약 개발자가 명시적으로 선언한 생성자가 있다면 컴파일러는 자동으로 기본 생성자를 추가하지 않는다.
개발자는 객체를 다양하게 초기화하기 위해서 생성자를 선언하는 습관을 가져야 한다.
클래스가 public class로 선언되면 기본 생성자도 public이 붙지만, 클래스가 public 없이 class로만 선언되면 기본 생성자에도 public이 붙지 않는다.
클래스(매개변수, ...){
//객체의 초기화 코드
}
생성자는 메소드와 비슷한 모양을 가지고 있으나, 리턴 타입이 없고 클래스 이름과 동일하다.
매개변수는 new 연산자로 생성자를 호출할 때 매개값을 생성자 블록 내부로 전달하는 역할을 한다.
package ch06.sec07_constructor.exam01;
public class Car {
//생성자 선언
Car(String model, String color, int maxSpeed) {
}
}
package ch06.sec07_constructor.exam01;
public class CarExample {
public static void main(String[] args) {
Car myCar = new Car("그랜저", "검정", 250);
//Car myCar = new Car(); //기본 생성자 호출 못함
}
}
객체마다 동일한 값을 갖고 있다면 필드 선언 시 초기값을 대입하는 것이 좋고, 객체마다 다른 값을 가져야 한다면 생성자에서 필드를 초기화하는 것이 좋다.
예를 들어 Korean 클래스에서 선언한다고 가정해보면 한국인이므로 nation은 대한민국으로 동일한 값을 가지지만, name과 ssn은 한국인마다 다르므로 생성자에서 초기화하는 것이 좋다.
package ch06.sec07_constructor.exam02;
public class Korean {
//필드 선언
String nation = "대한민국";
String name;
String ssn;
//생성자 선언
public Korean(String name, String ssn) {
this.name = name;
this.ssn = ssn;
}
}
필드 사용시 매개변수의 이름이 너무 짧거나 의미가 없으면 혼란이 올 수 있기 때문에, 가능하면 초기화시킬 필드명과 동일한 이름을 사용하는 것을 선호한다. 매개변수명이 필드명과 동일하다면 필드임을 구분하기 위해서 this키워드를 필드명 앞에 붙여줌으로써 현재 객체의 데이터임을 명시한다.
this자바에서 this는 현재 인스턴스 자신을 가리키는 참조 변수이다. 개발자가 선언하거나 변경할 수 없고, JVM이 자동으로 제공하는 숨겨진 참조값이다. 개발자가 따로 조작할 것은 없고, 필요한 상황에서 그냥 사용하면 된다.
매개변수를 달리하는 생성자를 여러 개 선언하는 것
public class Car{
Car(){}
Car(String model){}
Car(String model, String color){}
Car(String model, String color, int maxSpeed){}
만약 매개변수의 순서만 바꾸는 것은 오버로딩이 아닌, 컴파일 에러가 발생하게 된다.
생성자가 오버로딩되어 있을 경우, new 연산자로 생성자를 호출할 때 제공되는 매개값의 타입과 수에 따라 실행될 생성자가 결정된다.
만약 생성자 오버로딩이 많아질 경우 생성자 간의 중복된 코드가 발생할 수 있다.
공통 코드를 한 생성자에만 집중적으로 작성하고, 나머지 생성자는 this(…)를 이용하여 공통 코드를 가지고 있는 생성자를 호출하여 개선한다.
this(…)는 생성자의 첫줄에 작성되며 다른 생성자를 호출하는 역할을 한다. 호출하고 싶은 생성자의 매개변수에 맞게 매개값을 제공하면 된다.
package ch06.sec07_constructor.exam05;
public class Car {
// 필드 선언
String company = "현대자동차";
String model;
String color;
int maxSpeed;
Car(String model) {
//20라인 생성자 호출
this(model, "은색", 250);
}
Car(String model, String color) {
//20라인 생성자 호출
this(model, color, 250);
}
Car(String model, String color, int maxSpeed) {
this.model = model;
this.color = color;
this.maxSpeed = maxSpeed;
}
}
객체의 동작을 실행 블록으로 정의하는 것
className)메소드 블록의 실행
- 클래스로부터 객체를 생성한다.
- 객체 내외부에서 메소드를 호출한다.
- 객체 내부: 단순하게 메소드명으로 호출한다.
- 객체 외부: 참조변수와
.연산자를 이용해서 호출한다.- 메소드의 리턴값(이 있을 경우) 저장한다. 변수 타입은 메소드의 리턴 타입과 동일해야 한다.
메소드의 매개변수가 2개라면 메소드를 호출할 때 매개값도 2개여야 한다. 하지만 메소드가 가변길이 매개변수를 가지고 있다면 매개변수의 개수와 상관없이 매개값을 줄 수 있다.
int sum(int ... values){}
int result = sum(1,2,3);
int result = sum(1,2,3,4,5);
가변길이 매개변수의 실체는 배열이다. 그래서 매개값들은 자동으로 배열 항목으로 변환되어 메소드에서 사용된다.
메소드의 실행을 강제 종료하고 호출한 곳으로 돌아간다는 의미
return 리턴값;
return문 이후에 실행문은 결코 실행되지 않기 때문에 Unreachable code라는 컴파일 에러가 발생하게 된다.
메소드 이름은 같되 매개변수 타입, 개수, 순서가 다른 메소드를 여러개 선언

메소드 오버로딩의 목적은 다양한 매개값을 처리하기 유연하기 처리하기 위해 사용한다.
package ch06.sec08_method.exam04;
public class Calculator {
//정사각형의 넓이
double areaRectangle(double width) {
return width * width;
}
//직사각형의 넓이
double areaRectangle(double width, double height) {
return width * height;
}
}
객체에 소속된 멤버
선언 방법에 따라 인스턴스 멤버와 정적 멤버로 분류할 수 있다.
| 구분 | 설명 | 사용 |
|---|---|---|
| 인스턴스(instance) 멤버 | 객체에 소속된 멤버 | 객체를 생성해야만 사용할 수 있는 멤버 |
| 정적(static) 멤버 | 클래스에 고정된 멤버 | 객체 없이도 사용할 수 있는 멤버 |
인스턴스 멤버를 외부 클래스에서 사용하기 위해서는 객체를 먼저 new 연산자를 사용해서 생성하고, 참조 변수로 접근해서 사용한다.
public class Car{
//인스턴스 필드 선언
int gas;
//인스턴스 메소드 선언
void setSpeed(int speed){}
}
Car myCar = new Car();
myCar.gas = 10;
myCar.setSpeed(10);
필드는 객체마다 따로 존재하지만 메소드는 각 객체마다 존재하는 것이 아닌 메소드 영역에 저장되고 공유된다.

메소드 영역에서 메소드를 공유하는 이유는 똑같은 코드의 중복 저장으로 인해 메모리 효율이 떨어진다. 그래서 메소드 영역에 공유해서 사용하고, 객체 생성 없이는 사용하지 못하도록 제한을 걸어둔다. 인스턴스 멤버는 객체에 소속된 멤버이다. 필드는 소속되지만 메소드는 객체에 포함되지 않는다.
this 키워드객체 내부에서는 인스턴스 멤버에 접근하기 위해 this를 사용한다.
생성자와 메소드의 매개변수명이 인스턴스 멤버인 필드명과 동일한 경우, 인스턴스 필드임을 강조하고자 할 때 this를 주로 사용한다.
package ch06.sec09_instance;
public class Car {
//필드 선언
String model;
int speed;
//생성자 선언
Car(String model) {
this.model = model; //매개변수를 필드에 대입(this 생략 불가)
}
//메소드 선언
void setSpeed(int speed) {
this.speed = speed; //매개변수를 필드에 대입(this 생략 불가)
}
void run() {
this.setSpeed(100);
System.out.println(this.model + "가 달립니다.(시속:" + this.speed + "km/h)");
}
}
메소드 영역의 클래스에 고정적으로 위치하는 멤버
자바는 클래스 로더를 이용해서 클래스를 메소드 영역에 저장하고 사용한다. static 멤버란 메소드 영역의 클래스에 고정적으로 위치하는 멤버이다. 그래서 static 멤버는 객체를 생성할 필요 없이 클래스를 통해 바로 사용이 가능하다.
static 키워드를 추가해 정적 필드와 정적 메소드로 선언한다.
public class ClassName{
//정적 필드 선언
static 타입 필드 [= 초기값];
//정적 메소드
static 리턴타입 메소드(매개변수, ...){}
}
public class Calculator{
String color;
static double pi = 3.141592;
}
객체를 생성하지 않고도 클래스가 메모리로 로딩되면 정적 멤버를 바로 사용할 수 있다.
클래스이름.필드(메소드)
물론 일반 객체 참조 변수로도 접근이 가능하다. 하지만 정적 요소는 클래스 이름으로 접근하는 것이 정석이다.
public class CalculatorExample {
public static void main(String[] args) {
double result1 = 10 * 10 * Calculator.pi;
}
}
static 필드는 선언과 동시에 초기값을 주는 것이 일반적이다. 만약 복잡한 초기화 작업이 필요하다면 static 블록을 사용해서 필드를 선언해줘야 한다.
static{
...
}
static 블록은 클래스 내부에 여러개가 선언되어 있을 경우에는 선언된 순서대로 실행된다.
public class Television {
static String company = "MyCompany";
static String model = "LCD";
static String info;
static {
info = company + "-" + model;
}
}
3개의 static 필드가 있고, company와 model은 선언과 동시에 초기화됐다. info는 초기화된 필드를 이용해서 static 블록에서 초기화 작업을 진행했다.
static 메소드와 static 블록은 객체가 없어도 된다는 특징 때문에 내부의 인스턴스 필드나 인스턴스 메소드를 사용할 수 없다. this도 사용할 수 없다.
public class ClassName{
//인스턴스 필드와 메소드 선언
int field1;
void method1(){}
//정적 필드와 메소드 선언
static int field2;
static void method2(){}
//정적 블록 선언
static{
// field=10; //컴파일 에러
// method1(); //컴파일 에러
field2=10;
method2();
}
//정적 메소드 선언
static void method3(){
//this.field1=10; //컴파일 에러
//this.method1(); //컴파일 에러
field2=10;
method2();
}
}
객체를 먼저 생성하고 참조 변수로 접근해야 한다.
static void method3(){
//객체 생성
ClassName obj = new ClassName();
//인스턴스 멤버 사용
obj.field1 = 10;
obj.method1();
}
자바의 프로그램 진입점인 main() 메소드도 동일한 규칙이 적용된다. main() 메소드도 public static void 메소드이기 때문에 객체 생성 없이 인스턴스 필드와 인스턴스 메소드를 main() 메소드에서 바로 사용할 수 없다.
final 타입 필드 [=초기값];
final 필드는 readonly일 뿐 상수는 아니다. 상수로 생성하기 위해서는 final을 아예 상수로 표현하기 위해서는 static final의 형태로 선언해줘야 한다.
불변의 값
static final 타입 상수 [= 초기값];
static final의 형태로 선언된 상수는 클래스 차원에서 공유되고, 값이 절대 변하지 않는다. 상수는 모두 대문자로 구성하고, 스네이크 표기법으로 표기하는 것이 관례이다.
static final도 static 필드이기 때문에 클래스명.상수명으로 접근해서 값을 받아올 수 있다.
클래스의 일부분, 클래스를 식별하는 용도
클래스명은 전세계적으로 유일한 이름이어야 한다. 사실 클래스명만으로는 유일한 클래스명을 생성하는 것은 불가하기 때문에 패키지명을 이용해서 클래스를 구분한다. 보통 기관의 도메인 이름의 역순으로 만들어 유일한 클래스명을 생성한다.
패키지는 클래스를 식별하는 용도로 사용되기 때문에 클래스의 전체 이름에 포함된다. Car클래스의 전체클래스명은 com.mycompany.Car인 것이다.패키지에 속한 바이트 코드 파일(*.class)은 따로 떼어 내어 다른 디렉토리로 이동할 수 없다.
자바에서 패키지 구조는 디렉토리 구조와 1:1로 매핑된다. 따라서 .class 파일은 자신이 속한 패키지 디렉토리 안에 있어야 하며, 다른 디렉토리로 이동시키면 클래스 로딩에 실패하게 된다.
클래스를 컴파일하는 과정에서 자동으로 생성
패키지 선언은 package 키워드와 함께 패키지 이름을 항상 소스 파일 최상단에 위치해야 한다.
package 상위패키지.하위패키지;
public class ClassName{}
다른 패키지에 있는 클래스를 사용하기 위해서 어떤 패키지의 클래스를 사용하는지 명시
같은 패키지에 잇는 클래스는 아무런 조건 없이 사용할 수 있다.
외부 패키지의 클래스를 사용하기 위해서는 해당 클래스의 전체 이름(패키지명 포함)을 import해야 한다.
package com.mycompany;
import **com.hankook.Tire**;
public class Car{
Tire tire = new Trie();
}
서로 다른 패키지에 동일한 클래스 이름이 존재한다. com.hankook.Tire, com.kumho.Tire 이렇게 두개가 있을 때 두 패키지를 모두 import하고, Tire 클래스를 사용할 경우에 컴파일러는 어떤 패키지의 클래스를 사용할지 결정할 수 없기 때문에 컴파일 에러를 발생시킨다.
만약 이미 외부에 선언한 동일한 클래스를 하나의 클래스에서 사용하고자 한다면 전체 이름을 사용해서 정확히 어떤 패키지의 클래스를 사용하는지 명시해야 한다. 이 경우에는 import를 하지 않아도 된다.
com.hankook.Tire tire = new com.hankook.Tire();
package ch06.sec12.hyundai;
//import 문으로 다른 패키지 클래스 사용을 명시
import ch06.sec12.hankook.SnowTire;
import ch06.sec12.kumho.AllSeasonTire;
public class Car {
//부품 필드 선언
ch06.sec12.hankook.Tire tire1 = new ch06.sec12.hankook.Tire();
ch06.sec12.kumho.Tire tire2 = new ch06.sec12.kumho.Tire();
SnowTire tire3 = new SnowTire();
AllSeasonTire tire4 = new AllSeasonTire();
}
중요한 필드와 메소드과 외부로 노출되지 않도록 해 객체의 무결성을 유지하기 위해 접근 제한자를 사용한다.
default는 접근 제한자가 아니라 접근 제한자가 붙지 않은 가장 기본적인 상태를 의미한다.
| 접근제한자 | 제한 대상 | 제한 범위 | 설명 |
|---|---|---|---|
private | 필드, 생성자, 메소드 | 객체 내부 | |
| (default) | 클래스, 필드, 생성자, 메소드 | 같은 패키지 | 같은 패키지 내에 있으면 허용하겠다. |
protected | 필드, 생성자, 메소드 | 같은 패키지이거나, 자식 객체만 사용 가능 | 부분적으로 허용하겠다. 상속된 것에 한해서 허용하겠다. |
public | 클래스, 필드, 생성자, 메소드 | 없음 | 다른 패키지에서 클래스를 사용하려면 해당 클래스를 반드시 public으로 지정해줘야 한다. |
package ch06.sec13_protect.exam01.package1;
//디폴트 접근 제한
class A {
}
package ch06.sec13_protect.exam01.package1;
public class B {
// 필드 선언
A a; //o<-A 클래스 접근 가능(필드로 선언 가능)
}
package ch06.sec13_protect.exam01.package2;
import ch06.sec13_protect.exam01.package1.B;
public class C {
//필드 선언
//A a; //x A 클래스 접근 불가(컴파일 에러), 디폴트 접근 제한은 같은 패키지만 가능
B b; //o//필드 선언
}
class B는 같은 패키지 내에 있기에 default 접근 제한 class A에 접근할 수 있지만 다른 패키지에 있는 class C는 A에는 접근하지 못하고, public으로 선언된 B에만 접근이 가능하다.
생성자를 어디에서나 호출할 수 있는 것은 아니다. 생성자는 public, default, private의 접근 제한을 가질 수 있다.
public: 모든 패키지에서 생성자를 호출할 수 있다private: 클래스 내부에서만 생성자를 호출할 수 있다.필드와 메소드도 어디에서나 읽고 호출할 수 있는 것은 아니다. public, default, private의 접근 제한을 가진다.
public: 모든 패키지에서 필드를 읽고 변경할 수 있다. 메소드를 호출할 수 있다.private: 클래스 내부에서만 필드를 읽고 변경할 수 있다. 메소드를 호출할 수 있다.만약에 객체의 필드(데이터)를 외부에서 마음대로 읽고 변경할 경우 객체의 무결성이 깨질 수 있다.
그래서 객체지향 프로그래밍에서는 필드는 은닉해두고, 메소드를 통해서 필드를 검증 후 필드의 값을 업데이트할 수 있도록 하고 있다. 이 업데이트하는 것이 Setter이다.
private double speed;
public void setSpeed(double speed){
if(speed<0){
this.speed=0;
return;
}else{
this.speed=speed;
}
}
여기서 speed는 private 필드이기 때문에 외부 클래스에서 접근할 수 없다. 하지만 setSpeed를 이용해서 speed가 음수가 아닌지 검증하고, 유효값이라면 speed의 값을 업데이트할 수 있다.
필드를 읽기만 해야 할 경우에는 Getter 메서드를 통해 값을 가져온다. 값을 꺼내서 별도의 변수에 저장하거나 타입 변환을 하더라도 원래 객체의 내부 상태는 유지되므로 무결성이 보장된다.
우리는 speed라는 필드가 있는 것을 알 수 없다. 하지만 getSpeed, setSpeed를 통해서 speed property가 있음을 확인할 수 있다.
getFieldNamesetFieldNameget(set)으로 시작하고 필드명의 첫글자를 대문자로 변경하여 캐멀케이스 형태로 저장한다.
isTrue: 필드 타입이 boolean인 경우에는 get이 아닌 is로 지어주는 것이 관례이다.인텔리제이에서 cmd+N에서 Getter/Setter 메뉴를 이용해서 자동으로 메소드를 생성해주는 기능을 제공하고 있다.




아주 특수한 행위를 하는 디자인 패턴
애플리케이션 전체에서 단 한개의 객체만 생성해서 사용하고 싶다면 싱글톤 패턴을 적용할 수 있다. 코드의 재사용이 아닌 경험의 재사용이다.
생성자를 private 접근 제한해 외부에서 new 연산자로 생성자를 호출할 수 없도록 한다. 생성자를 호출할 수 없으니 외부에서 마음대로 객체를 생성할 수 없다.
private 클래스(){}
싱글톤 패턴이 제공하는 정적 메소드를 통해 간접적으로 객체를 얻을 수 있다.
public class ClassName {
// 1. 자신의 타입으로 정적 필드를 선언하고, 미리 생성된 객체로 초기화
private static ClassName singleton = new ClassName();
// 2. 생성자를 private으로 선언해 외부에서 객체 생성을 막는다
private ClassName() { }
// 3. 정적 메서드를 통해 객체를 간접적으로 반환
public static ClassName getInstance() {
return singleton;
}
}
singleton은 클래스 내부에서 단 하나의 객체만 유지하도록 한다. 이 필드는 private으로 선언해 외부에서 직접 접근하거나 변경하지 못하도록 막는다.getInstance()는 외부에서 객체를 얻을 수 있는 유일한 방법이다. 이 메소드가 반환하는 객체는 오직 하나, 즉 정적 필드가 참조하는 싱글톤 객체이다.