backend
  • README
  • DOCS
    • Java Docs
    • Servlet Docs
    • JSP Docs
    • DB & SQL Docs
    • Spring Boot Docs
    • Spring Security Docs
    • AWS Docs
  • 설치하기
    • Intellij 설정
  • 자바
    • 01 Java란?
    • 02 자바 시작하기
    • 03 자료형과 연산자
    • 04 제어문
    • 05 메소드
    • 06 클래스 기초
      • Static 보충자료
      • 패키지 보충자료
    • 07 객체지향 프로그래밍
    • 08 클래스 더 알아보기
      • 열거형 ENUM 보충자료
    • 09 클래스와 자료형
      • 다형성 보충자료
      • 제네릭 보충자료
    • 10 컬렉션 프레임워크
      • 컬렉션 프레임워크 보충자료
    • 11 람다식과 함수형 프로그래밍
      • 람다식 보충자료
    • 12 오류 대비하기
      • 오류 보충자료
    • 13 멀티태스킹
      • 멀티태스킹 보충자료
    • 교재보충
      • java.lang
  • 스프링
    • 서블릿, JSP
      • 05 Servlet(서블릿)
        • 서블릿 보충자료
        • 서블릿 추가코드
        • XML, YAML, JSON
      • 06 JSP(자바 서버 페이지)
        • JSP 보충자료
      • 07 JSTL(JSP 스탠다드 태그 라이브러리)
        • JSTL 보충자료
      • 08 Cookie(쿠키), Session(세션)
      • 09 서블릿,필터,리스너
        • 서블릿,필터,리스너 보충자료
      • 11 도서관리 프로젝트 실습
    • Spring Boot
      • 01 스프링 등장 배경, 객체지향
        • 스프링 등장 배경, 객체지향 보충자료
      • 02 IOC(제어의 역전), DI(의존성 주입)
        • IOC 보충자료
        • DI 보충자료
      • 03 스프링 구조
        • 스프링 구조 보충설명
      • 04 테스트코드 실습
      • 05 스프링 빈 설정
        • 스프링 빈 설정 보충자료
      • 06 싱글톤
        • 싱글톤 보충 자료
      • 07 스프링 빈 자동설정
        • 스프링 빈 자동설정 보충자료
      • 08 빈 생명주기
        • 빈 생명주기 보충자료
      • 09 빈 스코프
        • 빈 스코프 보충자료
      • 10 스프링 MVC
        • 스프링 MVC 보충자료
        • 데이터베이스 연동에 필요한 부분
      • 11 Validation(검증)
        • Validation(검증) 보충자료
      • 12 Bean Validation(빈검증)
        • Bean Validation(빈검증) 보충자료
      • 13 예외처리
        • 예외처리 보충자료
      • 14 타입변환
      • 15 JDBC(Java Database Connectivity)
      • 16 커넥션풀
      • 17 트랜잭션
        • 트랜잭션 보충자료
      • 18 JDBC 템플릿 활용
      • 19 MyBatis
      • 20 JPA(Java Persistence API)
      • 22 게시판 프로젝트 실습
    • Spring Security
      • 보안(Security)
      • Spring Security
      • 2. Spring Security 알아보기
        • 보안 위협 실제 사례와 방어 전략
      • 3. Spring Security 기본 동작 흐름
      • 4. Spring Security로 인증 권한 추가하기
        • Spring Security의 인증 및 인가
      • 5. Spring Security에서 세션 관리하기
        • 세션(Session)과 쿠키(Cookie) 비교, 토큰(Token)과의 관계
        • 해싱 및 해싱알고리즘
        • base64
      • 6. Spring Security 악용 보호
        • SameSite
      • 7. Spring Security로 인가 권한 추가하기
      • 8. Bcrypt(비크립트) 암호화
      • OAuth2 적용하기
  • 네트워크
    • HTTP
    • OSI 7계층
  • DB&SQL
    • 01 Database(데이터베이스)와 SQL 개요
    • 02 관계형 모델
    • 03 집합
    • 04 JOIN 연산
    • 05 MySQL
      • 세이브포인트
      • DBeaver, Mysql 오토커밋 설정 관련
    • 06 SQL 기초
      • 예시데이터 쿼리문
    • 07 SQL 실습
      • 실습 스키마
    • 08 Join 활용
      • 실습스키마
    • 09 SQL 활용
      • 실습스키마
    • 10 정규화
      • 실습 스키마
    • 데이터타입
    • 예시 프로젝트 스키마 구성
  • AWS
    • SSL 연결하기
    • 보충설명
Powered by GitBook
On this page
  • 접근 제어자(Access Modifier)와 캡슐화(Encapsulation)
  • 1. public (공개 접근 제어자)
  • 2. protected (보호된 접근 제어자)
  • 3. default (패키지 전용 접근 제어자)
  • 4. private (비공개 접근 제어자)
  • 접근 제어자 비교 예제
  • 접근 제어자 요약
  • Getter 와 Setter
  • 1. Getter
  • Getter 메소드 예시
  • 2. Setter
  • Setter 메소드
  • Getter와 Setter의 역할
  • Getter
  • Setter
  • 예시: Getter와 Setter를 활용한 캡슐화
  • 사용 예시
  • 상속(Inheritance)
  • 1. 상속의 기본 구조
  • 예시: 상속 기본 예제
  • 출력 결과
  • 2. 상속의 장점
  • 3. 메소드 오버라이딩(Overriding)
  • 오버라이딩 예시
  • 출력 결과
  • 오버로딩과 오버라이딩
  • 4. super 키워드
  • super 키워드 예시
  • 출력 결과
  • 5. 상속에서의 생성자
  • 상속에서의 생성자 예시
  • 6. 상속의 제한
  • 다형성
  • 1. 다형성과 instanceof의 기본 개념
  • 2. instanceof 연산자의 사용
  • 3. 예시: 다형성과 instanceof
  • 출력 결과
  • 설명
  • 보충 - 정적 바인딩과 동적 바인딩
  • 4. instanceof의 사용 이유
  • 5. 다운캐스팅과 instanceof의 관계
  • 다운캐스팅 예시
  • 다운캐스팅을 통한 자식 클래스의 기능 사용 예제
  • 출력 결과
  • 설명
  • final 클래스
  • 1. final 클래스의 특징
  • 2. final 클래스 선언
  • 3. final 클래스의 상속 불가
  • 4. final 클래스의 사용 예
  • 5. final 메소드와의 차이
  • 예시: final 메소드
  • 추상 클래스(Abstract Class)
  • 추상 클래스 선언 예시
  • 추상 메소드(Abstract Method)
  • 추상 메소드 선언 예시
  • 3. 추상 클래스와 추상 메소드의 사용 예
  • 출력 결과
  • 4. 추상 클래스와 추상 메소드의 역할
  • 인터페이스
  • 1. 인터페이스의 주요 특징
  • 2. 인터페이스 선언
  • 3. 인터페이스 구현
  • 인터페이스 구현 예시
  • 출력 결과
  • 4. 인터페이스의 장점
  • 5. 인터페이스와 추상 클래스의 차이점
  • 추상 클래스와 인터페이스 비교 예시
  • 6. 인터페이스와 다형성
  • 다형성 예시
  • 7. 디폴트 메소드와 정적 메소드
  • 디폴트 메소드와 정적 메소드 예시
  • 싱글턴 패턴(Singleton Pattern)
  • 1. 싱글턴 패턴의 주요 특징
  • 2. 싱글턴 패턴 구현 방법
  • 싱글턴 패턴 구현 예시
  • 설명
  • 정리
  1. 자바

