예외처리, 제네릭

star_pooh·2024년 11월 14일
0

TIL

목록 보기
20/39
post-thumbnail

예외처리

프로그램에서 발생할 것 같은 문제를 정의하고 대응하는 것

오류(Error) vs 예외(Exception)

회복 가능 여부를 기준으로 오류와 예외를 구분

회복 가능 여부란?
예외가 발생할 수 있다는 것을 인지하고 그에 따른 대응을 할 수 있는 것

  • 오류(Error)
    • 일반적으로 회복이 불가능한 문제
    • 시스템 레벨 또는 환경적인 이유로 발생
    • 어떠한 에러로 프로그램이 종료되었는지를 확인하고 대응
  • 예외(Exception)
    • 일반적으로 회복이 가능한 문제
    • 코드 레벨에서 가능한 대응을 예외 처리라고 칭함

예외의 종류

  • 코드 실행 관점의 예외 종류
    - 컴파일 에러
    - .java파일을 .class파일로 컴파일할 때 발생
    - 대부분은 틀린 문법으로 인해 발생
    - 틀린 문법을 수정하여 해결
    - 런타임 에러
    - 프로그램 실행 중에 발생

  • 예외 처리 관점의 예외 종류
    • 확인된 예외 (Checked Exception)
      • 컴파일 시점에 확인하는 예외로서 반드시 예외 처리 필요
        ⚠️ 인지한 특정 문제에 대해 예외 처리가 완료되었는지 확인(Check)할 수 있는 예외
        ⚠️ 예외 처리를 완료하지 않았다면 컴파일 에러 발생
    • 미확인된 예외 (Unchecked Exception)
      • 런타임 시점에 확인되는 예외
        • 예외 처리가 반드시 필요하지 않음

예외 발생과 try-catch-finally문

예외 정의 및 알리기

  • 문제에 대한 예외가 Exception 클래스에 없거나 더 구체화하고 싶다면 별도의 예외 클래스에서 정의할 수 있음
class OurBadException extends Exception {
	public OurBadException() {
		super("위험한 행동을 하면 예외처리를 꼭 해야합니다!");
	}
}

  • 문제가 발생할 수도 있다는 것을 해당 메소드와 로직에서 throw, throws를 사용하여 알려야함
    • throws
      • 메소드 이름 뒤에 붙이며, 어떤 예외 사항이 발생할 수 있는지 알려주는 예약어
      • 여러 종류의 예외 사항을 적을 수 있음
    • throw
      • 메소드 내에서 실제로 예외 객체를 던질 때 사용
      • 예외 객체 앞에 불임
      • 일반 메소드의 return 키워드처럼 throw 아래 구문들은 실행되지 않으며, throw문과 함께 메소드가 종료됨
class OurClass {
    private final Boolean just = true;

	// throws를 사용하여 OurBadException이 발생할 수 있다는 것을 명시
    public void thisMethodIsDangerous() throws OurBadException {
        if (just) {
			// 예외가 발생했을 때, throw를 사용하여 예외 객체를 던짐
            throw new OurBadException();
        }
    }
}

예외 핸들링하기

메소드를 확인하여 위험하면(throws가 있다면) try-catch-(finally)를 사용하여 핸들링이 필요함

  • trycatch는 필수지만 finally는 생략 가능
  • try는 예외가 발생할 수 있지만 실행을 시도해야하는 코드를 담음
  • catch는 예외가 발생한 것을 잡아낸다면 어떻게 처리할 지에 대한 코드를 담으며, 여러 개 사용 가능
  • try의 코드를 실행하던 중 예외가 발생하면, 즉시 try의 실행을 멈추고 catch의 코드를 실행함
  • finally는 예외의 발생 유무와 상관없이 실행되어야 할 코드를 담음
