Backend/Java

[Java]Java IO(Exception/throw/File/스트림)

해로몬 2024. 10. 24. 17:09

1. 예외(Exception)란?

  • 예외는 프로그램 실행 중 발생하는 비정상적인 상황을 의미합니다. 예를 들어, 배열 인덱스 초과, 파일을 찾을 수 없음, null 값을 참조하려고 할 때 발생하는 오류 등이 예외입니다.
  • 예외가 발생하면 프로그램의 정상적인 흐름이 깨질 수 있으므로, 예외를 처리해야 프로그램이 정상적으로 종료되거나 안정적으로 실행됩니다.

<if문을 사용한 예외 방지>

if문을 사용하여 예외 상황을 처리하는 방법은 프로그램이 예외를 발생시키지 않고, 조건을 확인하여 예외가 발생할 가능성이 있는 상황을 미리 처리하는 방식입니다
예를 들어, 숫자를 0으로 나누는 오류는 미리 조건을 확인하여 방지할 수 있습니다.

2. 예외의 종류

자바에서 예외는 크게 두 가지로 나뉩니다.

  1. Checked Exception (검사된 예외):
  1. Unchecked Exception (검사되지 않은 예외):

3. 예외 처리 구문 (try-catch-finally)

자바에서 예외를 처리하는 기본 구문은 try-catch-finally 블록입니다.

  • try 블록: 예외가 발생할 가능성이 있는 코드를 작성합니다.
  • catch 블록: 예외가 발생하면 처리하는 코드를 작성합니다. 예외가 발생했을 때 해당 예외를 처리합니다.
  • finally 블록: 예외 발생 여부와 관계없이 항상 실행되는 블록입니다. 주로 자원 해제(예: 파일 닫기, 데이터베이스 연결 해제)에 사용됩니다.

[try-catch]

try {
	// 예외 발생 가능 코드
	// 일반 예외 / 실행 예외 발생 가능 코드
} catch( 예외 클래스명 참조변수명 ) {
	//해당 예외가 발생한 경우 처리블록
}

 

 

[try - carch - finally]

try {
	// 예외 발생 가능 코드
	// 일반 예외 / 실행 예외 발생 가능 코드
} catch( 예외 클래스명 참조변수명 ) {
	//해당 예외가 발생한 경우 처리블록
} finally {
	// 예외 발생여부에 상관없이 무조건 실행블록
}

 

<예외발생 throw>

throw는 자바에서 예외를 명시적으로 발생시키는 데 사용하는 키워드입니다. 특정 조건에서 개발자가 직접 예외를 발생시키고, 그 예외를 처리할 수 있도록 하는 기능을 제공합니다. 주로 사용자 정의 예외를 던지거나, 특정 로직에서 의도적으로 예외를 발생시킬 때 사용됩니다.

public class Example {
    public static void main(String[] args) {
        throw new NullPointerException("강제 예외 발생");
    }
}

 

throw를 사용하는 상황

  • 검증 조건에 맞지 않을 때: 입력값이 유효하지 않거나 특정 조건을 충족하지 못할 경우 명시적으로 예외를 던져 오류를 처리하도록 할 수 있습니다.
  • 사용자 정의 예외: 사용자가 정의한 예외를 직접 발생시켜 특정 상황에 맞는 오류 처리를 유도할 수 있습니다.
  • 프로그램 로직에서 특정 상태를 명확히 알릴 때: 프로그램에서 비정상적인 흐름이나 중단을 요구하는 경우 직접 예외를 던질 수 있습니다.

throw와 throws의 차이

  • throw: 실제로 예외를 발생시키는 데 사용됩니다.
  • throws: 메서드 선언부에서 예외가 발생할 가능성이 있음을 선언하는 키워드입니다.

 

<예외 던지기 throws>

throws는 자바에서 메서드 선언부에 사용되어, 해당 메서드가 예외를 던질 가능성이 있음을 호출하는 쪽에 알리는 키워드입니다. 즉, 메서드 내부에서 발생할 수 있는 예외를 처리하지 않고 호출한 곳으로 던질 때 사용합니다. 이를 통해 호출한 메서드에서 예외를 직접 처리하거나, 또 다른 곳으로 예외 처리를 위임할 수 있습니다.


