[Java] 객체지향설계

허경두·2025년 4월 2일

Java

목록 보기
6/12

객체지향설계란?

객체(Objcet)들이 모여서 상호 협력하면서 데이터를 처리하는 방식의 프로그래밍 설계 방법을 일컫는다

키워드 : 재사용성 유지보수성 중복 코드 제거

  • 객체지향언어의 특징
    1. 코드의 재사용성이 높다
    2. 코드의 관리가 용이하다
    3. 신뢰성이 높은 프로그래밍을 가능하게 한다
      제어자와 메서드를 이용해서 데이터를 보호하고 올바른 값을 유지하며 코드의 중복을 제거하여 코드의 불일치로 인한 오동작을 방지할 수 있다
  • 핵심 원칙
    • 캡슐화 : 객체의 외부와 내부를 구분 (클래스로 데이터와 함수를 묶는 것)
    • 상속
    • 추상화
    • 다형성

클래스와 객체

클래스(class) : 객체를 정의해 놓은 것 (설계도)

  • 프로그래밍적 관점의 클래스
    1. 데이터와 함수의 결합이다
    2. 사용자 정의 타입이다

객체(object) : 실제로 존재하는 것 (설계도로 만든 사물)

  • 객체는 속성과 기능으로 이루어져 있다

    • 속성(property) : 멤버변수, 특성, 필드, 상태
    • 기능(function) : 메서드, 함수, 행위
  • 인스턴스
    설계도를 바탕으로 소프트웨어 세계에 구현된 구체적인 실체
    즉, 객체를 소프트웨어에 실체화 하면 그것을 ‘인스턴스’라고 부른다
    실체화된 인스턴스는 메모리에 할당된다

    • 선언 : 클래스명 변수명;
    • 생성 : new 클래스명();
      → 엄밀히 따지면 new 연산자가 인스턴스를 생성하고 생성자인 클래스명()은 변수를 초기화한다
    • 인스턴스는 참조변수를 통해서만 다룰 수 있으며 참조변수의 타입은 인스턴스의 타입과 일치해야 한다
      Fruit f = new Fruit(); // 참조변수 f의 타입은 Fruit, new 키워드로 생성된 인스턴스의 타입도 Fruit

메서드

메서드(method) : 특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것
클래스 내에 선언된 함수

  • 메서드의 장점

    1. 높은 재사용성
    2. 중복된 코드 제거
    3. 프로그램의 구조화
      부분 작업 단위로 나누어 메서드를 작성하면 전체 작업을 구조화할 수 있다
  • 메서드의 구조

    int add(int x, int y) { // 선언부. 순서대로 반환타입, 메서드 이름, 매개변수
    	int result = x + y; // 구현부
        return result;
     }
  • 매개변수

    매개변수(parameter) : 함수에 전달되는 변수

    구분기본형 매개변수 (Primitive Type)참조형 매개변수 (Reference Type)
    데이터 타입int, double, char, boolean배열, String, 객체(Class) 등
    전달 방식값(Call by Value)만 전달참조(Call by Reference) 전달
    원본 데이터 영향없음 (복사본만 변경됨) → READ ONLY있음 (같은 객체를 참조) → READ & WRITE
    메모리 저장 위치스택(Stack) 메모리힙(Heap) 메모리
    메모리 할당 방식값 자체가 저장됨객체의 주소(참조값)가 저장됨
    예제 코드void modify(int x) { x = 10; }void modify(int[] arr) { arr[0] = 10; }
  • 클래스 메서드와 인스턴스 메서드

    구분클래스(static) 메서드인스턴스 메서드
    선언 방식static 키워드 사용static 키워드 없음
    소속클래스 자체에 소속객체(인스턴스)에 소속
    호출 방법클래스명.메서드명()객체명.메서드명()
    인스턴스 필요 여부필요 없음 (객체 생성 없이 호출 가능)필요함 (객체 생성 후 호출 가능)
    멤버 변수 접근클래스 변수(static 변수)만 접근 가능모든 인스턴스 변수와 메서드에 접근 가능
    사용 예시Math.pow(2,3);String str = "hello"; str.length();

    인스턴스 메서드에서는 클래스 메서드나 변수를 사용할 수 있지만,
    클래스 메서드에서는 인스턴스 메서드나 변수를 사용할 수 없다

    → 인스턴스가 존재하지 않을 수도 있기 때문이다

    인스턴스 멤버 간의 호출은 문제 없다
    → 하나의 인스턴스 멤버가 존재한다는 것이 인스턴스가 이미 존재한다는 것을 의미하기 때문이다