public class StudyException {
    public static void main(String[] args) {
        OurClass ourClass = new OurClass();

        try {
            // 예외가 발생할 수 있지만 실행을 시도해야하는 코드
            ourClass.thisMethodIsDangerous();
        } catch (OurBadException e) {
            // 예외를 잡아낸 즉시 실행해야 하는 코드
            System.out.println(e.getMessage());
        } finally {
        	// 예외 발생 여부와 상관없이 항상 실행되어야 하는 코드
            System.out.println("우리는 방금 예외를 handling 했습니다!");
        }
    }
}

Throwable Class

  • 모든 객체의 원형인 Object 클래스에서 시작
  • 문제 상황을 뜻하는 Throwable 클래스가 Object 클래스를 상속
  • Throwable의 자식 클래스로 에러(Error)클래스와 예외(Exception) 클래스 존재
  • 에러(Error)클래스와 예외(Exception) 클래스는 각각 IOException 클래스, RuntimeException 클래스와 같이 구분하여 처리

Chained Exception (연결된 예외)

Chained Exception (연결된 예외)

  • 예외는 다른 예외를 유발할 수 있음
  • 예외 A가 예외 B를 발생시켰다면, 예외 A는 예외 B의 원인
  • 원인 예외를 새로운 예외에 등록 후, 새로운 예외를 발생시키는 것을 예외 연결이라 칭함
    • 여러 가지 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위함
    • checked exception을 unchecked exception으로 포장(wrapping)하는데 유용하게 사용
  • initCause()
    • 지정한 예외를 원인 예외로 등록
  • getCause()
    • 원인 예외를 반환
// 모든 예외 클래스는 존재한다고 가정
// 여러 가지 예외를 하나의 큰 분류의 예외로 묶기
public class Main {
    public static void main(String[] args) {
        try {
            install();
        } catch (InstallException e) {
            System.out.println("원인 예외 : " + e.getCause()); // 4. 원인 예외를 출력(SpaceException)
            e.printStackTrace(); // 5. 예외가 발생한 곳을 추적
        }
    }

    public static void install() throws InstallException {
        try {
            throw new SpaceException("설치할 공간이 부족합니다.");
        } catch (SpaceException e) {
            InstallException ie = new InstallException("설치중 예외발생"); // 1. InstallException 발생
            ie.initCause(e); // 2. InstallException의 원인으로 SpaceException 등록
            throw ie; // 3. 예외 객체를 던짐
        } catch (MemoryException e) {
            // ...
        }
    }
}
// 출력 예상
// 원인 예외 : SpaceException: 설치할 공간이 부족합니다.
// InstallException: 설치중 예외발생
// SpaceException: 설치할 공간이 부족합니다.

// 단순히 '설치 공간이 부족'이 아닌
// '설치 중 -> 설치 공간 부족'이기 때문에 어디서 예외가 발생했는지 추적에 용이

// 모든 예외 클래스는 존재한다고 가정
// checked exception을 unchecked exception으로 포장
public class Main {
    public static void main(String[] args) {
            install();
    }

    public static void install() {
        // 원래대로라면 IOException은 checked exception이기 때문에 예외처리가 필수이지만
        // unchecked exception인 RuntimeException으로 포장했기 때문에 예외처리를 하지 않아도 됨
        // 또한, public RuntimeException(Throwable cause) {} 이기 때문에
        // 예외가 발생했을 때 원인이 IOException인 것을 확인할 수 있음
        throw new RuntimeException(new IOException("설치할 공간이 부족합니다."));
    }
}

Generic(제네릭)

  • 타입 언어에서 중복되거나 필요 없는 코드를 줄여줌
  • 그러면서도 타입 안정성을 해치지 않음

제네릭 문법

  • 클래스 또는 메소드에 사용할 수 있으며, 클래스 이름 뒤의 <> 안에 들어가야 할 타입 변수 지정
    • 어떤 것을 사용해도 상관없지만, 암묵적으로 T, U, V, E를 사용
  • 선언해둔 타입 변수는 해당 클래스 내에서 특정한 타입이 들어갈 자리에 대신 들어감
