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;
}
}
private 접근 제한자는 외부에서 접근할 수 없어 원래는 new 키워드를 이용한 객체 생성이 불가능하다.
이 때 위와 같이 static 키워드를 이용하여 singleton 타입의 객체를 생성할 수 있다.
public class MainClass {
public static void main(String[] args) {
// Singleton 객체는 생성자가 private이므로 인스턴스 생성 불가
// new 키워드로 객체 생성 및 조회가 불가능
// Singleton s1 = new Singleton();
Singleton s1 = Singleton.getInstance();
System.out.println("s1의 레퍼런스 : " + s1);
Singleton s2 = Singleton.getInstance();
System.out.println("s2의 레퍼런스 : " + s2);
}
}
위와 같이 Singleton 타입을 이용하여 객체를 생성할 수 있고, s1과 s2 모두 같은 주소를 참조하고 있는 것을 확인할 수 있다.
위와 같이 단 하나의 객체만을 생성해야 할 필요가 있을 때 Singleton 패턴을 이용한다.
- static 블록 안에는 static 변수만 사용해야 하고, static 메서드만 호출가능
- 지정된 변수와 메서드를 객체와 무관하게 만들어주기 때문에 this를 가질 수 없음
- non-static 메서드로 재정의될 수 엇음
- 대표적인 static 메서드는 main() 메서드
- static에 단순히 블록({})을 사용한 경우에는 정적 초기화자라고 부름
→ static 변수를 초기화하는 역할을 가지고 클래스가 로딩될 때 main() 메서드가 있더라도 앞서 딱 한 번 실행
2) 정적 변수 (static field)
- 모든 객체들이 공유하는 공유 변수
- 객체 생성 없이 클래스 이름만으로 참조 가능
- 객체를 만들어 참조할 수도 있지만 객체를 만들지 않고도 참조가 가능하기 때문에 이를 클래스 변수라고도 부름
public class Asean {
// 출석 20, 발표 30, 기말 50
public String name;
private int attendanceScore; // 출결점수
private int finalTermScore; // 기말점수
public static int presentationScore; // 29
public Asean(String name, int attendanceScore, int finalTermScore) {
this.name = name;
this.attendanceScore = attendanceScore;
this.finalTermScore = finalTermScore;
}
// static 블록 내부 코드는 프로그램 시작 시 즉시 자동으로 한 번 호출
static {
presentationScore = 29;
}
public void showStudentScore() {
System.out.println("학생명 : " + this.name);
System.out.println("출결점수 : " + this.attendanceScore);
System.out.println("발표점수 : " + presentationScore);
System.out.println("기말점수 : " + this.finalTermScore);
System.out.println("최종점수 : " + (this.attendanceScore + presentationScore + this.finalTermScore));
System.out.println("===========================================");
}
}
public class MainClass {
public static void main(String[] args) {
// 인스턴스생성 전부터 이미 조회 가능한 팀점수
System.out.println(Asean.presentationScore);
// 학생 4명 생성
Asean a1 = new Asean("가", 20, 43);
Asean a2 = new Asean("나", 15, 30);
Asean a3 = new Asean("다", 10, 35);
Asean a4 = new Asean("라", 18, 40);
a1.showStudentScore();
a2.showStudentScore();
a3.showStudentScore();
a4.showStudentScore();
// 어떤 인스턴스를 활용해도 팀 점수 조회 가능
System.out.println(a1.presentationScore);
System.out.println(a2.presentationScore);
System.out.println(a3.presentationScore);
System.out.println(a4.presentationScore);
}
}
위 그림과 같이 static 변수로 지정하면 main() 함수보다 앞서 실행되고, 해당 변수는 객체들이 전체적으로 공유하는 값으로 호출이 가능하다.
즉, 해당 변수는 힙 영역에 있는 것이 아니므로 this 키워드는 사용 불가하다.
위 코드에서 presentationScore가 public으로 선언되어 있으므로 어떤 인스턴스를 활용하더라도 호출이 가능하다.
2) 정적 메서드 (static method)
- 해당 클래스의 객체 생성 없이도 참조 가능
- static 메서드 안에서는 non-static 멤버를 객체 생성 없이 직접 참조 불가
- static 메서드 안에서는 static 변수를 선언할 수 없음
public class Asean {
// 출석 20, 발표 30, 기말 50
public String name;
private int attendanceScore; // 출결점수
private int finalTermScore; // 기말점수
private static int presentationScore; // 29
public Asean(String name, int attendanceScore, int finalTermScore) {
this.name = name;
this.attendanceScore = attendanceScore;
this.finalTermScore = finalTermScore;
}
// static 블록 내부 코드는 프로그램 시작 시 즉시 자동으로 한 번 호출
static {
presentationScore = 29;
}
// 스태틱 메서드도 객체 없이 바로 호출 가능
public static void showPresentationScore() {
System.out.println(presentationScore);
}
}
위 코드에서 presentationScore이 private으로 지정되어 있으므로 객체를 생성하지 않고서는 호출이 불가하다.
→ 스태틱 메서드를 활용하면 호출 가능
public class MainClass {
public static void main(String[] args) {
// 인스턴스생성 전부터 이미 조회 가능한 팀점수
// System.out.println(Asean.presentationScore); // private이므로 호출 불가
// 스태틱 메서드도 스태틱 변수처럼 호출 가능
Asean.showPresentationScore();
}
}
위와 같이 static 메서드를 활용하여 접근 가능하고 private 변수로 선언되어 있어 외부에서 변수의 값 자체를 변경할 수 없다는 장점이 있다.
- promotion은 멤버 변수의 값을 대입할 때 발생하지만, 메서드 호출 시 사용하는 매개변수에도 발생 가능
- 보통 메서드 호출 시 선언부에서 지정한 데이터타입과 일치하는 매개값을 전달하여 호출하지만, 매개변수에 다형성을 적용하면 자식 객체 전달 가능
1) 강제 타입 변환(Type Casting)
→ 부모 타입을 자식 타입으로 변환하는 것
- 부모 타입으로 형 변환이 된 자식 객체만 강제 타입 변환 사용 가능
public class Parent {
public void method1() {
System.out.println("부모측 1번 메서드 호출");
}
public void method2() {
System.out.println("부모측 2번 메서드 호출");
}
}
public class Child extends Parent{
@Override
public void method2() {
System.out.println("자식측에서 재정의한 2번 메서드");
}
public void method3() {
System.out.println("자식만 가지고 있는 3번 메서드");
}
}
public class MainClass1 {
public static void main(String[] args) {
// Promotion이 적용되면 자식클래스에만 정의된 요소 조회 불가능
Parent p = new Child();
p.method1(); // Parent에만 정의된 메서드
p.method2(); // 오버라이딩된 메서드는 타입상관없이 인스턴스 내 자식쪽 호출
//p.method3(); // p 타입 변수로는 자식쪽에만 정의된 요소 호출 불가
System.out.println("-----------------");
}
}
Parent p = new Child;
① 좌변에 부모클래스, 우변에 자식클래스
② 동일한 메서드가 있어야 함 (@Override)
→ 우선순위는 자식쪽에 있음, 즉 부모타입임에도 불구하고 자식쪽 메서드를 호출할 수 있다. → Promotion
public class MainClass1 {
public static void main(String[] args) {
// Promotion이 적용되면 자식클래스에만 정의된 요소 조회 불가능
Parent p = new Child();
p.method1(); // Parent에만 정의된 메서드
p.method2(); // 오버라이딩된 메서드는 타입상관없이 인스턴스 내 자식쪽 호출
//p.method3(); // p 타입 변수로는 자식쪽에만 정의된 요소 호출 불가
System.out.println("------------------------");
Child c = (Child)p; // 부모타입 변수를 자식타입으로 강제형변환(cast)
c.method2(); // 위와 동일하게 자식측 method2 호출
c.method3(); // 형 변환 후에는 자식쪽에만 있는 요소도 호출 가능
Child cc = new Child(); // 애초에 자식타입으로 인스턴스 대입 시에도
cc.method3(); // 자식쪽에만 있는 요소 호출 가능
}
}
public class Child2 extends Parent{
@Override
public void method2() {
System.out.println("2번 자식측에서 재정의한 2번 메서드");
}
public void method3() {
System.out.println("2번 자식만 가지고 있는 3번 메서드");
}
}
public class MainClass2 {
public static void main(String[] args) {
// Parent 타입에는 Child, Child2 모두 대입 가능
Parent p1 = new Child(); // 이 부분만 고쳐도 되는 효율성
p1.method2();
// Parent 타입으로는 Child1의 method2도, Child2의 method2도 호출 가능
// method2는 Parent에도 정의되어 있지만, Child와 Child2에서 오버라이딩된 메서드이기 때문에 호출 가능
}
}
public class MainClass2 {
public static void main(String[] args) {
// Parent 타입에는 Child, Child2 모두 대입 가능
Parent p1 = new Child2(); // 이 부분만 고쳐도 되는 효율성
p1.method2();
// Parent 타입으로는 Child1의 method2도, Child2의 method2도 호출 가능
// method2는 Parent에도 정의되어 있지만, Child와 Child2에서 오버라이딩된 메서드이기 때문에 호출 가능
}
}
2. instanceof 키워드
→ 객체가 지정한 클래스의 인스턴스인지 아닌지 검사할 때 사용
Person p = new student();
p instanceof Student // true 반환
왼쪽 항의 객체가 오른쪽 항 클래스의 인스턴스, 즉 오른쪽 항의 객체가 생성되었다면 true 리턴
public class Human {
public String name;
public int age;
public Human(String name, int age) {
this.name = name;
this.age = age;
}
public Human() {
this("noname", 0);
// this.name = "noname";
// this.age = 0;
}
public void showInfo() {
System.out.println("이름: " + this.name);
System.out.println("나이: " + this.age);
System.out.println("---------------");
}
}
public class Student extends Human {
public Student(String name, int age) {
super(name, age);
}
}
public class Cat {
private String name;
private int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public void meow() {
System.out.println("야옹 야옹");
}
}
public class MainClass {
public static void main(String[] args) {
// Human, Student, Cat 인스턴스 생성
Human h1 = new Human("김자바", 20);
h1.showInfo();
Student s1 = new Student("태국인", 19);
s1.showInfo();
Cat c1 = new Cat("룰루", 6);
c1.meow();
System.out.println(h1 instanceof Human); // h1이 Human 기반인가
System.out.println(s1 instanceof Human); // s1이 Human 기반인가
System.out.println(h1 instanceof Student); // h1이 Student 기반인가
System.out.println(s1 instanceof Student); // s1이 Student 기반인가
// 관련이 아예 없는 객체간 비교는 에러 발생
//System.out.println(h1 instanceof Cat);
//System.out.println(s1 instanceof Cat);
}
}