오버로딩

오버로딩(overloading) : 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것

  • 조건
    1. 메서드 이름이 같아야 한다
    2. 매개변수의 개수 또는 타입이 달라야 한다
  • 예시
    void println()
    void println(boolean x)
    void println(char x)
    void println(int x)
  • 장점
    1. 기억하기 쉽다
    2. 오류의 가능성을 줄일 수 있다 (코딩 실수)
    3. 이름만 보고 기능을 추측할 수 있다
    4. 이름을 절약할 수 있다

가변인자

가변인자(variable arguments) : 매개변수의 수를 가변적으로 조절할 수 있게 해주는 매개변수

선언 : 타입... 변수명

가변 인자는 매개변수 중 가장 마지막에 선언해야 한다 → 가변인자인지 아닌지를 구별할 방법이 없기 때문이다

내부적으로 배열을 사용하기 때문에 메서드를 호출할 때마다 배열이 생성된다 → 비효율적이므로 꼭 필요한 경우에만 사용해야 한다

  • 예시
    public PrintStream printf(String format, Object... args) {...}
  • 배열 타입의 매개변수와의 차이점
    배열 타입의 경우 반드시 매개변수를 지정해 줘야한다 (인자가 없을 경우, 길이가 0인 배열이나 null)
    가변인자는 내부에서 길이가 0인 배열이 생성된다
  • 가변인자 오버로딩 시 주의사항
    String test(String s, String... args) {...}
    String test(String... args) {...}
    test("1", "2", "3"); // reference to test is ambiguous 에러 발생
    타입이 같아 첫 번째 인자와 가변인자가 구분되지 않으므로 어떤 메서드를 사용할 지도 모호한 상태가 된다
    → 가능하면 가변인자를 포함한 메서드는 오버로딩하지 않는 것이 좋다

생성자

생성자(constructor) : 인스턴스 초기화 메서드 (인스턴스 변수들을 초기화하는 것)

  • 특징
    1. 생성자의 이름은 클래스의 이름과 같다
    2. 리턴 값이 없다
    3. 클래스의 생성자가 하나도 없을 때 기본 생성자가 자동으로 생성된다 (기본 생성자 : 매개변수가 없는 생성자)
    4. 한 생성자에서 다른 생성자를 호출할 때 반드시 첫 줄에서 호출해야 한다

this : 객체 자기 자신을 의미하는 참조 변수
→ 인스턴스 변수와 지역 변수를 구분할 수 있다

  • 예시

    class Test {
    	String iv1;
        int iv2;
    
    	Test() {...}
        Test(String iv1, int iv2) {
        	this(); // 자신의 다른 생성자 호출
        	this.iv1 = iv1; // this.iv1은 자신의 인스턴스 변수, iv1은 입력받은 매개변수(지역 변수)
            this.iv2 = iv2;
        }
        
        /** 원래 생성자 → 생략하면 변경에 유리하다
        Test(생략) Test(Test this(생략), String iv1, int iv2) {
        	this.iv1 = iv1;
            this.iv2 = iv2;
            return this;(생략)
        }
        **/
     }

생성자를 사용해서 인스턴스를 복사할 수 있다

  • 예시
    Test t = new Test();
    Test t2 = new Test(t);