1. throws의 사용 목적

  • 메서드에서 발생할 수 있는 예외를 호출한 메서드로 전달하여 처리할 수 있도록 합니다.
  • Checked Exception(검사된 예외)에 대해 예외 처리를 강제합니다. throws를 사용하지 않으면 컴파일러에서 오류가 발생합니다.
  • Unchecked Exception(검사되지 않은 예외)는 throws로 명시할 필요가 없지만, 코드 가독성을 위해 사용하기도 합니다.

 

public void myMethod() throws IOException {
    // 이 메서드는 IOException을 던질 수 있음
    throw new IOException("입출력 오류 발생");
}

2. throws 키워드를 사용하는 이유

  • Checked Exception 처리 강제: 자바에서는 Checked Exception이 발생할 수 있는 메서드는 반드시 예외 처리를 강제합니다. 이를 처리하는 방식은 두 가지입니다:
  • 예외 처리를 위임: 메서드 내부에서 발생한 예외를 직접 처리하지 않고, 호출한 메서드가 그 예외를 처리하도록 할 수 있습니다. 이를 통해 예외 처리가 필요 없는 단순 로직과 예외 처리 로직을 분리할 수 있습니다.

[java.io 패키지]

자바의 java.io 패키지는 **입출력(IO)**을 처리하는데 필수적인 클래스들을 제공합니다. 이 패키지는 데이터의 입출력 처리와 관련된 다양한 스트림 클래스를 포함하고 있어, 프로그램이 파일, 네트워크, 메모리 등에서 데이터를 읽고 쓰는 작업을 간단하게 처리할 수 있게 도와줍니다.

1. 데이터 종류 및 저장 위치

데이터는 두 가지로 나눌 수 있습니다:

  1. 임시 저장 데이터: 메모리에 저장되는 휘발성 데이터로, 프로그램 종료 시 사라집니다.
  2. 영구 저장 데이터: 하드디스크나 SSD와 같은 영구적인 저장장치에 저장되며, 파일이나 데이터베이스를 사용해 저장합니다.

이 중 파일은 자주 사용되는 저장 방식으로, 자바에서는 이를 처리하기 위해 java.io.File 클래스를 제공합니다.

 

2. CRUD 작업

입출력의 기본적인 데이터 처리 작업은 CRUD입니다:

  • Create: 데이터를 생성
  • Read: 데이터를 읽기
  • Update: 데이터를 갱신
  • Delete: 데이터를 삭제

이러한 작업들은 자바에서 파일 입출력 시 자주 사용되며, java.io 패키지를 통해 쉽게 처리할 수 있습니다.

 

 

<절대경로와 상대경로>

파일을 읽거나 쓸 때, 파일의 위치를 경로(path)로 지정합니다. 자바에서는 파일 경로를 사용할 때, 절대경로상대경로 두 가지 방식이 있습니다. 이 둘은 파일 시스템 내에서 파일이나 디렉토리의 위치를 나타내는 방식이 다릅니다.

1. 절대경로 (Absolute Path)

절대경로는 파일이나 디렉토리의 위치를 루트 디렉토리(root directory)로부터 완전한 경로를 나타냅니다. 즉, 파일 시스템의 최상위 디렉토리에서부터 시작해, 파일이나 디렉토리까지의 모든 경로를 명확하게 기술한 경로입니다.
특징:

  • 항상 동일한 경로를 가리킵니다.
  • 어느 위치에서 실행하더라도 해당 경로는 변경되지 않습니다.

 

2. 상대경로 (Relative Path)

상대경로현재 작업 디렉토리(현재 프로그램이 실행되고 있는 위치)를 기준으로, 파일이나 디렉토리의 경로를 지정하는 방식입니다. 즉, 현재 디렉토리를 기준으로 상대적으로 어디에 파일이 있는지를 나타냅니다.
특징:

  • 현재 작업 디렉토리에 따라 경로가 달라질 수 있습니다.
  • 프로그램이 실행되는 위치에 따라 파일의 경로가 다르게 해석됩니다.
  • 프로젝트 내에서 동적으로 경로를 지정할 때 유용합니다.
