본문 바로가기

coding/Java

[Java] 예외처리와 제네릭

예외처리(Exception)과 제네릭(Generics)에 대해 알아보자.

 

(1) 예외처리

자바에서 오동작이나 결과에 악영향을 미칠 수 있는 실행 중 발생한 오류를 예외처리라고 한다. 자주 발생하는 예외에 대한 정보는 다음과 같다.

 

 

자바는 예외처리를 위해 try-catch-finally문을 사용한다. 사용법은 다음과 같으며, finally 블록은 생략 가능하다.

try {

	//예외가 발생할 가능성이 있는 실행문
    
}

catch (처리할 예외 타입 선언) {

	//예외 처리문
    
}

finally {

	//예외 발생 여부와 상관없이 무조건 실행되는 문장
    
}

 

예외처리의 최상위 클래스는 java.lang.Throwable 클래스이다. 이 클래스에 속해있는 메소드 두 개만 살펴보자면

  • public String getMessage() : 예외의 원인을 담고 있는 문자열을 반환
  • public void printStackTrace() : 예외가 발생한 위치와 호출된 메소드의 정보를 출력
import java.util.*; //이거 꼭 import 해줘야 한다

public class FinalTermTest {
	public static void md1(int n) {
		md2(n, 0);
	}
	
	public static void md2(int n1, int n2) {
		int r = n1 / n2;
	}
	
	public static void main(String[] args) {
		try {
			md1(3);
		}
		catch(ArithmeticException | InputMismatchException e) {
			e.getMessage();
			e.printStackTrace();
		}
		System.out.println("프로그램 종료");
	}
}

예외가 발생한 위치의 순서는 md2 -> md1 -> main 임을 알 수 있다.

 

(2) 제네릭

'타입 매개 변수'를 지칭함. 자바에서 클래스 코드를 찍어내듯이 생산할 수 있도록 일반화시키는 도구이다. 'E', 'T', 'V'와 같이 하나의 대문자로 사용되며, 구체적인 타입을 지정하면 지정된 타입만 다룰 수 있는 구체화 된 클래스를 만들 수 있다. 가령 Vector<E>에서 E 대신 Integer와 같이 구체적인 타입을 지정하면, Vector<Integer>는 정수 값만 저장하는 벡터로, Vector<String>은 문자열만 저장하는 벡터로 사용할 수 있다. 특정 타입만 다루지 않고 여러 종류의 타입으로 변신할 수 있도록, 일반화시키기 위해 <E>를 사용하는 것이다. 여기서의 E를 '제네릭 타입'이라고 부른다.

관례적으로는 다음과 같이 사용한다.

  • E : Element를 의미함(컬렉션의 요소)
  • T : Type을 의미
  • V : Value를 의미
  • K : Key를 의미

컬렉션의 요소는 객체들만 가능하다. 무슨 말이냐 하면, int, char, double 같은 기본 타입의 데이터는 원칙적으로 컬렉션의 요소로 불가능하기 때문에, 제네릭 타입도 기본 타입으로는 선언 및 생성 불가능하다.

Vector<int> v = new Vector<int>();  (X)
Vector<Integer> v = new Vector<Integer>();  (O)

 

다음과 같은 예시를 보자. Apple과 Orange라는 클래스가 있고, Box 클래스는 말그대로 상자처럼 무엇이든 저장하고 꺼낼 수 있는, set(), get() 메소드를 이용한 클래스이다. 

두 가지 문제 상황이 발생할 수 있다. 첫 째, 각 과일을 레퍼런스 타입으로 Box를 통해 받아오고 싶으면 형변환을 해야 한다. 둘 째, 사용자가 set() 메소드에 인스턴스가 아닌 문자열을 넣어버린다면? 아니면 정수를 넣어버린다면? 우리는 문자열이나 숫자가 아닌, 사과와 오렌지의 인스턴스(객체)를 상자에 넣었다 빼고 싶다. 

import java.util.*;

class Apple {
	public String toString() { return "I am an apple."; }
}

class Orange {
	public String toString() { return "I am an orange."; }
}

class Box {
	private Object ob;
	
	public void set(Object o) { ob = o; }
	
	public Object get() { return ob; }
}

public class FinalTermTest {
	public static void main(String[] args) {
		Box aBox = new Box();
		Box bBox = new Box();
		
		aBox.set("Apple"); // 이게 아니라 aBox.set(new Apple());
		bBox.set("Orange"); // 이게 아니라 bBox.set(new Orange());
		
		Apple ap = (Apple)aBox.get(); // 형변환 해야됨
		Orange og = (Orange)bBox.get(); // 형변환 해야됨
	}
}

 

제네릭 기반의 클래스를 정의하고 인스턴스를 생성하면 개선된 결과를 얻을 수 있다. 형변환을 할 필요가 없을 뿐더러, 사용자가 인스턴스가 아닌 다른 자료형을 넣으면 컴파일러가 오류를 발생시켜준다.

인스턴스 생성시 결정되는 자료형의 정보를 Object에서 T로 대체해보았다.

import java.util.*;

class Apple {
	public String toString() { return "I am an apple."; }
}

class Orange {
	public String toString() { return "I am an orange."; }
}

class Box<T> {
	private T ob;
	
	public void set(T o) { ob = o; }
	
	public T get() { return ob; }
}

public class FinalTermTest {
	public static void main(String[] args) {
		Box<Apple> aBox = new Box<>(); //원래는 뒤의 다이아몬드 기호 안에도 Apple을 넣어줘야 하지만 빼도 상관X
		Box<Orange> bBox = new Box<>(); //여기도!
		
		//aBox.set("Apple");
		//bBox.set("Orange");
		//error: incompatible types: String cannot be converted to (Apple 혹은 Orange)
		
		Apple ap = aBox.get(); // 형변환 하지 않음
		Orange og = bBox.get(); // 형변환 하지 않음
	}
}