멤버 변수의 초기화

  1. 명시적 초기화 : int a = 10;

  2. 생성자 : 생성자 내에서 초기화

  3. 초기화 블럭

    int a, b;
    static int c, d;
    
    //인스턴스 초기화 블럭
    {
    	a++;
        b = 20;
     }
     // 클래스 초기화 블럭
     static {
     	c++;
        d = 40;
     }
  • 초기화 시점
    • 인스턴스 변수 : 인스턴스가 생성될 때마다
    • 클래스 변수 : 클래스가 로딩될 때 단 한 번
  • 초기화 순서
    • 인스턴스 변수 : 기본값 → 명시적초기화 → 인스턴스 초기화 블럭 → 생성자
    • 클래스 변수 : 기본값 → 명시적초기화 → 클래스 초기화 블럭

상속

상속(inheritance) : 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것

extends 키워드를 사용하여 구현할 수 있다

class Child extends Parent {...}

출처 : https://www.tcpschool.com/java/java_inheritance_concept#google_vignette

  • 특징

    1. 생성자와 초기화 블럭은 상속되지 않는다 (멤버만 상속)
    2. 자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다
    3. 자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 하나의 인스턴스로 생성된다
    4. Java에서는 단일 상속만을 허용한다 → 두 조상에 선언부가 같은 메서드가 존재할 경우 어떤 것을 상속받을 지 구별할 수 없기 때문이다
      → 추가적으로 재사용이 필요한 경우에는 포함관계를 이용한다
  • super : 상속한 조상 클래스를 의미하는 키워드

  • Object 클래스 : 모든 클래스의 조상

  • Object 클래스를 제외한 모든 클래스의 생성자 첫 줄에 생성자.this() 또는 super()를 호출해야 한다 (안했을 경우, 컴파일러가 자동으로 삽입한다)

  • 조상 클래스의 멤버변수는 조상의 생성자에 의해 초기화되도록 해야한다
    왜? 자손 클래스에서 조상 클래스의 멤버 변수를 사용하기 위해서
    Q1. super.XXX 의 형태로 직접 초기화 하면 안되나?

    • super.x = 10 의 형태로 자손 클래스에서 초기화하면 캡슐화의 원칙에 위배 된다 → 유지보수에 불리하다
    • 조상 클래스의 멤버변수에 접근 제한자가 있을 경우 자손 클래스에서 초기화할 수 없다

    Q2. setter를 사용해서 초기화 하면 안되나?

    • 생성자가 객체의 초기 상태를 보장한다
    • 불완전한 상태로 객체가 생성될 위험이 있다
    • 조상 클래스만 따로 사용할 때도 변수가 안전하게 준비된다
    • setter를 사용하면 조상 클래스의 변수 초기화 책임이 자손 클래스에게 간다 (객체지향설계의 단일책임원칙 위배)
    • 생성자는 초기화 후 변수의 상태 변경이 제한되지만, setter는 외부에서도 상태 변경이 가능하다

    위 두 질문 자체가 모순된다

    • 생성자가 객체를 초기화하기 위한 특수한 메서드인데, 이 메서드 대신 setter 등의 사용자 정의 메서드를 사용하겠다는 것은 일부러 특수 목적을 위해 만들어 놨는데 굳이 원시적인 방법으로 돌아가겠다는 것이다
    • 위 질문들을 하게 된 계기와 이후 스토리가 이곳에 자세히 적혀있다...

포함 관계

한 클래스의 멤버변수로 다른 클래스 타입의 참조변수를 선언하는 것

기존의 클래스를 재사용하는 또 다른 방법

  • 예시
    class Circle {
    	Point p = new Point();
        int r;
     }
  • 상속 vs. 포함관계
    • 상속 : 원(Circle)은 점(Point)이다 (is-a)
    • 포함관계 : 원(Circle)은 점(Point)을 가지고 있다 (has-a)

오버라이딩

