API의 생산자로서 소비자들에게 API를 제공하는 입장에 서보자.
class Calculator{
int left, right;
public void setOprands(int left, int right){
this.left = left;
this.right = right;
}
public void divide(){
try {
System.out.print("계산결과는 ");
System.out.print(this.left/this.right);
System.out.print(" 입니다.");
} catch(Exception e){
System.out.println("\n\ne.getMessage()\n"+e.getMessage());
System.out.println("\n\ne.toString()\n"+e.toString());
System.out.println("\n\ne.printStackTrace()");
e.printStackTrace();
}
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 0);
c1.divide();
}
}
에러가 발생
0으로 나누려고 했기 때문
우리가 할 수 있는 조치는 두가지다.
- setOprands의 두번째 인자로 0이 들어오면 예외를 발생시킨다.
- 메소드 divide를 실행할 때 right의 값이 0이라면 예외를 발생시킨다.
첫번째 방법
class Calculator{
int left, right;
public void setOprands(int left, int right){
if(right == 0){
throw new IllegalArgumentException("두번째 인자의 값은 0이 될 수 없습니다.");
}
this.left = left;
this.right = right;
}
public void divide(){
try {
System.out.print("계산결과는 ");
System.out.print(this.left/this.right);
System.out.print(" 입니다.");
} catch(Exception e){
System.out.println("\n\ne.getMessage()\n"+e.getMessage());
System.out.println("\n\ne.toString()\n"+e.toString());
System.out.println("\n\ne.printStackTrace()");
e.printStackTrace();
}
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 0);
c1.divide();
}
}
두번째 방법
class Calculator{
int left, right;
public void setOprands(int left, int right){
this.left = left;
this.right = right;
}
public void divide(){
if(this.right == 0){
throw new ArithmeticException("0으로 나누는 것은 허용되지 않습니다.");
}
try {
System.out.print("계산결과는 ");
System.out.print(this.left/this.right);
System.out.print(" 입니다.");
} catch(Exception e){
System.out.println("\n\ne.getMessage()\n"+e.getMessage());
System.out.println("\n\ne.toString()\n"+e.toString());
System.out.println("\n\ne.printStackTrace()");
e.printStackTrace();
}
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 0);
c1.divide();
}
}
throw는 예외를 발생시키는 명령이다.
throw 뒤에는 예외 정보를 가지고 있는 예외 클래스가 위치
예외 | 사용해야 할 상황 |
IllegalArgumentException | 매개변수가 의도하지 않은 상황을 유발시킬 때 |
IllegalStateException | 메소드를 호출하기 위한 상태가 아닐 때 |
NullPointerException | 매개 변수 값이 null 일 때 |
IndexOutOfBoundsException | 인덱스 매개 변수 값이 범위를 벗어날 때 |
ArithmeticException | 산술적인 연산에 오류가 있을 때 |
예외의 여러가지 상황들
ex )
class E{
void throwArithmeticException(){
throw new ArithmeticException();
}
}
문제없이 컴파일 될 것이다
ex 2)
import java.io.IOException;
class E{
void throwArithmeticException(){
throw new ArithmeticException();
}
void throwIOException(){
throw new IOException();
}
}
컴파일 되지 않는다.
이유 : IOException은 try...catch하거나 throw 해야 한다는 뜻
ArithmeticException, IOException 모두 Exception이다. 그럼에도 불구하고 유독 IOException만 try...catch 혹은 throw 를 해야 한다는 뜻이다. 자바는 이 두개의 예외를 다른 방법으로 대하고 있는 것
해결 방법
import java.io.IOException;
class E{
void throwArithmeticException(){
throw new ArithmeticException();
}
void throwIOException1(){ // 1번째 방법
try {
throw new IOException();
} catch (IOException e) {
e.printStackTrace();
}
}
void throwIOException2() throws IOException{ // 2번째 방법
throw new IOException();
}
}
Exception 클래스가 더 많은 예외 상황을 포괄하는 것이고 ArithmeticException 클래스는 더 구체적인 상황을 특정한다.
getMessage, printStackTrace, toString이 Throwable 클래스에서 정의 되어 있다.
예외로 '던질 수 있는' 클래스는 반드시 Throwable 클래스를 상속 받아야 한다.
예외의 종류
- Throwable
- Error
- Exception
- Runtime Exception
Throwable
클래스 Throwable은 범 예외 클래스들의 공통된 조상이다. 모든 예외 클래스들이 가지고 있는
공통된 메소드를 정의하고 있다
Error
에러는 여러분의 애플리케이션의 문제가 아니라 그 애플리케이션이 동작하는 가상머신에
문제가 생겼을 때 발생하는 예외다.
Exception
- checked 예외 - RuntimeException을 제외한 Exception의 하위 클래스
- unchecked 예외 - RuntimeException의 하위 클래스
checked 예외는 반드시 예외처리를 해야 하는 되는 것이고, unchekced는 해도 되고 안 해도 되는 예외다.
바로 이 지점이 IOException과 ArithmeticException의 차이점이다.
- ArithmeticException의 부모 중에 RuntimeException이 있다. 반면에 IOException은 Exception의 자식이지만 RuntimeException의 자식은 아니다
- IOException은 checked이고 ArithmeticException은 unchekced이다. (Error도 unchecked이다)
나만의 예외 만들기
예외를 만들기 전에 해야 할 것은 자신의 예외를 checked로 할 것인가? unchecked로 할 것인가를 정해야 한다.
API 쪽에서 예외를 던졌을 때 API 사용자 쪽에서 예외 상황을 복구 할 수 있다면 checked 예외를 사용한다.
사용자가 API의 사용방법을 어겨서 발생하는 문제거나 예외 상황이 이미 발생한 시점에서 그냥 프로그램을 종료하는 것이 덜 위험 할 때 unchecked를 사용
class DivideException extends RuntimeException {
DivideException(){
super();
}
DivideException(String message){
super(message);
}
}
class Calculator{
int left, right;
public void setOprands(int left, int right){
this.left = left;
this.right = right;
}
public void divide(){
if(this.right == 0){
throw new DivideException("0으로 나누는 것은 허용되지 않습니다.");
}
System.out.print(this.left/this.right);
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 0);
c1.divide();
}
}
만약 DivideException을 Exception으로 바꿀려면 어떻게 될까?
답 : RuntimeException을 Exception으로 변경하면 된다.
class DivideException extends Exception {
하지만 컴파일 에러가 난다.
이를 해결하려면 2가지의 방법이 있다.
하나는 예외처리를 하는 것이다
class DivideException extends Exception {
DivideException(){
super();
}
DivideException(String message){
super(message);
}
}
class Calculator{
int left, right;
public void setOprands(int left, int right){
this.left = left;
this.right = right;
}
public void divide(){
if(this.right == 0){
try {
throw new DivideException("0으로 나누는 것은 허용되지 않습니다.");
} catch (DivideException e) {
e.printStackTrace();
}
}
System.out.print(this.left/this.right);
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 0);
c1.divide();
}
}
2번째 방법은 사용자에게 예외를 던진다. 사용자는 반드시 예외에 대한 처리를 해야 한다.
class DivideException extends Exception {
DivideException(){
super();
}
DivideException(String message){
super(message);
}
}
class Calculator{
int left, right;
public void setOprands(int left, int right){
this.left = left;
this.right = right;
}
public void divide() throws DivideException{
if(this.right == 0){
throw new DivideException("0으로 나누는 것은 허용되지 않습니다.");
}
System.out.print(this.left/this.right);
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 0);
try {
c1.divide();
} catch (DivideException e) {
e.printStackTrace();
}
}
}