07 객체지향 프로그래밍

접근 제어자(Access Modifier)와 캡슐화(Encapsulation)

접근 제어자(Access Modifier)는 클래스, 필드, 메소드, 생성자 등에 대해 다른 클래스에서 접근할 수 있는 범위를 지정하는 키워드입니다. Java에서 접근 제어자를 사용하면 객체 지향 프로그래밍의 중요한 개념인 캡슐화(Encapsulation)를 실현할 수 있습니다. 접근 제어자는 데이터를 보호하고, 외부 클래스가 필드나 메소드를 잘못 사용하지 못하도록 제어하는 데 사용됩니다.

Java에는 네 가지 주요 접근 제어자가 있습니다:

  1. public

  2. protected

  3. default (아무 접근 제어자를 명시하지 않은 경우)

  4. private

1. public (공개 접근 제어자)

  • 어디서나 접근 가능: 다른 클래스, 패키지에서 자유롭게 접근할 수 있습니다.

  • 모든 클래스가 접근할 수 있으며, 제한이 없습니다. 클래스 내부뿐만 아니라, 다른 패키지의 클래스에서도 접근할 수 있습니다.

2. protected (보호된 접근 제어자)

  • 같은 패키지 또는 **서브 클래스(자식 클래스)**에서 접근 가능합니다.

  • 다른 패키지의 클래스에서는 접근할 수 없지만, 해당 클래스를 상속한 자식 클래스에서는 접근이 가능합니다.

3. default (패키지 전용 접근 제어자)

  • 아무 접근 제어자도 명시하지 않았을 때 사용됩니다.

  • 같은 패키지 내의 클래스에서만 접근 가능하며, 다른 패키지에서는 접근할 수 없습니다.

  • default 접근 제어자는 패키지 전용입니다.

4. private (비공개 접근 제어자)

  • 해당 클래스 내부에서만 접근 가능합니다.

  • 외부 클래스에서는 물론, 같은 패키지나 상속받은 자식 클래스에서도 접근할 수 없습니다. 즉, 클래스 내부에서만 사용할 수 있는 가장 제한적인 접근 제어자입니다.

  • 주로 캡슐화를 위해 필드에 사용되며, 외부에서 필드에 직접 접근하지 못하도록 하고, 메소드를 통해서만 간접적으로 접근할 수 있게 합니다.

접근 제어자 비교 예제

accessModifier 패키지에 작성

// SmartPhone.java
public class SmartPhone {
    String model = "Galaxy";  // default 접근 제어자
    public String sdCard = "128GB";  // public 접근 제어자
    private String cpu = "Snapdragon";  // private 접근 제어자
    protected String os = "Android";  // protected 접근 제어자
}
// Main.java (같은 패키지)
public class Main {
    public static void main(String[] args) {
        SmartPhone phone = new SmartPhone();

        // default 접근 제어자 (같은 패키지에서는 접근 가능)
        System.out.println("모델: " + phone.model);  // 출력: 모델: Galaxy

        // public 접근 제어자 (어디서나 접근 가능)
        System.out.println("SD카드: " + phone.sdCard);  // 출력: SD카드: 128GB

        // private 접근 제어자 (접근 불가 - 컴파일 에러)
        // System.out.println("CPU: " + phone.cpu);  // 에러 발생

        // protected 접근 제어자 (같은 패키지에서 접근 가능)
        System.out.println("운영체제: " + phone.os);  // 출력: 운영체제: Android
    }
}

접근 제어자 요약

접근 가능
public
protected
default
private

해당 클래스 안에서

✅

✅

✅

✅

동일 패키지 안에서

✅

✅

✅

동일 패키지 또는 자손 클래스 안에서

✅

✅

다른 패키지 포함 어느 곳에서든

✅

Getter 와 Setter

Getter와 Setter는 객체의 필드(변수)에 대한 접근을 제어하는 메소드입니다. 이들은 객체 지향 프로그래밍에서 캡슐화(Encapsulation)를 실현하는 중요한 도구로, 필드에 직접 접근하지 않고, 메소드를 통해 간접적으로 값을 읽거나 수정할 수 있도록 합니다. 이를 통해 데이터의 무결성을 유지하고, 외부에서 필드의 값을 잘못 변경하지 않도록 보호할 수 있습니다.

1. Getter

  • Getter 메소드는 객체의 필드 값을 반환하는 역할을 합니다.

  • 보통 필드에 접근하는데 사용되며, 메소드 이름은 get으로 시작하고 필드 이름을 이어 붙여 작성합니다.

  • Getter 메소드는 주로 읽기 전용 필드에 사용되며, 필드 값을 외부에서 조회할 수 있게 합니다.

Getter 메소드 예시

getterSetterExample 패키지

public class Person {
    private String name;  // private 필드 (외부에서 직접 접근 불가)

    // Getter 메소드
    public String getName() {
        return name;  // 필드 값을 반환
    }
}

위 코드에서 getName() 메소드를 사용하면 name 필드의 값을 반환할 수 있습니다. 필드가 private로 선언되어 외부에서 직접 접근할 수 없지만, Getter 메소드를 통해 간접적으로 값을 읽을 수 있습니다.

public class Main {
    public static void main(String[] args) {
        Person person = new Person();
        person.name = "홍길동";  // 접근 불가 (private 필드)

        System.out.println(person.getName());  // Getter로 접근 가능
    }
}

2. Setter

  • Setter 메소드는 객체의 필드 값을 수정하는 역할을 합니다.

  • 보통 set으로 시작하고 필드 이름을 이어 붙여 작성합니다.

  • Setter 메소드를 통해 필드 값을 변경할 때, 검증 로직을 추가하여 필드에 적절한 값만 저장되도록 제어할 수 있습니다.