오버라이딩(overriding) : 조상 클래스로부터 상속받은 메서드의 내용(구현부)을 변경하는 것

  • 필요성

    1. 같은 타입인데 동작은 다르게 하기 위해서 (다형성)
    2. 상속받은 기능(메서드)이 해당 클래스의 상황에 맞지 않을 때
  • 제한 사항

    1. 접근 제어자는 조상 클래스 보다 좁은 범위로 변경할 수 없다
      e.g. 조상이 public 일 경우, 자손도 public 으로 해야한다
      조상이 private일 경우, 자손은 private ~ public 모두 가능하다
    2. 조상 클래스보다 많은 수의 예외를 선언할 수 없다
      class Parent {
      	void parentMethod() throws IOException, SQLException {...}
       }
       
       class Child extends Parent {
       	void parentMethod() throws Exception {...}
       }
      → Exception은 모든 예외의 조상이므로 자손이 조상 클래스보다 더 많은 예외를 선언한 것이므로 잘못되었다
    3. 인스턴스 메서드를 static으로, static을 인스턴스 메서드로 변경할 수 없다
      * 조상 클래스의 static 메서드를 자손 클래스에서 똑같이 정의하는 것은 오버라이딩이 아니라 별개의 static 메서드이다 → static 멤버는 해당 클래스에 종속적이다

package와 import

package : 클래스의 묶음
import : 클래스의 패키지를 미리 명시하는 키워드

import 패키지명.클래스명;
import static 패키지명.클래스명;
  • static import : static 멤버(변수, 메서드)를 호출할 때 클래스 이름 생략 가능

제어자

클래스, 변수, 메서드에 부가적인 의미를 부여한다

  • 접근 제어자

    접근 제어자적용 대상같은 클래스같은 패키지자식 클래스전체 접근
    public클래스, 필드, 메서드✅ 가능✅ 가능✅ 가능✅ 가능
    protected필드, 메서드✅ 가능✅ 가능✅ 가능❌ 불가능
    default (명시 안함)클래스, 필드, 메서드✅ 가능✅ 가능❌ 불가능❌ 불가능
    private필드, 메서드✅ 가능❌ 불가능❌ 불가능❌ 불가능

    * 생성자에 private을 사용할 수 있다
    → 클래스 내부에서 인스턴스를 생성하여 외부로 반환할 수 있다

    class Singleton {
    	private static Singleton s = new Singleton();
       
        private Singleton() {...}
        
        public static Singleton getInstance() { //static을 사용해서 인스턴스 없이 호출할 수 있게 해준다
       		return s;
        }
     }

    → 사용할 수 있는 인스턴스의 개수 제한
    → 다른 클래스에서 이 클래스의 생성자를 사용하지 못하므로 다른 클래스의 조상이 될 수 없다

  • 그 외

    제어자적용 대상설명
    static필드, 메서드, 클래스(내부 클래스)클래스 변수 및 메서드를 정의하며, 인스턴스 없이 사용 가능
    final클래스, 메서드, 필드, 변수클래스 상속 금지, 메서드 오버라이딩 금지, 변수 값 변경 불가
    abstract클래스, 메서드추상 클래스 및 추상 메서드 정의 (구현 강제)
    synchronized메서드, 블록멀티스레드 환경에서 동기화 수행
    volatile필드변수 값이 항상 메인 메모리에서 최신 상태 유지 (멀티스레드 환경)
    transient필드직렬화(Serialization)에서 제외되는 변수 지정
    strictfp클래스, 메서드부동소수점 연산을 플랫폼에 관계없이 동일하게 수행
    native메서드네이티브 코드(C/C++ 등)에서 구현된 메서드 호출
  • 제어자의 조합

    1. 메서드에 static과 abstract 동시 사용 불가
      → static은 구현부가 있음
    2. 클래스에 abstract와 final 동시 사용 불가
      → 확장할 수 없는 클래스를 상속을 통해서 완성하지 못함
    3. abstract 메서드는 private일 수 없음
      → 자손 클래스에서 접근 못함
    4. 메서드에 private과 final을 같이 사용할 필요 없음
      → private 이면 어차피 오버라이딩 못함

