[JAVA] 정적 중첩 클래스를 활용하여 계층간 독립적인 Validation을 적용해보자

2025. 10. 16. 22:12·Java & Kotlin

들어가며

우아한테크코스 프리코스 1주차 문제를 풀며 객체지향적인 코드에 도달하기 위해 수많은 검색과 코드를 보고있다. 내가 최근 느낀점은 "책임과 역할을 상황에 맞게 잘 나누는 것" 이 가장 중요해 보인다. 그 수단으로는 코드 아키텍처, 정적 팩토리 메서드, 상수화 등등 여러가지가 있겠지만 코드가 심화되면 심화될수록 검증에 관한 코드도 중요해진다. 그리고 나는 검증도 역할과 책임을 나누기 위해 계층별로 검증을 따로 하는게 맞다고 판단을 하였다. 그래서 그 방법으로 선택한 정적 중첩 클래스를 활용한 Validation에 대해서 적어볼까 한다!

 

 

본 글에서 ‘정적 중첩 클래스(static nested class)’를 다룹니다.
흔히 ‘정적 내부 클래스’라고도 부르지만, 자바 명세에서 내부 클래스(inner class) 는 비정적 멤버/지역/익명 클래스를 뜻합니다.

본론으로

그전에 정적이란 무엇일까? 에 대해서 잠깐 알고 가고자 한다. 다들 static 키워드는 알 것이다.

Java에서 static 키워드는 클래스 로딩 시점에 단 한 번 메모리에 올라가며,
프로그램이 종료될 때까지 유지된다는 뜻이다. 즉, static으로 선언된 멤버(필드, 메서드)는 JVM의 메소드 영역(static 영역) 에 저장되고, new로 생성한 인스턴스처럼 Heap 영역에 여러 개 만들어지지 않는다. 이 때문에 static 멤버는 인스턴스와 무관하게 공유되며 자주 사용하면 프로그램 전체 메모리 점유율에 악영향을 줄 수 있다.
그래서 static을 남용하지 말라는 이유가 여기 있다.

 

이정도는 자바좀 써본 사람이면 많이 들어봤을 것이다!!!


그렇다면 static class는.....!??!?! static으로 선언된 클래스(즉, 정적 중첩 클래스)도 메모리에 한 번만 올라간다는 점에서는 동일하지만, 다른 static 멤버와는 동작 방식이 약간 다르다.

public class StaticTest2 {
    static class InnerStatic {}

    public static void main(String[] args) {
        InnerStatic a = new StaticTest2.InnerStatic();
        InnerStatic b = new StaticTest2.InnerStatic();
        System.out.println(a == b); // false
    }
}

 

a==b의 출력결과는 false가 나온다... 띠용??? 메모리에 한 번만 올라간다며!!! 그렇다. 좀 다르다.

클래스 정의는 한 번만 로드되지만, 인스턴스는 매번 새로 생성된다. 정적 중첩 클래스는 외부 클래스의 인스턴스와 완전히 분리되어 있으므로, 외부 객체를 생성하지 않아도 독립적으로 생성이 가능하다.

 




그렇다면 여기서 내부 클래스를 활용할 때 주의해야 할 점이 나온다.
 

static 키워드가 붙지 않은 내부 클래스(= 인스턴스 내부 클래스)는 외부 클래스 인스턴스를 암묵적으로 참조한다.

public class Bag {
    class Pouch {}
}

Bag b = new Bag();
Bag.Pouch p = b.new Pouch(); // 반드시 외부 인스턴스가 필요

 

이 경우, Pouch 객체는 자신을 만든 Bag 객체에 대한 참조를 가진다. 이 참조는 GC(가비지 컬렉션) 의 해제 대상에서 제외되므로, 다음과 같은 메모리 누수를 초래할 수 있다.

public class Outer {
    private int[] data;
    class Inner {}

    public Outer(int size) {
        data = new int[size];
    }

    Inner getInnerObject() {
        return new Inner();
    }
}
public class Main {
    public static void main(String[] args) {
        ArrayList<Object> list = new ArrayList<>();

        for (int i = 0; i < 50; i++) {
            list.add(new Outer(100_000_000).getInnerObject());
            System.out.println(i);
        }
    }
}

 

위의 코드를 실행하면 메모리 누수로 인해 OutOfMemoryError가 발생한다. 왜 메모리 누수가 날까? 코드흐름과 함께 자세히 알아보자!

 

1. 메인 함수의 new Outer(100_000_000).getInnerObject() 한 줄로 Outer가 만들어지고, 이어서 getInnerObject() 안에서 Inner(일반 내부 클래스) 가 생성된다.

2. 만들어진 Inner(내부 클래스)는 "자신을 만든 Outer(외부 클래스)"를 자동으로 기억(암묵적 참조)한다. (list → Inner → Outer)

3. 우리가 Outer를 변수에 따로 저장하진 않았지만, list가 보관 중인 Inner가 Outer를 붙잡고 있다. → GC가 해제 불가

4. Outer 안의 int[100_000_000] 배열도 함께 유지된다. (int 4바이트 기준 약 400MB)

5. 400MB × 50 = 20GB 💥 → OutOfMemoryError

 

 

그렇다면 정적 중첩 클래스로 바꿔보겠다!! 이러면 잘 될까?

public class Outer {
    private int[] data;
    static class Inner {}

    public Outer(int size) {
        data = new int[size];
    }

    Inner getInnerObject() {
        return new Inner();
    }
}

 

이번에는 정상적으로 실행된다.(휴)
이유는 간단하다 — static class는 외부 참조를 가지지 않기 때문이다. 즉, Inner는 Outer와 완전히 독립적이므로 GC가 Outer 인스턴스를 정상적으로 해제할 수 있다.

 