Setter 메소드

getterSetterExample 패키지 Person에 코드추가

// Setter 메소드
public void setName(String name) {
this.name = name;  // 필드 값을 설정
}

위 코드에서 setName() 메소드를 사용하면 name 필드의 값을 변경할 수 있습니다. 필드가 private로 선언되어 외부에서 직접 수정할 수 없지만, Setter 메소드를 통해 간접적으로 값을 설정할 수 있습니다.

getterSetterExample 패키지 Main에 코드추가

person.setName("홍길동");  // Setter로 필드 값을 설정

System.out.println(person.getName());  // Getter로 값 확인

인텔리제이 단축키로 기본적인 getter 와 setter 작성

  • 메뉴 - 코드 - 생성

    • 윈도우: alt + insert

    • 맥: command + N

  • getter 및 setter 선택

Getter와 Setter의 역할

Getter

  • 객체의 필드 값을 조회할 때 사용합니다.

  • 필드가 private로 선언되었기 때문에, 외부에서 직접 접근할 수 없고, Getter를 통해 값에 접근할 수 있습니다.

Setter

  • 객체의 필드 값을 설정할 때 사용합니다.

  • 필드에 직접 접근하지 못하도록 하고, Setter 메소드를 통해 값을 변경할 수 있습니다. 또한 Setter 내부에서 검증 로직을 추가하여 유효하지 않은 값이 설정되지 않도록 할 수 있습니다.

예시: Getter와 Setter를 활용한 캡슐화

useGetterSetterEncapsulation 패키지에 작성

public class Person {
    private String name;
    private int age;

    // Getter for name
    public String getName() {
        return name;
    }

    // Setter for name
    public void setName(String name) {
        this.name = name;
    }

    // Getter for age
    public int getAge() {
        return age;
    }

    // Setter for age (나이가 0보다 작으면 안 됨)
    public void setAge(int age) {
        if (age > 0) {
            this.age = age;
        } else {
            System.out.println("나이는 0보다 커야 합니다.");
        }
    }
}

사용 예시

public class Main {
    public static void main(String[] args) {
        Person person = new Person();

        // Setter를 통해 필드 값을 설정
        person.setName("홍길동");
        person.setAge(25);

        // Getter를 통해 필드 값을 조회
        System.out.println("이름: " + person.getName());  // 출력: 이름: 홍길동
        System.out.println("나이: " + person.getAge());   // 출력: 나이: 25

        // 잘못된 값 설정 시 메시지 출력
        person.setAge(-5);  // 출력: 나이는 0보다 커야 합니다.
    }
}

위 예시에서 name과 age 필드는 private으로 선언되어 외부에서 직접 접근할 수 없지만, getName(), setName(), getAge(), setAge() 메소드를 통해 간접적으로 접근할 수 있습니다. 또한, setAge() 메소드에서 나이를 설정할 때, 검증 로직을 추가하여 유효하지 않은 나이를 입력하면 오류 메시지를 출력하게 했습니다.

상속(Inheritance)

상속(Inheritance)은 객체 지향 프로그래밍(OOP)에서 중요한 개념 중 하나로, 기존 클래스(부모 클래스, 슈퍼 클래스, 상위 클래스)의 속성과 메소드를 새로운 클래스(자식 클래스, 서브 클래스, 하위 클래스)가 물려받는 기능을 말합니다. 이를 통해 코드의 재사용성을 높이고, 새로운 기능을 추가하거나 기존 기능을 확장할 수 있습니다.

Java에서는 상속을 사용하여 클래스 간의 계층 구조를 만들 수 있으며, 자식 클래스는 부모 클래스의 모든 필드와 메소드를 상속받아 사용할 수 있습니다. 또한, 필요에 따라 상속받은 메소드를 재정의(오버라이딩(Overriding))하여 자식 클래스에서 다른 동작을 구현할 수도 있습니다.

1. 상속의 기본 구조

Java에서 상속은 extends 키워드를 사용하여 구현합니다.

class 부모클래스 {
    // 부모 클래스의 필드와 메소드
}

class 자식클래스 extends 부모클래스 {
    // 자식 클래스에서 추가할 필드와 메소드
}

예시: 상속 기본 예제

inheritanceAnimal 패키지에 Animal과 Dog, Main 작성

// 부모 클래스 (Animal)
class Animal {
    String name;

    void eat() {
        System.out.println(name + "가 음식을 먹습니다.");
    }
}

// 자식 클래스 (Dog)
class Dog extends Animal {
    // Dog 클래스만의 추가 필드와 메소드
    void bark() {
        System.out.println(name + "가 짖습니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();  // Dog 클래스의 객체 생성
        dog.name = "바둑이";   // 부모 클래스의 필드 사용
        dog.eat();            // 부모 클래스의 메소드 사용
        dog.bark();           // 자식 클래스의 메소드 사용
    }
}

출력 결과

바둑이가 음식을 먹습니다.
바둑이가 짖습니다.
  • Dog 클래스는 Animal 클래스를 상속받아 name 필드와 eat() 메소드를 물려받았으며, bark()라는 고유한 메소드를 추가로 정의했습니다.

  • 자식 클래스인 Dog 객체는 부모 클래스의 필드와 메소드를 그대로 사용할 수 있으며, 자신만의 추가 기능도 구현할 수 있습니다.

2. 상속의 장점

  1. 코드 재사용: 부모 클래스의 코드(필드와 메소드)를 재사용하여 코드 중복을 줄이고, 유지보수가 용이해집니다.

  2. 확장성: 자식 클래스는 부모 클래스의 기능을 그대로 물려받고, 필요에 따라 새로운 기능을 추가할 수 있습니다.

  3. 유지보수성: 부모 클래스의 코드를 수정하면, 상속받은 자식 클래스들에도 자동으로 반영됩니다.

3. 메소드 오버라이딩(Overriding)

  • 오버라이딩(Overriding)은 자식 클래스에서 부모 클래스의 메소드를 재정의하는 것을 말합니다. 자식 클래스는 상속받은 부모 클래스의 메소드를 그대로 사용할 수도 있지만, 자식 클래스의 요구에 맞게 해당 메소드를 다른 방식으로 구현할 수 있습니다. 오버라이딩할 때는 부모 클래스의 메소드와 이름, 매개변수, 리턴 타입이 동일해야 합니다.

오버라이딩 예시

overridingAnimal 패키지에 Animal과 Dog, Main 작성

// 부모 클래스 (Animal)
class Animal {
    String name;