제어자를 사용해서 변수를 캡슐화 하는 이유

  1. 외부로부터의 직접 접근을 차단하여 객체의 무결성을 유지할 수 있다
  2. 데이터 변경을 통제된 방식으로 처리할 수 있다
    제어자를 써도 setter로 변경할 수 있지 않냐? 에 대한 답
    비유하자면 setter는 통로인데 setter 내부의 구현으로 가드를 세울 수 있다
  3. 객체의 내부 구현을 숨겨서 유연성을 확보할 수 있다 → 외부에서는 public 메서드에만 접근 가능하므로 public 메서드의 선언부를 수정하지 않는 이상 외부는 내부의 변화에 영향을 받지 않는다

다형성

다형성(polymorphism) : 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하는 것

Parent c = new Child();
Child p = (Child) new Parent(); // 컴파일은 되지만, 실행 시 에러

* 조상 클래스 타입이면 조상 클래스의 멤버만 사용 가능
* 조상 타입을 자손 타입으로 변경(Down-casting) 시 명시적으로 형변환해야 한다 → 실행 시 에러가 발생하기 때문에 허용되지 않는다

형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다.
단지 참조변수의 형변환을 통해서 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절하는 것뿐이다.
출처 : Java의 정석 3판 (저자 : 남궁성)

  • 다형성에서 멤버의 사용

    • 메서드 : 실제 인스턴스의 메서드(오버라이딩된 메서드)가 호출됨
    • 변수 : 참조변수의 타입에 해당하는 변수를 사용
    Parent p = new Child();
    Child c = new Child();
    
    p.x; // Parent 타입이므로 Parent의 x인 50
    p.method(); // 인스턴스가 Child 이므로 Child의 메서드 사용
    c.x; // Child 타입이므로 Child의 x인 10
    c.method(); // 인스턴스가 Child 이므로 Child의 메서드 사용
    
    class Parent {
    	int x = 50;
        void method() {
        	System.out.println("Parent");
        }
     }
     
     class Child extends Parent {
    	int x = 10;
        void method() {
        	System.out.println("Child");
        }
     }
  • 매개변수에 다형성을 적용시킴으로써 중복되는 코드를 제거할 수 있다

    void add(Parent p) {
    	p.printSomthing();
    	return p.x + p.y;
    } // Parent를 상속받는 클래스가 여러 개일 경우, add 메서드를 각 클래스별로 작성하지 않아도 된다
  • 배열에서도 여러 자손 클래스를 사용할 수 있다

    Parent[] pArr = new Parent[10]; // Parent를 상속받는 클래스들을 pArr의 요소로 사용할 수 있다

instanceof 연산자

참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 사용

참조변수 instanceof 타입(클래스명)

참조변수의 타입이 우측 타입과 일치하거나 우측 타입을 상속 받는 클래스(우측 타입의 자손 클래스)이면 true, 아니면 false를 반환한다

  • 예시
    Child casting(Parent p) {
    	if (p instanceof Child) { // p는 Child의 자손이 아니므로 false 반환
       	Child c = (Child)p;
           return c;
       }
    }

바인딩

정적 바인딩(Static Binding) : 컴파일 타임참조 변수의 타입으로 어떤 메서드를 호출할 지 결정하는 것
동적 바인딩(Dynamic Binding) : 런타임실제 객체의 타입으로 어떤 메서드를 호출할 지 결정하는 것

구분정적 바인딩 (Static Binding)동적 바인딩 (Dynamic Binding)
변수✅ 항상 정적 바인딩❌ 해당 없음
인스턴스 메서드❌ 오버라이딩된 경우 제외✅ 오버라이딩된 경우 적용
static 메서드✅ 정적 바인딩❌ 동적 바인딩 불가
final/private 메서드✅ 오버라이딩 불가 → 정적 바인딩❌ 동적 바인딩 불가
오버로딩 메서드✅ 컴파일 시점에 결정❌ 해당 없음

