네트워크 캠퍼스/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. 팝업 스토어 클래스 내부에서 오버라이딩이 필수인 메서드가 누락될 수 있다.