    void sound() {
        System.out.println("동물이 소리를 냅니다.");
    }
}

// 자식 클래스 (Dog)
class Dog extends Animal {
    // 부모 클래스의 sound() 메소드를 오버라이딩
    @Override
    void sound() {
        System.out.println(name + "가 멍멍 짖습니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.name = "바둑이";
        dog.sound();  // 오버라이딩된 메소드 호출
    }
}

출력 결과

바둑이가 멍멍 짖습니다.
  • Dog 클래스는 부모 클래스인 Animal의 sound() 메소드를 오버라이딩하여, Dog 객체에서 호출할 때는 "멍멍"이라는 소리를 출력하도록 변경했습니다.

  • @Override 어노테이션은 오버라이딩한 메소드임을 명시하는 데 사용되며, 컴파일러가 올바르게 오버라이딩했는지 확인할 수 있게 합니다.

    • 부모의 특정 메소드를 오버라이드함을 명시

      • 없어도 오류가 나지는 않음

      • 붙였는데 메소드명이 다를 시 오류 (실수 방지)

인텔리제이 단축키로 오버라이드 메소드 생성 가능

  • 메뉴 - 코드 - 생성

    • 윈도우: alt + insert

    • 맥: command + N

  • Override Methods 선택

오버로딩과 오버라이딩

  • 오버로딩은 같은 이름의 메소드의 매개변수를 다르게 설정하는 것

  • 오버라이딩은 부모의 메소드를 자식이 재정의

  • 헷갈릴 수 있으나 매우 중요한 개념이므로 정확하게 알아둘 것

4. super 키워드

super 키워드는 부모 클래스의 필드나 메소드에 접근할 때 사용됩니다. 이를 통해 자식 클래스에서 부모 클래스의 메소드를 호출하거나 필드에 접근할 수 있습니다. 오버라이딩한 메소드 내부에서 부모 클래스의 메소드를 호출하고 싶을 때도 super를 사용할 수 있습니다.

super 키워드 예시

superKeywordAnimal 패키지에 Animal과 Dog, Main 작성

// 부모 클래스 (Animal)
class Animal {
    void sound() {
        System.out.println("동물이 소리를 냅니다.");
    }
}

// 자식 클래스 (Dog)
class Dog extends Animal {
    @Override
    void sound() {
        super.sound();  // 부모 클래스의 sound() 메소드 호출
        System.out.println("멍멍 짖습니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.sound();  // 부모 메소드와 자식 메소드 모두 실행
    }
}

출력 결과

동물이 소리를 냅니다.
멍멍 짖습니다.
  • 자식 클래스에서 super.sound()를 호출하여 부모 클래스의 sound() 메소드를 실행한 후, 자식 클래스의 추가 로직을 실행합니다.

5. 상속에서의 생성자

  • 자식 클래스의 생성자는 자동으로 부모 클래스의 생성자를 호출합니다. 이때, 부모 클래스의 기본 생성자가 먼저 호출되고, 그다음에 자식 클래스의 생성자가 실행됩니다.

  • 만약 부모 클래스에 매개변수가 있는 생성자가 있다면, 자식 클래스에서 super(매개변수)를 사용해 명시적으로 부모 생성자를 호출해야 합니다.

상속에서의 생성자 예시

inheritanceAnimal 패키지에 Animal과 Dog, Main 작성

// 부모 클래스 (Animal)
class Animal {
    String name;

    // 매개변수가 있는 생성자
    Animal(String name) {
        this.name = name;
    }
}

// 자식 클래스 (Dog)
class Dog extends Animal {
    // 자식 클래스의 생성자에서 부모 생성자를 호출
    Dog(String name) {
        super(name);  // 부모 클래스의 생성자 호출
    }

    void display() {
        System.out.println("이름: " + name);
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog("바둑이");  // 자식 클래스의 생성자 호출
        dog.display();  // 출력: 이름: 바둑이
    }
}
  • 자식 클래스 Dog의 생성자에서 super(name)을 사용하여 부모 클래스 Animal의 생성자를 호출합니다.

  • 부모 클래스의 생성자를 호출하면, 부모 클래스의 필드 name이 초기화됩니다.

6. 상속의 제한

  • 단일 상속: Java에서는 다중 상속을 지원하지 않으며, 하나의 클래스는 오직 한 개의 부모 클래스만 상속받을 수 있습니다. 다중 상속으로 인한 모호성을 방지하기 위함입니다.

다형성

다형성(Polymorphism)과 instanceof 연산자는 객체 지향 프로그래밍에서 중요한 역할을 합니다. 다형성은 하나의 객체가 여러 가지 형태를 가질 수 있는 능력을 의미하며, 부모 클래스의 참조 변수로 자식 클래스를 참조할 수 있게 합니다. instanceof 연산자는 객체가 특정 클래스나 인터페이스의 인스턴스인지를 확인하는 데 사용됩니다. 이를 통해 다형성의 환경에서 어떤 객체가 실제로 어떤 클래스의 인스턴스인지 확인하여 올바른 동작을 할 수 있게 합니다.

1. 다형성과 instanceof의 기본 개념

  • 다형성: 부모 클래스 타입의 변수로 여러 자식 클래스의 객체를 참조할 수 있습니다. 하지만 자식 클래스의 고유한 기능을 사용해야 할 때, 해당 객체가 실제로 어떤 클래스의 인스턴스인지 알아야 하는 경우가 있습니다. 이때 instanceof 연산자를 사용합니다.

  • instanceof 연산자: 객체가 특정 클래스의 인스턴스인지를 확인할 때 사용되며, 결과는 true 또는 false로 반환됩니다.

2. instanceof 연산자의 사용

instanceof는 객체가 특정 클래스나 인터페이스의 인스턴스인지 확인하는 연산자로, 형식은 다음과 같습니다.

객체명 instanceof 클래스명
  • 객체가 해당 클래스의 인스턴스면 true를 반환하고, 아니면 false를 반환합니다.

3. 예시: 다형성과 instanceof

polymorphismAndAnimal 패키지에 Animal과 Dog, Main 작성

// 부모 클래스
class Animal {
    void sound() {
        System.out.println("동물이 소리를 냅니다.");
    }
}

// 자식 클래스 Dog
class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("강아지가 멍멍 짖습니다.");
    }

    void bark() {
        System.out.println("강아지가 짖습니다.");
    }
}

// 자식 클래스 Cat
class Cat extends Animal {
    @Override
    void sound() {
        System.out.println("고양이가 야옹 소리를 냅니다.");
    }