// 예제에선 타입 변수로 T를 사용
public class Generic<T> {
	// 메소드의 데이터 타입, 매개변수의 데이터 타입 등에 사용
    private T t;
   
    public T get() {
        return this.t;
    }

    public void set(T t) {
        this.t = t;
    }

    public static void main(String[] args) {
    	// 인스턴스를 만들 때 타입 변수에 들어갈 실제 값 입력
       Generic<String> stringGeneric = new Generic<>();
        stringGeneric.set("Hello World");

        String tValueTurnOutWithString = stringGeneric.get();
 		System.out.println(tValueTurnOutWithString);
    }
}

  • 제네릭을 사용한 클래스는 제네릭 클래스라 부르며 원시 타입임
  • <> 사이에 들어가는 변수를 타입 변수라고 칭함
  • 제네릭 배열은 생성 불가
  • 객체의 static 멤버에 사용할 수 없음
    • 타입 변수는 인스턴스 변수로 간주되기 때문에 모든 객체에 동일하게 동작해야하는 static 필드에 사용 불가
static T get() { ... } // 에러
static void set(T t) { ... } // 에러
  • 다수의 타입 변수 사용 가능
public class Generic<T, U, E> {
    public E multiTypeMethod(T t, U u) { ... }
}
Generic<Long, Integer, String> instance = new Generic();
instance.multiTypeMethod(longVal, intVal);
  • 타입 변수에 지정할 수 있는 종류를 제한할 수 있음
// Car의 자손만 지정 가능
class sportCar<T extends Car> {
	ArrayList<T> list = new ArrayList<>();
}
// 인터페이스를 구현한 타입으로 지정할 때도 동일하게 extends 사용
interface Speed {}
class sportCar<T extends Speed> {}
// Car의 자손이면서 Speed 인터페이스도 구현해야한다면
// & 를 사용하여 연결
class sportCar<T extends Car & Speed> {}
  • 와일드카드를 통해 제네릭의 제한을 구체적으로 설정 가능
    - <? extends T>
    - T와 그 자손들만 가능
    - <? super T>
    - T와 그 조상들만 가능
    - <?>
    - 제한 없음
    - & 사용 불가

// 어떤 타입의 매개변수가 들어와도 매개 값을 출력
public static <T, U> void printStr(T text1, U text2){
    System.out.println(text1 + " " + text2);
}
  • 반환 타입 앞에 제네릭을 사용한 경우, 해당 메소드에서만 적용되는 제네릭 타입 변수를 선언할 수 있음
public class CheckOne<T> {
	// 제네릭 클래스 내부에 선언
	public static void method1(T t) { ... } // 컴파일 에러 발생
}
public class CheckTwo {
	// 일반 클래스 내부에 선언
	public static <T> void method2(T t) { ... }
}
  • CheckOne의 제네릭 타입 변수는 인스턴스가 생성될 때 결정이 되는데, static 메소드는 인스턴스 생성과 관계가 없음
  • 그렇게 때문에 CheckOne이 생성되는 시점에 제네릭 타입을 메소드가 매개변수로 받아낼 수 없기 때문에 컴파일 에러 발생

  • CheckTwo의 제네릭 타입 변수는 method2 에만 적용되기 때문에 호출되는 시점에 타입이 결정되어 컴파일 에러가 발생하지 않음
  • 그렇기 때문에 static 메소드지만 제네릭을 사용할 수 있음
public class CheckThree<T> {
	public static <T> void method3(T t) { ... }
}
  • CheckThreemethod3에서 같은 이름의 타입 변수를 사용했지만 다른 변수
  • CheckThree에 사용된 타입변수는 인스턴스가 생성될 때마다 지정되기 때문에 인스턴스 변수로 간주되고, method3에 사용된 타입변수는 지역 변수로 간주

0개의 댓글

관련 채용 정보