본문 바로가기

내일배움캠프

TIL 11일차

예외처리

 

예외 처리(Exception Handling)는 프로그래밍에서 발생할 수 있는 예상치 못한 상황 또는 오류에 대비하는 방법입니다. 자바에서 예외는 두 가지 종류로 나뉩니다: 확인된 예외(checked exception)와 확인되지 않은 예외(unchecked exception).

  1. 확인된 예외(checked exception): 컴파일러가 확인할 수 있는 예외로서, 반드시 예외 처리를 해야 합니다. IOException, FileNotFoundException 등이 여기에 속합니다. 이를 처리하지 않으면 컴파일 오류가 발생합니다.
  2. 확인되지 않은 예외(unchecked exception): 컴파일러가 예외 처리를 강제하지 않는 예외로서, 주로 프로그래머의 실수나 프로그램의 상태에 따라 발생합니다. NullPointerException, ArrayIndexOutOfBoundsException 등이 여기에 속합니다. 이를 처리하지 않아도 컴파일 오류는 발생하지 않지만, 실행 중에 프로그램이 비정상적으로 종료될 수 있습니다.

자바에서 예외 처리는 try, catch, finally 블록을 사용하여 수행됩니다.

  • try: 예외가 발생할 수 있는 코드 블록을 포함합니다.
  • catch: 예외가 발생했을 때 처리할 코드 블록을 정의합니다.

finally: 예외 발생 여부에 관계 없이 항상 실행되는 코드 블록을 정의합니다.