    void purr() {
        System.out.println("고양이가 그르렁거립니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        // 다형성을 사용하여 Animal 타입의 참조 변수로 여러 자식 객체를 참조
        Animal myDog = new Dog();
        Animal myCat = new Cat();

        // 공통 메소드 sound() 호출 (오버라이딩된 메소드가 실행됨)
        myDog.sound();  // 강아지가 멍멍 짖습니다.
        myCat.sound();  // 고양이가 야옹 소리를 냅니다.

        // 객체가 실제로 어떤 클래스의 인스턴스인지 확인하기 위해 instanceof 사용
        if (myDog instanceof Dog) {
            Dog dog = (Dog) myDog;  // 다운캐스팅 (Animal -> Dog)
            dog.bark();  // 강아지가 짖습니다.
        }

        if (myCat instanceof Cat) {
            Cat cat = (Cat) myCat;  // 다운캐스팅 (Animal -> Cat)
            cat.purr();  // 고양이가 그르렁거립니다.
        }
    }
}

출력 결과

강아지가 멍멍 짖습니다.
고양이가 야옹 소리를 냅니다.
강아지가 짖습니다.
고양이가 그르렁거립니다.

설명

  1. 다형성: myDog과 myCat 변수는 Animal 타입이지만, 각각 Dog와 Cat 객체를 참조하고 있습니다. 따라서 다형성을 통해 부모 클래스 타입으로 여러 자식 객체를 참조할 수 있습니다.

  2. 메소드 오버라이딩: sound() 메소드는 Dog와 Cat 클래스에서 각각 오버라이딩되었기 때문에, myDog.sound()와 myCat.sound()는 각각 Dog와 Cat 클래스에서 구현된 메소드가 호출됩니다.

  3. instanceof 연산자:

    • myDog instanceof Dog는 myDog 객체가 Dog 클래스의 인스턴스인지 확인합니다. true가 반환되면 Dog로 다운캐스팅하여 bark() 메소드를 호출할 수 있습니다.

    • myCat instanceof Cat도 마찬가지로 Cat 객체인지 확인하고, 다운캐스팅하여 purr() 메소드를 호출합니다.

보충 - 정적 바인딩과 동적 바인딩

  • 정적 바인딩

    • 컴파일 시점(Compile-time)에 메서드 호출이 결정됩니다.

    • private, static, final 메서드와 같이 컴파일 타임에 호출이 확정되는 경우 정적 바인딩이 사용됩니다.

    • 예: 컴파일 시점에 메서드가 확정된 main() 함수 호출.

  • 동적 바인딩

    • 실행 시점에 객체의 실제 타입을 기준으로 호출할 메서드가 결정됩니다.

    • 일반적으로 오버라이딩(Overriding) 된 메서드에서 사용됩니다.

    • 예: 부모 클래스 타입으로 참조된 자식 객체에서 메서드를 호출할 때, 실제 객체의 메서드가 실행됨.

4. instanceof의 사용 이유

instanceof 연산자는 다음과 같은 상황에서 유용합니다.

  1. 다운캐스팅(Downcasting): 부모 클래스 타입의 참조 변수를 자식 클래스 타입으로 변환할 때, instanceof를 사용하여 안전하게 다운캐스팅할 수 있습니다. 이를 통해 해당 객체가 실제로 그 클래스의 인스턴스인지 확인한 후 다운캐스팅을 수행할 수 있습니다.

  2. 다양한 객체 처리: 다형성을 사용하면서, 동일한 부모 클래스를 상속한 여러 자식 객체를 처리할 때 instanceof를 사용하여 객체의 실제 타입을 확인하고, 해당 객체에 맞는 동작을 할 수 있습니다.

5. 다운캐스팅과 instanceof의 관계

다운캐스팅은 부모 클래스 타입을 자식 클래스 타입으로 변환하는 작업입니다. 하지만 잘못된 타입으로 캐스팅을 시도할 경우 ClassCastException이 발생할 수 있습니다. 이를 방지하기 위해 instanceof를 사용하여 안전하게 다운캐스팅할 수 있습니다.

다운캐스팅 예시

Animal myAnimal = new Dog();

if (myAnimal instanceof Dog) {
    Dog myDog = (Dog) myAnimal;  // 안전하게 다운캐스팅
    myDog.bark();  // 강아지가 짖습니다.
}
  • instanceof를 사용하여 myAnimal이 실제로 Dog 클래스의 인스턴스인지 확인한 후에 다운캐스팅을 수행하므로 안전합니다.

다운캐스팅을 통한 자식 클래스의 기능 사용 예제

downcastingAniaml 패키지에 ****Animal과 Dog 및 Cat, Main 작성

// 부모 클래스
class Animal {
    void sound() {
        System.out.println("동물이 소리를 냅니다.");
    }
}

// 자식 클래스 Dog
class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("강아지가 멍멍 짖습니다.");
    }

    // Dog 클래스의 고유 메소드
    void bark() {
        System.out.println("강아지가 크게 짖습니다.");
    }
}

// 자식 클래스 Cat
class Cat extends Animal {
    @Override
    void sound() {
        System.out.println("고양이가 야옹 소리를 냅니다.");
    }

    // Cat 클래스의 고유 메소드
    void purr() {
        System.out.println("고양이가 그르렁거립니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        // 부모 클래스 타입으로 자식 객체를 참조 (업캐스팅)
        Animal myAnimal = new Dog();

        // 부모 클래스 타입으로는 자식 클래스의 고유 메소드(bark)를 호출할 수 없음
        // myAnimal.bark();  // 컴파일 에러

        // 다운캐스팅을 통해 자식 클래스의 고유 메소드 사용
        if (myAnimal instanceof Dog) {
            Dog myDog = (Dog) myAnimal;  // 명시적 타입 변환 (다운캐스팅)
            myDog.bark();  // 자식 클래스의 고유 메소드 호출
        }

        // 다른 예시: Cat 객체를 참조
        myAnimal = new Cat();  // 업캐스팅
        if (myAnimal instanceof Cat) {
            Cat myCat = (Cat) myAnimal;  // 다운캐스팅
            myCat.purr();  // 자식 클래스의 고유 메소드 호출
        }
    }
}

출력 결과

강아지가 크게 짖습니다.
고양이가 그르렁거립니다.

설명

  1. 업캐스팅(Upcasting):

    • Animal myAnimal = new Dog();에서 myAnimal은 부모 클래스인 Animal 타입으로 업캐스팅되어 있습니다. 이때, myAnimal 참조 변수는 Dog 객체를 가리키고 있지만, 부모 클래스인 Animal의 기능만 사용할 수 있습니다.