내부 클래스가 외부 인스턴스를 참조할 필요가 없을 경우,
반드시 static을 붙여 정적 중첩 클래스로 선언해야 한다.

 

그래서 나는 정적 중첩 클래스를 활용해 계층간 독립적인 검증을 하기로 결정했다. 아래는 이번 우테코 프리코스 1주차 문제에서 쓰인 ConsoleReader.Validator를 정적 중첩 클래스로 만들어 빈 값을 검증하는 예시이다.

public class ConsoleReader {
    public static String readMessage() {
        return Validator.validate(Console.readLine());
    }

    private static class Validator {
        public static String validate(String message) {
            return validateBlank(message);
        }

        private static String validateBlank(String message) {
            if (message.isBlank()) {
                throw CustomException.from(ErrorMessage.BLANK_INPUT_ERROR);
            }
            return message;
        }
    }
}

 

 

이유를 멋있게 정리하자면 아래와 같다.

 

  • Validator는 ConsoleReader의 필드나 상태를 전혀 사용하지 않는다.
  • 이 Validator는 ConsoleReader 내부에서만 쓰이는 로직이다. -> 다른 계층에서는 그 클래스만의 Validator를 만들면 된다!
  • 별도의 클래스로 빼면 불필요하게 노출되므로 캡슐화가 깨진다.
  • 정적 중첩 클래스로 묶으면 “이 검증 로직은 오직 콘솔 입력용이다”라는 의도를 명확히 전달할 수 있다.
  • static으로 선언해 외부 참조를 완전히 차단했다.
  • IntelliJ와 같은 IDE에서도 “외부 클래스 멤버를 사용하지 않으므로 static class로 선언하세요”라는 경고를 띄운다.
  • 즉, 외부 멤버를 참조하지 않는 내부 클래스는 static으로 만드는 것이 표준적 권장사항이다.

아래 사진은 마침 우테코 8기 프리코스 지식 나눔 채팅방에 private static 메서드에 관한 이야기가 나왔길래, 정적 중첩 클래스도 주제와 연관있는 이야기라고 생각하여 지식을 공유하였다!! 이 글 보고 나처럼 정적 중첩 클래스 적용한 사람 있으면 너무 뿌듯할 것 같다 ㅎㅎ.

 

마무리하며

사실 정적 중첩 클래스를 Validation의 방법으로 채택한 이유는, 검증도 그 행위의 책임을 갖고 있는 쪽에서 하는 게 맞다고 생각을 했다. 그래서 찾다가찾다가 "좀 독립적인 검증 방법이 없을까?" 해서 찾은 개념이 정적 중첩 클래스이다!

이 방법은 역할별로 확실하게 독립적인 검증을 할 수 있다는 장점이 있어보이는데, 코드가 너무 길어지지 않을까 좀 걱정이 된다. 실제로 이 검증 방법을 택해서 1주차 문제를 풀어가보면 문제 특성상 예외를 깐깐하게 따질 수가 있어서, 도메인 쪽의 검증 부분이 굉장히 많아지게 된다. 그래서 뭔가 다른 계층에 비해 도메인의 코드가 너무 길어서 걱정이긴하다.

뭔가 틀렸다면 우테코의 동료들이 채찍질을 해주겠지! 하지만 나는 코드가 이렇게 길어진 것은 검증에서도 SRP(단일 책임 원칙)을 지키려고 노력했다!!! 라고 당당하게 말하며 매를 맞을 예정이다. 내 주관이 들어간 코드이다 보니 얼른 코드리뷰 받고 싶다 ㅎㅎ.  참 다양한 코드 아키텍처들이 존재하는구나를 깨닫는 요즘이다 재밌다 개발.

'Java & Kotlin' 카테고리의 다른 글

[JAVA] 일급 콜렉션을 이용하여 상태와 로직을 따로 관리하자!  (0) 2025.10.17
[JAVA] final 키워드 정복하기!  (0) 2025.10.16
[JAVA] 정적 팩토리 메서드(Static Factory Method)란? (버거 먹고싶은 작성자와 스프링의 반환 방식을 곁들인)  (0) 2025.10.16
[JAVA] String trim() 메서드 : 문자열의 앞뒤에 있는 모든 공백 문자를 제거해주는 아주 좋은친구 소개  (0) 2024.11.09
[JAVA] 빠른 입출력을 위한 BufferdReader, BufferWriter  (0) 2024.10.16
'Java & Kotlin' 카테고리의 다른 글
  • [JAVA] 일급 콜렉션을 이용하여 상태와 로직을 따로 관리하자!
  • [JAVA] final 키워드 정복하기!
  • [JAVA] 정적 팩토리 메서드(Static Factory Method)란? (버거 먹고싶은 작성자와 스프링의 반환 방식을 곁들인)
  • [JAVA] String trim() 메서드 : 문자열의 앞뒤에 있는 모든 공백 문자를 제거해주는 아주 좋은친구 소개
노을을
노을을
진인사대천명
  • 노을을
    노을의 개발일기장
    노을을
  • 전체
    오늘
    어제
    • All (57) N
      • Java & Kotlin (16) N
      • Spring (1) N
      • Problem Solve (11) N
      • Computer Science (0)
      • Infra (1)
      • DB (2)
      • Various Dev (23)
        • 우아한테크코스 (9)
        • Git&Github (2)
        • Unity (12)
      • Book (1)
      • Writing (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    자바
    유니티
    코딩
    알고리즘
    개발
    우아한테크코스
    코딩테스트
    합격
    8기
    게임개발
    티스토리챌린지
    git
    오픈미션
    java
    개발자
    프리코스
    백준
    우테코
    github
    코테
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
노을을
[JAVA] 정적 중첩 클래스를 활용하여 계층간 독립적인 Validation을 적용해보자
상단으로

티스토리툴바