쉐도잉 vs. 오버라이딩

쉐도잉(Shadowing) : 자식 클래스에서 부모 클래스와 같은 이름의 변수를 선언해서 부모 클래스의 변수가 가려지는 현상

항목쉐도잉 (Shadowing)오버라이딩 (Overriding)
대상변수메서드
동작 방식부모 클래스의 변수와 같은 이름으로 자식 클래스가 다시 정의부모 클래스의 메서드를 자식 클래스가 재정의
바인딩 방식✅ 정적 바인딩 (컴파일 시 타입 기준)✅ 동적 바인딩 (실행 시 객체 타입 기준)
접근 기준참조 변수의 타입 기준실제 객체의 클래스 기준
실행 결과 예측변수는 오버라이딩이 안되므로 항상 타입 기준으로 정해짐메서드는 오버라이딩 가능, 실행 시 객체 기준으로 호출됨
대표 예int x = 10 in Parent, int x = 20 in Childvoid speak() in Parent, overridden in Child
주의점헷갈리기 쉬움 → 변수는 오버라이딩이 아니라 숨김(hiding)메서드는 재정의되며 동작이 다형성(polymorphism)에 영향
class Parent {
    int x = 10;
    void show() { System.out.println("Parent"); }
}

class Child extends Parent {
    int x = 20;  // 🟡 쉐도잉
    
    @Override
    void show() { System.out.println("Child"); } // 🔵 오버라이딩
}

public class Main {
    public static void main(String[] args) {
        Parent p = new Child();
        System.out.println(p.x); // 🔸 10 → 변수는 정적 바인딩
        p.show();               // 🔹 "Child" → 메서드는 동적 바인딩
    }
}

추상클래스

추상화 : 클래스간의 공통점을 찾아내서 공통의 조상을 만드는 작업
추상클래스(abstract class) : 하나 이상의 추상 메서드를 포함한 클래스. 미완성 설계도
추상메서드(abstract method) : 선언부만 작성한 메서드

  • 특징
    1. 자손들마다 구현부를 각각 구현할 부분과 같은 메서드를 사용할 부분을 구분할 수 있다
    2. 자손들이 반드시 구현해야 할 메서드를 강제함으로써 일관성을 유지하고 실수를 방지할 수 있다
    3. 새로운 기능을 추가할 때 기존 코드에 영향을 최소화할 수 있다 (Loose Coupling)
      → 확장성 증가

인터페이스

인터페이스(interface) : 모든 메서드가 추상 메서드인 클래스. 기본 설계도

  • 특징

    1. 일반 메서드 또는 멤버변수를 가질 수 없다 (추상메서드와 상수만 가능)
      → 모든 멤버변수는 public static final 이며 생략할 수 있다
      → 모든 메서드는 public abstract 이며 생략할 수 있다
    2. 인터페이스는 인터페이스로부터만 상속받을 수 있다
    3. 다중 상속이 가능하다

    리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다
    출처 : Java의 정석 3판 (저자 : 남궁성)

    → 매개변수도 마찬가지로 구현한 클래스의 인스턴스를 입력해야 한다

  • 장점

    1. 개발시간을 단축시킬 수 있다 (분업)
      인터페이스의 메서드를 호출하는 사람은 구현부만 알면 됨
      다른 한 사람은 인터페이스 구현체 작성
    2. 표준화가 가능하다
    3. 상속관계도 아니고 같은 조상도 아닌 클래스들에게 같은 인터페이스를 구현함으로써 관계를 맺어줄 수 있다
    4. 독립적인 프로그래밍이 가능하다
      1. 메서드 선언과 구현의 분리
      2. 클래스 간의 관계를 인터페이스로 간접적인 관계로 만들어서, 각 클래스에 대한 변경이 서로 영향을 미치지 않게 할 수 있다
        출처 : https://blog.naver.com/29java/70189390355
  • 디폴트 메서드
    인터페이스에 새로운 메서드를 추가하는 것은 구현한 모든 클래스들이 구현을 해야하므로 디폴트 메서드로 기본적인 구현을 제공한다

    • 디폴트 메서드의 충돌
      1. 여러 인터페이스 간 충돌
        → 구현하는 클래스에서 디폴트 메서드 오버라이딩
      2. 조상 클래스와 인터페이스 간 충돌
        → 조상 클래스의 메서드가 상속되고 디폴트 메서드는 무시됨
  • static 메서드 : 인터페이스의 원칙에는 위배되지만 에러가 발생하지는 않는다