    • myAnimal.bark()를 호출하려 하면 컴파일 에러가 발생합니다. 이는 Animal 클래스에는 bark() 메소드가 존재하지 않기 때문입니다.

  2. 다운캐스팅(Downcasting):

    • if (myAnimal instanceof Dog)을 통해 객체가 실제로 Dog 타입인지 확인한 후, 명시적 타입 변환을 사용하여 Dog 타입으로 다운캐스팅합니다.

    • 이렇게 변환된 객체(myDog)는 이제 자식 클래스 Dog의 고유 메소드인 bark()를 호출할 수 있습니다.

  3. 다른 예시:

    • 마찬가지로 Cat 클래스도 업캐스팅되었다가 다운캐스팅을 통해 purr() 메소드를 호출할 수 있습니다.

final 클래스

final 클래스는 상속할 수 없는 클래스를 의미합니다. 즉, final 키워드가 붙은 클래스는 다른 클래스에서 상속받아 확장할 수 없습니다. final 클래스는 보통 상속의 필요성을 없애기 위해 사용되며, 클래스의 동작을 변경하거나 확장할 수 없게 만듭니다.

1. final 클래스의 특징

  • 상속 불가: final로 선언된 클래스는 상속할 수 없습니다. 이는 클래스가 완전한 형태로 사용되기를 원할 때 유용합니다.

  • 안정성 제공: 클래스의 동작을 더 이상 변경하지 못하게 하므로, 클래스의 안정성을 보장합니다.

  • 보안성 강화: 클래스의 중요한 동작을 자식 클래스가 재정의(오버라이딩)하거나 수정할 수 없도록 막습니다.

2. final 클래스 선언

final class FinalClass {
    public void display() {
        System.out.println("이것은 final 클래스입니다.");
    }
}

위 코드에서 FinalClass는 final로 선언되었으므로, 다른 클래스가 이 클래스를 상속할 수 없습니다.

3. final 클래스의 상속 불가

// 컴파일 에러가 발생하는 예시
class SubClass extends FinalClass {  // ⚠️ 오류: FinalClass는 상속할 수 없습니다.
}

위의 코드처럼 FinalClass를 상속하려고 하면, 컴파일 오류가 발생합니다. final 클래스는 상속이 불가능하기 때문에, 자식 클래스에서 이를 상속받으려 하면 오류가 발생합니다.

4. final 클래스의 사용 예

  • 불변 객체 생성: final 클래스는 객체의 상태나 동작을 변경할 필요가 없을 때, 즉 불변 객체(immutable object)를 만들기 위해 자주 사용됩니다. 예를 들어, String 클래스는 자바에서 대표적인 final 클래스입니다.

public final class String {
    // String 클래스는 final로 선언되어 상속할 수 없습니다.
}
  • 보안: 중요한 클래스의 동작을 자식 클래스에서 변경할 수 없도록 막기 위해 사용됩니다.

5. final 메소드와의 차이

  • final 클래스: 해당 클래스는 상속이 불가능합니다. 즉, 다른 클래스가 이 클래스를 확장하여 사용할 수 없습니다.

  • final 메소드: final 메소드가 선언된 메소드는 오버라이딩(Overriding)할 수 없습니다. 즉, 자식 클래스에서 메소드를 재정의할 수 없습니다. 그러나 클래스 자체는 상속이 가능합니다.

예시: final 메소드

fianlMethod 패키지에 작성

class Parent {
    public final void show() {
        System.out.println("이것은 final 메소드입니다.");
    }
}

class Child extends Parent {
    // public void show() {  // ⚠️ 컴파일 에러: final 메소드는 오버라이딩할 수 없습니다.
    //     System.out.println("이것은 오버라이딩 메소드입니다.");
    // }
}

위 예시에서 Parent 클래스의 show() 메소드는 final로 선언되었기 때문에 자식 클래스에서 오버라이딩할 수 없습니다.

추상 클래스(Abstract Class)

추상 클래스는 객체를 직접 생성할 수 없는 클래스로, 주로 공통된 속성이나 동작을 정의하고 이를 상속받은 자식 클래스에서 구체적인 구현을 강제하는 역할을 합니다.

  • 객체 생성 불가: 추상 클래스는 객체를 직접 생성할 수 없습니다. 이는 추상 클래스가 미완성된 클래스이기 때문입니다.

  • 공통된 속성과 메소드 정의: 추상 클래스는 공통된 필드와 메소드를 정의할 수 있습니다. 자식 클래스는 이들을 상속받아 사용할 수 있습니다.

  • 추상 메소드를 포함할 수 있음: 추상 클래스는 추상 메소드를 포함할 수 있으며, 추상 메소드는 자식 클래스에서 반드시 구현해야 합니다.

  • 일반 메소드도 포함 가능: 추상 클래스는 구현된 일반 메소드도 포함할 수 있어, 일부 동작은 부모 클래스에서 정의하고, 나머지는 자식 클래스에서 구현하게 할 수 있습니다.

추상 클래스 선언 예시

// 추상 클래스 선언
abstract class Animal {
    // 일반 필드
    String name;

    // 일반 메소드
    void sleep() {
        System.out.println(name + "가 잠을 잡니다.");
    }

    // 추상 메소드 (구현 없음)
    abstract void sound();
}

추상 메소드(Abstract Method)

추상 메소드는 구현되지 않은 메소드로, 추상 클래스에서 선언만 하고 자식 클래스에서 반드시 구현해야 합니다.

  • 구현 없음: 추상 메소드는 메소드의 선언만 있고 구현이 없습니다. 즉, 메소드의 동작을 정의하지 않고 선언만 합니다.

  • 자식 클래스에서 구현 강제: 추상 메소드는 자식 클래스에서 반드시 오버라이딩(Overriding)되어 구체적인 구현을 제공해야 합니다.

  • abstract 키워드로 선언: 추상 메소드는 abstract 키워드를 사용하여 선언되며, 메소드 본문 {}이 없습니다.

추상 메소드 선언 예시

// 추상 메소드 (구현이 없음, 선언만 존재)
abstract void sound();

인텔리제이 단축키로 추상 메소드 생성 가능

  • 메뉴 - 코드 - 생성

    • 윈도우: alt + insert

    • 맥: command + N

  • Implement Methods 선택

3. 추상 클래스와 추상 메소드의 사용 예

abstractAnimal 패키지에 작성

// 추상 클래스 Animal
abstract class Animal {
    String name;

    // 일반 메소드
    void sleep() {
        System.out.println(name + "가 잠을 잡니다.");
    }

