네트워크 캠퍼스/JAVA
240115 싱글톤 패턴, final(class/변수), 상수, abstract
gayeon_
2024. 1. 15. 16:24
싱글톤 패턴(Singleton Pattern)
- 싱글톤 패턴은 어떤 클래스의 객체는 오직 하나임을 보장하며, 이 객체에 접근할 수 있는 전역적인 접촉점을 제공하는 패턴이다.
- 클래스 객체를 유일하게 하나만 생성하여 모든 곳에서 하나의 객체에 접근하게 하여, 전역의 개념으로 객체를 사용할 수 있다.
- 싱글톤 패턴은 객체의 생성을 제한하기 위해 사용한다.
싱글톤 예제
package singleton;
public class Singleton {
/*
* 싱글턴 패턴 - 객체를 1개만 만들어 유지하기 위한 디자인 패턴
* 1. 외부에서 생성자를 사용할 수 없도록 생성자에 private을 붙인다
*/
private Singleton() {}
/*
* 2. 자신의 클래스 내부에서 스스로의 객체 1개를 생성한다.
* 이때, 멤버변수는 힙에 할당된 객체 없이 쓸 수 있도록 static이다.
*/
private static Singleton instance;
static {
instance = new Singleton();
}
/*
* 3. 외부에서 이 클래스의 객체를 필요로 하는 경우
* 2번에서 static으로 생성된 객체의 주소를 return한다.
*/
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
package singleton;
public class MainClass {
public static void main(String[] args) {
// Singleton 객체는 생성자가 private이므로 인스턴스 생성 불가
// Singleton s1 = new Singleton();
Singleton s1 = Singleton.getInstance();
System.out.println("s1의 레퍼런스: " + s1);
Singleton s2 = Singleton.getInstance();
System.out.println("s2의 레퍼런스: " + s2);
}
}
예제의 메모리구조는 위와 같다.
final
- final 키워드는 클래스, 메서드, 변수에 적용되며 abstract와 동시에 사용될 수 없다.
- final 클래스의 경우에는 상속이 안된다. 즉 서브클래스를 가질 수 없다.
- final 메서드는 재정의를 할 수 없다.
- final 변수는 값을 변경할 수 없다.
final class
- 클래스 선언 시 final을 사용하면 그 클래스는 상속이 불가능해진다.
- final 클래스는 자식 클래스를 가질 수 없고, 오직 외부에서 객체 생성을 통해서만 사용할 수 있다.
- final 클래스의 대표적인 예가 String 클래스이다. 사용자가 임의로 String 클래스를 상속받아 메서드를 재정의하는 것을 방지하기 위해 사용한다.
final method
- final 메서드는 자식 클래스에서 부모 클래스의 메서드를 재정의하지 못하게 한다.
- 하지만 클래스에 final이 붙지 않는다면 상속은 가능하므로 자식 클래스에서 final 메서드의 참조는 가능하다.
- 자식 클래스에서 반드시 부모의 메서드를 기능의 변경없이 사용하도록 강요할 경우에 final 메서드를 선언한다.
final 메서드 예제
package final_.method;
public class Parent {
public void method1() {}
public final void method2() {} // 상속 시 오버라이딩 불가
}
package final_.method;
public class Child extends Parent {
@Override
public void method1() {
// 자식 쪽에서 오버라이딩
}
// @Override
// public void method2() {
// // final이 붙은 메서드는 재정의 불가능
// }
}
final 변수
- final 변수는 한 번 값을 할당하면 그 값을 변경할 수 없다.
- final 변수는 선언시에 초기화하는 방법과 생성자를 통하여 초기화하는 방법이 있는데 만약 초기화하지 않고 남겨두면 컴파일 에러가 발생한다.
final 변수 예제
package final_.field;
public class Person {
/*
* final 변수는 단 한 번 초기화될 수 있고
* 이후에는 변경이 불가능하기 때문에
* 선언 시에 아예 직접 초기화를 해주거나
* 혹은 생성자에서 초기화를 해줘야 한다.
*/
public final String nationality = "대한민국";
public final String name; // 선언부 초기화를 안 하면 생성자 초기화 필요
public int age; // final이 없는 멤버변수는 초기화하지 않아도 된다.
public Person(String name) {
this.name = name;
}
}
package final_.field;
public class MainClass {
public static void main(String[] args) {
Person kim = new Person("김자바");
// kim.nationality = "일본"; // final 변수라 변경 불가
// kim.name = "이자바"; // final 변수라 변경 불가
kim.age = 25;
System.out.println("국적: " + kim.nationality); // public이므로 조회 가능
System.out.println("이름: " + kim.name); // public이므로 조회 가능
System.out.println("나이: " + kim.age); // public이므로 조회 가능
}
}
String에 final이 붙는 예제
package final_.field;
public class Collector {
// 참조형 변수를 가진 경우 변수 자체의 주는 final이지만
// 참조형 변수의 내부자료까지 바뀌지 않음을 보장하지 않는다.
final String[] stickers = {"소금빵", "초코소라삥", "파운드"};
}
package final_.field;
import java.util.Arrays;
public class MainClass2 {
public static void main(String[] args) {
Collector c1 = new Collector();
System.out.println(Arrays.toString(c1.stickers));
c1.stickers[0] = "크로와상";
System.out.println(Arrays.toString(c1.stickers));
}
}
* c1.stickers[0]이 변경될 수 있는 이유는
String의 경우 내부적으로 String pool을 참조하기 때문에 변경이 가능하다.
1000번지라는 주소만 변경되지 않으면 되기 때문이다.
상수(static final)
- 자바에서는 불변의 값을 저장하는 필드를 상수(constant)라고 부른다.
- 상수는 객체마다 저장할 필요가 없는 공용성을 가져야 하며, 여러가지 값으로 초기화될 수 없기 때문에 static과 final 제한자를 동시에 붙여 선언한다.
- 상수 이름은 모두 대문자로 작성하는 것이 관례이다. 연결된 단어라면 (_)로 단어들을 연결해준다.
상수 예제
package final_.const_;
// 상수를 선언할 때는 상수 집합을 만드는 목적으로
// 아래와 같이 클래스를 선언한다.
public class CountrySizes {
// 상수는 보통 public으로 풀고 사용한다. (값 변경 불가능, 공용성을 띈다)
public final static int KOREA_SIZE = 1003631;
public final static int STATES_SIZE = 9833519;
public final static int THAILAND_SIZE = 513120;
}
package final_.const_;
public class MainClass {
public static void main(String[] args) {
// 상수만 모아둔 클래스 특성상 클래스명이 곧 집합을 대표하는 이름이 된다.
System.out.println(CountrySizes.KOREA_SIZE);
System.out.println(CountrySizes.STATES_SIZE);
System.out.println(CountrySizes.THAILAND_SIZE);
// 이를 잘 사용하는 예시는 자바의 Math 클래스가 있다.
System.out.println(Math.PI);
System.out.println(Math.E);
}
}
abstract
- abstract 키워드는 클래스와 메서드에 적용된다.
- 추상(abstract) 클래스는 실체 클래스들의 멤버변수와 메서드들의 이름을 통일할 목적으로 사용한다.
- 추상(abstract) 메서드가 있는 클래스는 반드시 추상 클래스여야 한다.
- 그러나 추상 클래스에 반드시 추상 메서드만 선언할 필요는 없고 일반 메서드도 선언할 수 있다.
abstract 키워드가 없는 예제
package abstract_.noabs;
public class PopupStore {
public void orderApple() {
System.out.println("착즙 사과주스를 내줍니다. 가격은 못정했습니다.");
}
public void orderOrange() {
System.out.println("착즙 오렌지주스를 내줍니다. 가격은 못정했습니다.");
}
public void orderGrape() {
System.out.println("착즙 포도주스를 내줍니다. 가격은 못정했습니다.");
}
}
package abstract_.noabs;
public class Store extends PopupStore{
@Override
public void orderApple() {
System.out.println("착즙 사과주스를 팝니다. 가격은 20000원입니다.");
}
@Override
public void orderOrange() {
System.out.println("착즙 오렌지주스를 팝니다. 가격은 20000원입니다.");
}
// 실수로 포도주스 가격 업데이트 누락
// @Override
// public void orderGrape() {
// System.out.println("착즙 포도주스를 팝니다. 가격은 20000원입니다.");
// }
}
package abstract_.noabs;
public class MainClass {
public static void main(String[] args) {
// 2가지 문제점
// 1. 정식 매장이 존재하는데 팝업스토어 생성 가능
PopupStore ps = new PopupStore();
// 2. 팝업스토어 클래스 내부에서 오버라이딩이 필수인 메서드가 누락될 수도 있음
// 부모타입으로 promotion 됐기 때문에 s가 사용 가능한 영역은 PopupStore
PopupStore s = new Store();
s.orderApple(); // 사과주스 가격 책정됨
s.orderOrange(); // 오렌지주스 가격 책정됨
s.orderGrape(); // 포도주스 가격 누락됨
}
}
실행 결과
위 코드의 문제점 2가지
1. 정식 매장이 존재하는데 팝업 스토어 생성이 가능하다.
2. 팝업 스토어 클래스 내부에서 오버라이딩이 필수인 메서드가 누락될 수 있다.