다중상속의 장점과 단점

장점

  • 코드의 중복을 줄여 재사용성 증가
  • 여러 기능을 조합하여 사용 가능 (유연성 증가)
  • 객체가 여러 개념을 동시에 포함할 수 있음

단점

  • 상속받는 메서드가 충돌할 수 있음
  • 코드의 복잡성이 증가

내부 클래스

내부 클래스(inner class) : 클래스 내에 선언된 클래스. 두 클래스가 밀접한 관계에 있을 경우 사용
e.g. 외부 클래스의 데이터를 처리하거나 특정 연산을 수행하는 helper 클래스를 구현할 때

  • 장점

    1. 내부 클래스에서 외부의 멤버들에 쉽게 접근할 수 있다
    2. 코드의 복잡성을 줄일 수 있다 (캡슐화)
  • 종류와 특징

    내부 클래스 종류선언 위치접근 가능 요소주요 특징
    인스턴스 클래스클래스 내부 (static 아님)외부 클래스의 모든 멤버(필드, 메서드)- 외부 클래스의 인스턴스를 생성해야 사용 가능
    - 외부 클래스의 private 멤버에도 접근 가능
    정적(static) 클래스클래스 내부 (static 선언)외부 클래스의 static 멤버만- 외부 클래스의 인스턴스 없이 사용 가능
    - static 멤버만 접근 가능
    - 일반적인 클래스처럼 사용 가능
    지역 클래스메서드 내부외부 클래스의 멤버 + 메서드의 final 지역 변수- 특정 메서드 내에서만 사용 가능
    - 컴파일러가 자동으로 final 처리 (Java 8 이후)
    익명 클래스메서드 내부 (new 연산자로 즉시 생성)외부 클래스의 멤버 + 메서드의 final 지역 변수- 이름이 없는 일회성 클래스
    - 생성과 동시에 인스턴스 생성
    - 주로 인터페이스나 추상 클래스 구현에 사용

지역 클래스에서는 왜 final인 지역 변수만 접근이 가능한가?

메서드와 지역 클래스 인스턴스의 생명주기가 다르기 때문이다

  • 지역 변수는 스택, 지역 클래스의 인스턴스는 에 저장된다
    → 지역 클래스에서 지역 변수를 사용할 경우 값을 복사하여 힙에 저장한다 (참조 변수의 경우 주소값이 복사되어 저장된다)
  • 메서드가 종료되어 스택에서 사라져도 지역 클래스의 인스턴스는 다른 메서드의 매개변수로 전달되어 힙에 유지될 수 있다

Q1. 변경이 되든 안되든 그냥 사용하면 안되나?
서로 다른 문맥에서 사용되기 때문에 어느 한 쪽에서 변경하면 오류나 혼란(예기치 못한 동작)이 가능성이 있다
Q2. List 같은 객체는 내부 요소 변경이 가능한데 위와 같은 혼란이 생길 수 있지 않은가?
생길 수도 있다
→ 불변 컬렉션을 사용하거나 책임 분리를 명확하게 하여 혼란을 방지해야 한다
책임 분리 예시 : add 메서드는 add만. add 메서드에서 다른 로직을 수행하거나 다른 메서드에서도 add를 수행하지 않도록 한다

0개의 댓글