절대경로 상대경로
루트 디렉토리부터 파일이나 디렉토리까지의 전체경로 현재 작업 디렉토리를 기준으로 한 상대적인 경로
파일위치가 명확하게 정의 현재 작업 디렉토리에 따라 파일 위치가 달라질 수 있음
언제나 고정된 경로를 참조 프로젝트나 파일의 구조가 바뀔 때 유용

 

실전에서의 사용

  • 절대경로는 주로 파일의 위치가 명확하고 고정적일 때 사용합니다. 예를 들어, 시스템 파일이나 설정 파일의 경로를 다룰 때 유용합니다.
  • 상대경로프로젝트 내에서 파일이나 자원을 동적으로 참조해야 할 때 사용됩니다. 특히, 파일의 위치가 자주 변경되거나, 다른 환경에서 동일한 코드를 실행할 때 유용합니다. 이를 통해 코드가 더 이식성 있게 동작할 수 있습니다.

<File>

 

File 클래스는 자바에서 파일이나 디렉토리(폴더)를 조작하기 위해 사용하는 클래스입니다. 파일의 생성, 삭제, 정보 조회와 같은 파일 시스템 관련 작업을 처리할 때 많이 사용됩니다. 하지만, 파일의 내용을 직접 읽거나 쓰는 작업은 하지 않고, 주로 파일이나 디렉토리 자체를 관리하는 데 사용됩니다.
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/File.html

 

File (Java SE 17 & JDK 17)

All Implemented Interfaces: Serializable, Comparable An abstract representation of file and directory pathnames. User interfaces and operating systems use system-dependent pathname strings to name files and directories. This class presents an abstract, sys

docs.oracle.com

 

1. File 클래스의 기본 개념

  • 파일디렉토리추상화한 클래스입니다.
  • 파일의 존재 여부 확인, 파일 삭제, 파일 경로 정보를 얻는 등의 파일 조작을 할 수 있습니다.
  • 파일뿐만 아니라 디렉토리(폴더)도 조작할 수 있습니다.

2. File 객체 생성

File 클래스는 파일이나 디렉토리의 경로를 기반으로 객체를 생성합니다. 이를 통해 파일에 대한 다양한 작업을 수행할 수 있습니다.

import java.io.File;

public class FileExample {
    public static void main(String[] args) {
        // 파일 객체 생성: 파일의 경로를 전달
        File file = new File("example.txt");

        // 경로 출력
        System.out.println("파일 경로: " + file.getAbsolutePath());
    }
}
메서드 설명
exists() 파일 또는 디렉토리의 존재 유무 확인
isFile() 경로가 파일인지 확인
isDirectory() 경로가 디렉토리인지 확인
getName() 파일 또는 디렉토리의 이름을 반환
getAbsolutePath() 파일 또는 디렉토리의 절대 경로를 반환
length() 파일의 크기를 반환
createNewFile() 새로운 파일을 생성(파일이 존재하면 생성x)
delete() 파일 또는 디렉토리 삭제
mkdir() 하나의 디렉토리 생성
mkdirs() 필요한 모든 상위 디렉토리까지 포함하여 디렉토리를 생성
listFiles() 디렉토리 내의 파일 및 디렉토리 목록을 File 객체 배열로 반환
canRead() 파일이 읽기 가능한지 확인
canWrite() 파일이 쓰기 가능한지 확인

 

 

[Input Output 스트림]

 

1. 스트림(Stream) 개념

스트림데이터의 흐름을 의미하며, 데이터를 읽고 쓰는 과정을 추상화한 개념입니다. 자바의 입출력 시스템은 스트림을 통해 데이터를 주고받습니다. 자바에서는 크게 두 가지 스트림을 사용합니다:

  • 입력 스트림 (Input Stream): 외부로부터 데이터를 읽어오는 스트림입니다. 예를 들어, 파일이나 키보드 입력으로부터 데이터를 읽을 때 사용됩니다.
  • 출력 스트림 (Output Stream): 데이터를 외부로 출력하는 스트림입니다. 예를 들어, 파일에 데이터를 기록하거나 콘솔에 출력을 할 때 사용됩니다.

2. 스트림의 종류

자바의 입출력 스트림은 데이터의 형태에 따라 바이트 스트림문자 스트림으로 나뉩니다.
(1) 바이트 스트림 (Byte Stream)

  • 바이트 단위(8비트)로 데이터를 처리하는 스트림입니다.
  • 모든 유형의 데이터를 처리할 수 있으며, 파일, 이미지, 오디오 등의 바이너리 데이터에 적합합니다.
  • 바이트 스트림 클래스:

(2) 문자 스트림 (Character Stream)

  • 문자 단위(16비트)로 데이터를 처리하는 스트림입니다.
  • 텍스트 데이터를 읽거나 쓸 때 사용되며, 유니코드 문자를 처리하기 위해 설계되었습니다.
  • 문자 스트림 클래스:

 

3. 바이트 스트림의 주요 클래스

  • 입력 스트림: InputStream을 상속하는 클래스들이 있으며, 파일로부터 데이터를 읽어오는 역할을 합니다.
  • 출력 스트림: OutputStream을 상속하는 클래스들이 있으며, 데이터를 파일로 출력하는 역할을 합니다.

 

스트림 종류 입력 클래스 출력 클래스 데이터 유형
바이트 스트림 FileInputStream FileOutputStream 바이트 데이터 (텍스트, 이미지, 비디오 등)
문자 스트림 FileReader FileWriter 문자 데이터 (텍스트 파일)

 

줄바꿈 방식의 차이
Windows\r\n (캐리지 리턴 + 줄바꿈)으로 줄을 나눕니다.
macOS (및 대부분의 유닉스 계열 시스템)에서는 \n (줄바꿈)만 사용합니다.

 

주 스트림과 보조 스트림

1. 주 스트림 (Primary Stream)

주 스트림데이터 입출력 작업을 직접 처리하는 스트림입니다. 파일, 네트워크, 메모리 등에서 직접 데이터를 읽고 쓰는 스트림으로, 바이트 스트림문자 스트림을 포함합니다. 주 스트림은 입출력의 기본 역할을 하며, 데이터를 읽거나 쓰는 핵심 작업을 수행합니다.
바이트 스트림

  • InputStreamOutputStream을 상속하는 클래스들이 바이트 단위의 데이터를 처리합니다.

문자 스트림

  • ReaderWriter를 상속하는 클래스들이 문자 단위의 데이터를 처리합니다.

이 두 클래스는 문자 스트림을 다루므로, 유니코드 기반의 다국어 데이터를 처리하는 데 적합

 

2. 보조 스트림 (Secondary Stream)

보조 스트림주 스트림을 감싸거나 연결하여 추가 기능을 제공하는 스트림입니다. 보조 스트림 자체는 데이터를 직접 입출력하지 않으며, 주 스트림에 성능 향상이나 데이터 변환 등의 부가적인 기능을 제공합니다. 예를 들어, 버퍼링, 데이터 변환(객체 직렬화), 필터링 등의 기능을 제공합니다.


보조 스트림의 주요 역할

  • 버퍼링: 입출력 성능을 개선하는 역할을 합니다.
  • 데이터 변환: 기본 타입이나 객체를 스트림으로 변환하거나, 반대로 스트림에서 데이터를 변환해 처리합니다.



버퍼링을 통한 성능 개선

파일 입출력 작업에서 버퍼링(Buffering)을 사용하면 성능을 크게 향상시킬 수 있습니다. 버퍼를 사용하면 데이터가 일정 크기만큼 모일 때 한 번에 처리되므로, 입출력 속도가 빨라집니다.

  • 버퍼링 클래스:

버퍼를 사용하는 이유
컴퓨터의 입출력 장치(디스크, 네트워크, 키보드 등)와 프로그램 간에는 속도 차이가 큽니다. 프로그램이 처리 속도가 빠른 반면, 입출력 장치의 속도는 상대적으로 느리기 때문에, 작은 데이터를 여러 번 주고받을 경우 성능 저하가 발생할 수 있습니다.

버퍼를 사용하면 데이터를 한 번에 모아서 처리하여 입출력 작업의 빈도를 줄이고, 전체적인 성능을 개선할 수 있습니다. 이는 자바의 파일 IO 작업에서 특히 중요합니다.