    // 추상 메소드 (구현 없음)
    abstract void sound();
}

// 자식 클래스 Dog
class Dog extends Animal {
    // 추상 메소드를 구현 (오버라이딩)
    @Override
    void sound() {
        System.out.println("강아지가 멍멍 짖습니다.");
    }
}

// 자식 클래스 Cat
class Cat extends Animal {
    // 추상 메소드를 구현 (오버라이딩)
    @Override
    void sound() {
        System.out.println("고양이가 야옹 소리를 냅니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        // Animal myAnimal = new Animal();  // ⚠️ 오류: 추상 클래스는 인스턴스화할 수 없음

        Animal myDog = new Dog();  // 업캐스팅
        myDog.name = "바둑이";
        myDog.sound();  // 강아지가 멍멍 짖습니다.
        myDog.sleep();  // 바둑이가 잠을 잡니다.

        Animal myCat = new Cat();  // 업캐스팅
        myCat.name = "나비";
        myCat.sound();  // 고양이가 야옹 소리를 냅니다.
        myCat.sleep();  // 나비가 잠을 잡니다.
    }
}

출력 결과

강아지가 멍멍 짖습니다.
바둑이가 잠을 잡니다.
고양이가 야옹 소리를 냅니다.
나비가 잠을 잡니다.

4. 추상 클래스와 추상 메소드의 역할

  1. 상속 구조의 설계: 추상 클래스는 상속의 기본 틀을 제공하며, 상위 클래스에서 공통된 속성과 동작을 정의합니다. 하위 클래스는 구체적인 동작을 구현해야 합니다.

  2. 코드의 재사용성: 공통적인 동작은 상위 추상 클래스에 정의하고, 자식 클래스는 이를 재사용할 수 있습니다.

  3. 구현 강제: 추상 메소드를 통해 자식 클래스가 반드시 특정 메소드를 구현하도록 강제할 수 있습니다. 이를 통해 일관성 있는 구현을 보장할 수 있습니다.

인터페이스

인터페이스(Interface)는 자바에서 추상적인 메소드의 집합을 정의하는 일종의 설계도입니다. 추상 클래스와 비슷한 개념이지만, 더 추상적이고 유연한 형태를 제공합니다. 인터페이스는 구체적인 구현을 포함하지 않으며, 오직 메소드의 시그니처(선언부)만 정의하고, 해당 인터페이스를 구현한 클래스가 구체적인 구현을 제공해야 합니다.

1. 인터페이스의 주요 특징

  1. 메소드 선언만 존재: 인터페이스에는 메소드의 선언만 있고, 구현부가 없습니다. 구현은 해당 인터페이스를 구현한 클래스에서 제공해야 합니다.(Java 8부터는 디폴트 메소드(default method)와 정적 메소드(static method)의 구현이 가능해졌습니다.)

  2. 다중 구현 가능: 자바에서는 하나의 클래스가 여러 인터페이스를 구현할 수 있습니다. 이는 자바에서 다중 상속이 불가능한 제약을 보완하는 역할을 합니다.

  3. 상수만 포함 가능: 인터페이스에 선언된 변수는 자동으로 public static final로 선언됩니다. 즉, 인터페이스의 변수는 상수입니다.

  4. 접근 제어자: 인터페이스의 메소드와 상수는 자동으로 public으로 선언됩니다. 메소드에 별도로 public을 명시하지 않아도 public abstract로 간주됩니다.

2. 인터페이스 선언

interface Animal {
    // 메소드 선언 (구현부 없음)
    void sound();

    // Java 8 이후: 디폴트 메소드 (구현 가능)
    default void sleep() {
        System.out.println("동물이 잠을 잡니다.");
    }

    // 상수 선언
    int MAX_AGE = 100;  // 자동으로 public static final로 선언됨
}

위의 Animal 인터페이스는 sound()라는 메소드를 정의하고 있으며, 구현부가 없습니다. 인터페이스를 구현하는 클래스에서 반드시 이 메소드를 구현해야 합니다. 또한 디폴트 메소드인 sleep()은 기본 동작을 제공할 수 있습니다.

3. 인터페이스 구현

인터페이스는 implements 키워드를 사용하여 클래스에서 구현할 수 있습니다. 인터페이스를 구현하는 클래스는 해당 인터페이스의 모든 메소드를 구현해야 합니다.

인터페이스 구현 예시

interfaceExample 패키지에 작성

// 인터페이스 정의
interface Animal {
    void sound();
    default void sleep() {
        System.out.println("동물이 잠을 잡니다.");
    }
}

// 인터페이스 구현 (Dog 클래스)
class Dog implements Animal {
    @Override
    public void sound() {
        System.out.println("강아지가 멍멍 짖습니다.");
    }
}

// 인터페이스 구현 (Cat 클래스)
class Cat implements Animal {
    @Override
    public void sound() {
        System.out.println("고양이가 야옹 소리를 냅니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myDog = new Dog();
        myDog.sound();  // 강아지가 멍멍 짖습니다.
        myDog.sleep();  // 동물이 잠을 잡니다. (디폴트 메소드 호출)

        Animal myCat = new Cat();
        myCat.sound();  // 고양이가 야옹 소리를 냅니다.
        myCat.sleep();  // 동물이 잠을 잡니다. (디폴트 메소드 호출)
    }
}

출력 결과

강아지가 멍멍 짖습니다.
동물이 잠을 잡니다.
고양이가 야옹 소리를 냅니다.
동물이 잠을 잡니다.

4. 인터페이스의 장점

  1. 다중 구현: 자바에서는 클래스는 하나만 상속받을 수 있지만, 여러 인터페이스를 동시에 구현할 수 있습니다. 이를 통해 자바의 다중 상속 제약을 해결할 수 있습니다.

  2. 다형성(Polymorphism): 인터페이스를 사용하면 다형성을 쉽게 구현할 수 있습니다. 여러 클래스가 같은 인터페이스를 구현하면, 인터페이스 타입으로 각기 다른 클래스의 객체를 참조할 수 있으며, 해당 인터페이스의 메소드를 호출할 수 있습니다.