try { // 예외가 발생할 수 있는 코드 } 
catch (예외클래스1 e1) { // 예외 처리 코드 } 
catch (예외클래스2 e2) { // 예외 처리 코드 } 
finally { // 예외 발생 여부와 관계없이 항상 실행되는 코드 }

 

또한, throw 키워드를 사용하여 개발자가 직접 예외를 발생시킬 수도 있습니다. 이는 특정 조건이 충족되지 않거나 잘못된 입력 등에 대한 응답으로 사용될 수 있습니다.

예외 처리를 통해 프로그램의 안정성을 높이고, 예외 상황에 대처할 수 있는 기능을 제공할 수 있습니다.

 

예외 처리 클래스를 만들어서 특정 예외 상황에 대응하는 것은 자바에서 매우 흔한 일입니다. 이를 통해 사용자 정의 예외를 만들어 프로그램의 가독성을 높이고 유지 보수를 용이하게 할 수 있습니다. 여기에는 몇 단계가 있습니다:

  1. 예외 클래스 생성: Exception 클래스를 확장하여 새로운 예외 클래스를 만듭니다.
  2. 생성자 정의: 예외 클래스에 필요한 생성자를 정의하여 예외 객체를 초기화합니다.
  3. 예외 메시지 설정: 예외 객체에 예외 메시지를 설정하여 예외 발생 시 유용한 정보를 전달합니다.

예를 들어, 파일을 열 때 파일이 존재하지 않는 경우 예외를 발생시키는 클래스를 만들어 보겠습니다.

public class FileNotFoundException extends Exception { // 예외 클래스 생성 
	public FileNotFoundException(String message) { // 생성자 정의 및 예외 메시지 설정 
    	super(message); 
    } 
}

이제 이 예외 클래스를 사용하여 파일을 열 때 파일이 존재하지 않는 경우를 처리할 수 있습니다.

import java.io.File; 
public class FileProcessor { 
	public void openFile(String fileName) throws FileNotFoundException { 
    	File file = new File(fileName); 
        if (!file.exists()) { // 파일이 존재하지 않는 경우 fileNotFoundException 발생 
        	throw new FileNotFoundException("File not found: " + fileName); 
        } // 파일이 존재하는 경우 처리 // (파일을 열거나 다른 작업을 수행할 수 있음) 
    }
}

 

위의 예제에서 openFile 메서드는 파일이 존재하지 않을 때 FileNotFoundException을 발생시킵니다. 호출하는 쪽에서는 이 예외를 처리하거나 위임할 수 있습니다.

public class Main { 
	public static void main(String[] args) { 
    	FileProcessor processor = new FileProcessor(); 
        String fileName = "example.txt"; 
        try { processor.openFile(fileName); 
     	}catch (FileNotFoundException e) { // 파일이 존재하지 않는 경우 처리 
        	System.out.println("File not found: " + e.getMessage()); // 예외 처리 코드 추가 가능 
        } 
    }
}

 

이렇게 하면 프로그램이 파일이 존재하지 않는 경우에 대비하여 예외를 처리할 수 있습니다. 이러한 방식으로 사용자 정의 예외 클래스를 만들어 프로그램의 예외 처리를 보다 효과적으로 관리할 수 있습니다.

 

제네릭

제네릭(Generic)은 자바에서 타입을 파라미터화하는 기능을 말합니다. 이를 통해 클래스, 인터페이스, 메서드를 정의할 때 일반화된 타입을 사용할 수 있습니다. 제네릭을 사용하면 코드의 재사용성과 타입 안정성을 높일 수 있습니다.

제네릭을 사용하는 가장 일반적인 이유는 다음과 같습니다:

  1. 타입 안정성(Type Safety): 제네릭을 사용하면 컴파일러가 코드를 더 강력하게 검사하여 타입 불일치로 인한 오류를 사전에 방지할 수 있습니다. 즉, 실행 중에 발생할 수 있는 타입 관련 오류를 컴파일 시간에 잡아낼 수 있습니다.
  2. 코드 재사용성(Reusability): 일반화된 코드를 작성함으로써 여러 종류의 데이터 타입에 대해 동작하는 클래스, 메서드, 인터페이스를 만들 수 있습니다. 이는 코드의 재사용성을 높이고 중복을 줄여줍니다.

제네릭을 사용하는 방법은 다음과 같습니다:

  1. 클래스나 인터페이스에서 제네릭 타입 선언하기: 제네릭 타입은 클래스나 인터페이스 선언 시에 파라미터로 선언됩니다. 보통 대문자 알파벳으로 표기하며, 클래스 내부에서 해당 타입을 사용할 수 있습니다.
    public class MyClass<T> {
        private T myField;
    
        public MyClass(T myField) {
            this.myField = myField;
        }
    
        public T getMyField() {
            return myField;
        }
    
        public void setMyField(T myField) {
            this.myField = myField;
        }
    }​
  2. 제네릭 메서드 만들기: 클래스나 인터페이스의 일반 메서드처럼, 메서드도 제네릭 타입을 사용할 수 있습니다.
    public class Utils {
        public static <T> T findMax(T[] array) {
            // 배열에서 최댓값을 찾는 메서드 구현
        }
    }
  3. 제네릭을 사용한 인터페이스 구현하기: 인터페이스에도 제네릭을 사용하여 여러 종류의 데이터 타입을 지원할 수 있습니다.
public interface List<T> {
    void add(T element);
    T get(int index);
}

 

 

제네릭을 사용하면 컴파일 시간에 타입 불일치 관련 오류를 잡을 수 있으므로 프로그램의 안정성을 높이고, 코드의 재사용성을 향상시킬 수 있습니다.

 
 

람다 표현식(Lambda Expressions):

람다 표현식은 간단히 말해 익명 함수입니다. 메서드를 하나의 식(expression)으로 표현한 것입니다. 주로 함수형 인터페이스(Functional Interface)의 구현체를 생성할 때 사용됩니다.

예를 들어, 기존의 익명 내부 클래스를 사용하여 Runnable 인터페이스의 구현체를 생성하는 것과 람다 표현식을 사용하는 것을 비교해보겠습니다.

// 익명 내부 클래스를 사용한 Runnable 구현체
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello, world!");
    }
};

// 람다 표현식을 사용한 Runnable 구현체
Runnable runnable = () -> System.out.println("Hello, world!");
 

람다 표현식은 메서드의 매개변수로 전달되거나, 메서드의 반환 값으로 사용될 수 있습니다. 또한 함수형 인터페이스의 메서드를 구현할 때 간결한 방법을 제공합니다.

스트림(Stream):

스트림은 데이터를 처리하는 연속적인 연산(operations)을 나타내는 요소들의 시퀀스입니다. 스트림은 컬렉션(Collection)과 비슷하지만, 컬렉션은 데이터를 저장하고 있지만 스트림은 데이터를 소비(consume)하고 생성할 수 있습니다. 스트림을 이용하면 데이터의 처리 과정을 선언적(Declarative)으로 표현할 수 있습니다.

 

스트림은 중간 연산(intermediate operations)과 최종 연산(terminal operations)으로 구성됩니다. 중간 연산은 다른 스트림을 반환하고, 최종 연산은 스트림을 소비하고 결과를 생성합니다.

List<String> myList = Arrays.asList("apple", "banana", "orange");

// 중간 연산: 문자열의 길이가 5 이상인 요소만 필터링
Stream<String> filteredStream = myList.stream().filter(s -> s.length() >= 5);

// 최종 연산: 필터링된 요소들을 출력
filteredStream.forEach(System.out::println);

 

위의 코드에서 filter는 중간 연산이고, forEach는 최종 연산입니다. 중간 연산을 연결하여 복잡한 데이터 처리 파이프라인을 만들 수 있으며, 최종 연산을 통해 결과를 생성합니다.

람다 표현식과 스트림을 함께 사용하면 코드를 더 간결하고 가독성이 높아지며, 병렬 처리(parallel processing)와 같은 성능 향상도 기대할 수 있습니다.

'내일배움캠프' 카테고리의 다른 글

TIL 13일  (1) 2024.05.01
TIL 12일차  (1) 2024.04.30
10일  (1) 2024.04.26
9일차  (0) 2024.04.25
8일차 TIL  (0) 2024.04.24