- 버퍼를 사용하지 않는 입출력은 우편 배달부가 편지를 하나씩 배달하는 것과 같습니다. 매번 배달할 때마다 시간이 걸립니다.
- 버퍼를 사용하는 입출력은 편지를 일정량 모아서 한 번에 배달하는 것과 비슷합니다. 더 적은 배달 횟수로 더 많은 편지를 처리할 수 있습니다.

 

 

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class Ex {
    public static void main(String[] args) {

        FileInputStream fis = null; // 파일로부터 데이터를 읽기 위한 FileInputStream 변수 선언

        try {
            // FileInputStream을 통해 특정 파일을 연다
            fis = new FileInputStream("/java/test.txt"); // 파일 경로 지정

            int data = 0; // 읽은 데이터를 저장할 변수 초기화

            // 파일에서 데이터를 하나씩 읽고, 읽은 값이 -1(파일 끝)이 될 때까지 반복
            while ((data = fis.read()) != -1) {
                // 읽은 데이터를 문자로 변환해 출력
                // 다국어(한글 포함) 읽을 수 없다.
                System.out.print((char) data);
            }
        } catch (FileNotFoundException e) {
            // 지정된 파일을 찾지 못했을 때 예외 처리
            System.out.println("[에러] 파일을 찾을 수 없습니다: " + e.getMessage());
        } catch (IOException e) {
            // 파일을 읽는 도중 발생하는 입출력 오류 처리
            System.out.println("[에러] 파일을 읽는 도중 문제가 발생했습니다: " + e.getMessage());
        } finally {
            // 파일 입력 스트림을 닫아 자원 해제 (null 체크 필수)
            if (fis != null) {
                try {
                    fis.close(); // 스트림을 닫아 메모리 누수를 방지
                } catch (IOException e) {
                    // 스트림을 닫는 도중 발생하는 예외 처리
                    System.out.println("[에러] 파일 스트림을 닫는 도중 문제가 발생했습니다: " + e.getMessage());
                }
            }
        }
    }
}

 

 

<보조스트림 - Data(I/O)Stream>

보조 스트림이란?

보조 스트림(Secondary Stream)은 주 스트림을 감싸서 추가적인 기능을 제공하는 스트림입니다. 예를 들어, ObjectOutputStream바이트 스트림 위에 적용되어 객체를 직렬화하고 이를 바이트 스트림으로 처리하는 기능을 제공합니다.

  • 주 스트림: 파일, 네트워크 등에서 데이터를 실제로 읽고 쓰는 역할을 담당합니다. (FileInputStream, FileOutputStream 등)
  • 보조 스트림: 주 스트림에 부가적인 기능을 추가합니다. (예: 객체 직렬화, 버퍼링 등)


DataInputStream과 DataOutputStream은 자바에서 기본 데이터 타입(int, float, boolean 등)을 바이너리 형식으로 읽고 쓸 수 있도록 지원하는 바이트 스트림 클래스입니다

 

1. DataOutputStream: 데이터를 바이너리 형식으로 쓰기

DataOutputStream은 기본 데이터 타입을 바이너리 형식으로 출력(쓰기)할 수 있는 스트림입니다. 기본 타입의 값을 직접 파일이나 스트림에 기록할 수 있으며, 이를 통해 데이터가 텍스트 형식이 아닌 바이너리로 저장됩니다.
DataOutputStream 주요 메서드

  • writeInt(int): 정수를 바이너리 형식으로 씀.
  • writeDouble(double): 실수를 바이너리 형식으로 씀.
  • writeBoolean(boolean): 논리 값을 바이너리 형식으로 씀.
  • writeUTF(String): UTF-8 인코딩으로 문자열을 씀.