  3. 유연성: 인터페이스는 객체들 간의 느슨한 결합(Loose Coupling)을 가능하게 하여, 유연한 코드 구조를 만들 수 있습니다. 이를 통해 코드의 재사용성과 유지보수성을 높일 수 있습니다.

5. 인터페이스와 추상 클래스의 차이점

항목

인터페이스(Interface)

추상 클래스(Abstract Class)

객체 생성 여부

객체 생성 불가

객체 생성 불가

메소드

추상 메소드와 디폴트 메소드 가능 (Java 8 이후)

추상 메소드와 일반 메소드 모두 가질 수 있음

변수

상수만 가질 수 있음 (public static final)

필드를 가질 수 있음

다중 상속

다중 구현 가능

다중 상속 불가

상속 키워드

implements

extends

추상 클래스와 인터페이스 비교 예시

compareAbstractWithInterface 패키지에 각각 작성

// 추상 클래스
abstract class Animal {
    abstract void sound();
    void sleep() {
        System.out.println("동물이 잠을 잡니다.");
    }
}

// 인터페이스
interface Movable {
    void move();
}

// 다중 구현
class Dog extends Animal implements Movable {
    @Override
    void sound() {
        System.out.println("강아지가 멍멍 짖습니다.");
    }

    @Override
    public void move() {
        System.out.println("강아지가 뛰어다닙니다.");
    }
}
  • 추상 클래스는 하나만 상속할 수 있지만, 인터페이스는 여러 개를 다중 구현할 수 있습니다.

6. 인터페이스와 다형성

인터페이스를 사용하면, 부모 인터페이스 타입의 참조 변수를 통해 여러 자식 클래스의 객체를 참조할 수 있습니다. 이를 통해 다형성(Polymorphism)을 구현할 수 있습니다.

다형성 예시

useInterfaceForPolymorphism 패키지에 각각 작성

interface Animal {
    void sound();
}

class Dog implements Animal {
    @Override
    public void sound() {
        System.out.println("강아지가 멍멍 짖습니다.");
    }
}

class Cat implements Animal {
    @Override
    public void sound() {
        System.out.println("고양이가 야옹 소리를 냅니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myAnimal = new Dog();
        myAnimal.sound();  // 출력: 강아지가 멍멍 짖습니다.

        myAnimal = new Cat();
        myAnimal.sound();  // 출력: 고양이가 야옹 소리를 냅니다.
    }
}
  • Animal 인터페이스 타입의 참조 변수 myAnimal은 Dog와 Cat 객체를 모두 참조할 수 있습니다.

  • 이는 다형성을 통해 동일한 메소드 호출이지만 다른 동작을 수행하게 만듭니다.

7. 디폴트 메소드와 정적 메소드

  • 디폴트 메소드: Java 8 이후, 인터페이스에서 메소드의 기본 구현을 제공할 수 있는 디폴트 메소드를 사용할 수 있습니다. 이를 통해 기존 인터페이스를 수정하지 않고도 새로운 메소드를 추가할 수 있습니다.

  • 정적 메소드: 인터페이스에서도 정적 메소드를 정의할 수 있으며, 이는 클래스처럼 인터페이스 이름을 통해 호출할 수 있습니다.

디폴트 메소드와 정적 메소드 예시

defaultMethodAndStaticMethod 패키지에 작성

interface Animal {
    // 추상 메소드
    void sound();

    // 디폴트 메소드
    default void sleep() {
        System.out.println("동물이 잠을 잡니다.");
    }

    // 정적 메소드
    static void info() {
        System.out.println("이것은 Animal 인터페이스입니다.");
    }
}

class Dog implements Animal {
    @Override
    public void sound() {
        System.out.println("강아지가 멍멍 짖습니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myDog = new Dog();
        myDog.sound();  // 출력: 강아지가 멍멍 짖습니다.
        myDog.sleep();  // 출력: 동물이 잠을 잡니다.

        // 정적 메소드는 인터페이스 이름을 통해 호출
        Animal.info();   // 출력: 이것은 Animal 인터페이스입니다.
    }
}

싱글턴 패턴(Singleton Pattern)

싱글턴 패턴(Singleton Pattern)은 클래스의 인스턴스를 오직 하나만 생성하도록 보장하는 디자인 패턴입니다. 즉, 하나의 애플리케이션 내에서 특정 클래스에 대한 단일 객체만 존재하도록 하고, 해당 객체에 대한 글로벌 접근을 제공하는 방식입니다.

1. 싱글턴 패턴의 주요 특징

  1. 인스턴스가 하나만 존재: 클래스의 인스턴스가 오직 하나만 생성되도록 제한합니다.

  2. 글로벌 접근점 제공: 애플리케이션 어디서든 이 단일 인스턴스에 접근할 수 있습니다.

  3. 클래스 자체가 인스턴스 생성을 관리: 클래스 내부에서 객체를 생성하고, 외부에서 객체를 새로 생성하지 못하게 합니다.

2. 싱글턴 패턴 구현 방법

싱글턴 패턴을 구현하려면 생성자를 private으로 선언하여 외부에서 객체를 생성하지 못하게 하고, 클래스 내부에서 정적 메소드를 사용하여 단일 인스턴스를 반환하도록 만듭니다.

싱글턴 패턴 구현 예시

singletonPatternExample 패키지에 각각 작성

public class Singleton {
    // 1. 클래스 내부에 유일한 인스턴스 생성 (static)
    private static Singleton instance = new Singleton();

    // 2. 생성자를 private으로 선언하여 외부에서 인스턴스 생성을 금지
    private Singleton() {
        // 생성자
    }

    // 3. 유일한 인스턴스를 반환하는 public 메소드 제공 (static)
    public static Singleton getInstance() {
        return instance;
    }

    // 싱글턴 클래스에서 사용할 메소드 예시
    public void showMessage() {
        System.out.println("싱글턴 인스턴스입니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        // Singleton 객체를 얻기 위한 호출 (항상 동일한 인스턴스 반환)
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();

        // 같은 인스턴스인지 확인
        System.out.println(singleton1 == singleton2);  // 출력: true

        // 메소드 호출
        singleton1.showMessage();  // 출력: 싱글턴 인스턴스입니다.
    }
}

설명

  • instance 변수: private static으로 선언하여 클래스 로딩 시 단 한 번만 객체를 생성합니다.

  • getInstance() 메소드: 외부에서 싱글턴 객체에 접근할 수 있도록 공용 정적 메소드로 제공됩니다. 이 메소드는 항상 유일한 인스턴스를 반환합니다.

  • private 생성자: 외부에서 생성자를 호출하지 못하도록 하여, 외부에서 객체를 생성할 수 없습니다.

정리

  • 싱글턴 패턴(Singleton Pattern)은 하나의 인스턴스만 생성하여 이를 전역적으로 접근할 수 있도록 하는 디자인 패턴입니다.

  • 객체의 생성 비용을 줄이고, 공유 자원을 관리할 때 유용합니다.

Previous패키지 보충자료Next08 클래스 더 알아보기

Last updated 5 months ago