try {
            // DataOutputStream을 사용해 데이터를 바이너리 형식으로 파일에 씀
            FileOutputStream fos = new FileOutputStream("data.bin");
            DataOutputStream dos = new DataOutputStream(fos);

            // 기본 데이터 타입을 바이너리 형식으로 기록
            dos.writeInt(42);             // 정수 쓰기
            dos.writeDouble(3.14159);      // 실수 쓰기
            dos.writeBoolean(true);        // 논리 값 쓰기
            dos.writeUTF("안녕하세요!");     // UTF-8 문자열 쓰기

            dos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

 

2. DataInputStream: 바이너리 데이터를 읽어오기

DataInputStream은 바이너리 형식으로 저장된 데이터를 읽어, 이를 기본 데이터 타입으로 변환해 줍니다. 바이너리 파일이나 네트워크로 전송된 이진 데이터에서 직접 정수, 실수 등의 기본형 데이터를 읽어올 수 있습니다.
DataInputStream 주요 메서드

  • readInt(): 바이너리로 저장된 정수를 읽어옴.
  • readDouble(): 바이너리로 저장된 실수를 읽어옴.
  • readBoolean(): 바이너리로 저장된 논리 값을 읽어옴.
  • readUTF(): UTF-8 형식으로 저장된 문자열을 읽어옴.

 

try {
            // DataInputStream을 사용해 바이너리 형식의 데이터를 읽음
            FileInputStream fis = new FileInputStream("data.bin");
            DataInputStream dis = new DataInputStream(fis);

            // 바이너리 데이터를 읽어와서 기본 데이터 타입으로 변환
            int number = dis.readInt();          // 정수 읽기
            double pi = dis.readDouble();        // 실수 읽기
            boolean isTrue = dis.readBoolean();  // 논리 값 읽기
            String message = dis.readUTF();      // UTF-8 문자열 읽기

            // 읽어온 데이터 출력
            System.out.println("정수: " + number);
            System.out.println("실수: " + pi);
            System.out.println("논리 값: " + isTrue);
            System.out.println("문자열: " + message);

            dis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

3. DataInputStream과 DataOutputStream의 사용 목적

  • 기본 데이터 타입을 효율적으로 처리
  • 바이너리 데이터의 입출력 : 기본데이터 타입을 이진형식으로 저장, 파일 크기를 줄이고 빠르게 읽고 쓰기 가능

<보조스트림 - Object(I/O)Stream>

ObjectInputStream과 ObjectOutputStream은 자바에서 객체를 직렬화(serialize)하거나 역직렬화(deserialize)하여 파일, 네트워크 스트림 등으로 입출력할 수 있게 도와주는 보조 스트림(secondary stream)입니다. 이 스트림들은 객체를 저장하고 복원하는 기능을 제공하므로, 데이터를 단순한 바이트나 텍스트뿐만 아니라 객체 형태로 저장할 수 있습니다.

직렬화(Serialization)와 역직렬화(Deserialization)

  • 직렬화(Serialization): 객체를 바이트 형식으로 변환하여 파일에 저장하거나 네트워크를 통해 전송할 수 있도록 만드는 과정입니다. 객체의 상태(필드 값)를 바이트 스트림으로 변환하여 저장할 수 있게 해줍니다.
  • 역직렬화(Deserialization): 바이트 스트림으로부터 객체를 복원하는 과정입니다. 즉, 파일이나 네트워크로 전송된 바이트 데이터를 객체로 복구하는 것입니다.

 

1. ObjectOutputStream: 객체를 직렬화하여 출력

ObjectOutputStream은 객체를 직렬화하여 파일 또는 다른 출력 스트림에 기록하는 데 사용됩니다. 직렬화된 객체는 바이트 형태로 저장되며, 파일이나 네트워크로 전송할 수 있습니다.

2. ObjectInputStream: 객체를 역직렬화하여 입력

ObjectInputStream은 직렬화된 객체 데이터를 읽어 객체로 복원하는 데 사용됩니다. 이는 파일이나 네트워크로부터 바이트 스트림을 받아, 다시 객체 형태로 변환하는 기능을 제공합니다.

3. 직렬화 가능 클래스 (Serializable 인터페이스)

객체를 직렬화하기 위해서는, 해당 클래스가 Serializable 인터페이스를 반드시 구현해야 합니다. Serializable은 마커 인터페이스로, 특정 메서드를 구현할 필요는 없지만 자바 가상 머신(JVM)이 직렬화할 수 있는 객체임을 인식하도록 합니다.

4. 직렬화되지 않은 필드 처리 (transient)
직렬화하고 싶지 않은 필드가 있다면, 해당 필드를 transient 키워드로 선언하면 됩니다. transient로 선언된 필드는 직렬화 과정에서 제외됩니다.

 

 

'Backend > Java' 카테고리의 다른 글

[Java] 커넥션 풀  (0) 2024.10.31
[Java] 중첩클래스  (0) 2024.10.31
[Java] Collection  (2) 2024.10.23
[java] System/Data/Calendar/Random/Array 클래스  (0) 2024.10.22
[java]java.lang 패키지  (0) 2024.10.21