Skip to content

Latest commit

 

History

History
949 lines (816 loc) · 28.1 KB

220306.md

File metadata and controls

949 lines (816 loc) · 28.1 KB

참조변수의 형변화

사용할 수 있는 멤버의 갯수를 조절하는 것, 그 외에 값 같은 것은 바뀌지 않는다.
조상 자손 관계의 참조변수는 서로 형변환 가능

class Car{}
class FireEngine extends Car {}
class Ambulance extends Car {}

// Car과 FireEngine, Car과 Ambulance로는 서로 형변환이 가능하지만
// FireEngine과 Ambulance는 서로 형변환을 할 수 없다.
FireEngine f = new FireEngine();

Car c = (Car)f;
FireEngine f2 = (FireEngine)c;
Ambulance a = (Ambulance)f;     // 에러

감소하는 형변환은 안전하고, 증가하는 형변환은 안전하지 않다.

instanceof 연산자

참조변수의 형변환 가능 여부를 확인하는데 사용하고, 가능하면 true를 반환한다.
형변환 전에 반드시 instanceof로 확인해야 한다.

void doWork(Car c) {
    if (c instanceof FireEngine) {      // 형변환이 가능한지 확인
        FireEngine fe = (FireEngine)c;  // 형변환
        fe.water();
    }
}

매개변수의 다형성

참조형 매개변수는 메소드 호출시, 자신과 같은 타입 또는 자손타입의 인스턴스를 넘겨줄 수 있다.

class Product {
    int price;
    int bonusPoint;
}
class Tv extends Product {}
class Computer extends Product {}
class Audio extends Product {}

class Buyer {
    int money = 1000;
    int bonusPoint = 0;

    void buy(Product p) {
        money -= p.price;
        bonusPoint += p.bonusPoint;
    }
}

다형성 덕분에 조상 클래스인 Product를 넘겨줄 수 있었다. 만약 이게 안된다면 계속 물품마다 오버로딩을 해줬어야 했을 것이다.
참조변수와 문자열을 결합할 때는 참조변수의 toString()을 호출한다. 그래서 toString()을 오버라이딩하면 물품마다 나오는 문자열을 다르게 할 수 있다.

package ch02;

class Product {
	int price;
	int bonusPoint;
	
	Product(int price) {
		this.price = price;
		bonusPoint = (int)(price/10.0);
	}
}

class Tv extends Product {
	Tv() { super(100); }
	
	public String toString() { return "Tv"; }
}

class Computer extends Product {
	Computer() { super(200); }
	
	public String toString() { return "Computer"; }
}

class Buyer {
	int money = 1000;
	int bonusPoint = 0;
	
	void buy(Product p) {
		if (money < p.price) {
			System.out.println("잔액이 부족합니다.");
			return;
		}
		
		money -= p.price;
		bonusPoint += p.bonusPoint;
		System.out.println(p + "을/를 구입하셨습니다.");
	}
}

public class EX3 {
	public static void main(String[] args) {
		Buyer b = new Buyer();
		
		b.buy(new Tv());
		b.buy(new Computer());
		
		System.out.println("현재 남은 돈은 " + b.money + "만원입니다.");
		System.out.println("현재 보너스점수는 " + b.bonusPoint + "점입니다.");
	}
}

Tv을/를 구입하셨습니다.
Computer을/를 구입하셨습니다.
현재 남은 돈은 700만원입니다.
현재 보너스점수는 30점입니다.

여러 종류의 객체를 배열로 다루기

조상타입의 배열에 자손들의 객체를 담을 수 있다.

Product p[] = new Product[3];
p[0] = new Tv();
p[1] = new Computer();
p[2] = new Audio();

Vector 클래스(가변 배열 기능)는 Object 배열을 가지고 있어 모든 종류의 객체 저장이 가능하다.

...
class Buyer {
	int money = 1000;
	int bonusPoint = 0;
	int i = 0;
	Product[] cart = new Product[10];
	
	void buy(Product p) {
		if (money < p.price) {
			System.out.println("잔액이 부족합니다.");
			return;
		}
		
		money -= p.price;
		bonusPoint += p.bonusPoint;
		cart[i++] = p;
		System.out.println(p + "을/를 구입하셨습니다.");
	}
	
	void summary() {
		int sum = 0;
		String itemList = "";
		
		for (int i=0; i<cart.length; i++) {
			if (cart[i]==null) break;
			sum += cart[i].price;
			itemList += cart[i] + ", ";
		}
		System.out.println("구입하신 물품의 총금액은 " + sum + "만원입니다.");
		System.out.println("구입하신 제품은 " + itemList + "입니다.");
	}
}
...

구입하신 물품의 총금액은 300만원입니다.
구입하신 제품은 Tv, Computer, 입니다.

추상 클래스(abstract class)

미완성 설계도. 미완성 메소드를 갖고 있는 클래스

abstract class Player {
    abstract void play(int pos);
    abstract void stop();
}

다른 클래스 작성에 도움을 주기 위한 것. 인스턴스 생성 불가
상속을 통해 추상 메소드를 완성해야 인스턴스 생성가능

class AudioPlayer extends Player {
    void play(int post) {/*내용*/}
    void stop() {/*내용*/}
}

// 둘 다 가능
AudioPlayer ap = new AudioPlayer();
Player p = new AudioPlayer();

추상 메소드(abstract method)

미완성 메소드. 구현부가 없는 메소드

abstract 리턴타입 메소드이름();

꼭 필요하지만 자손마다 다르게 구현될 것으로 예상되는 경우에 사용한다.

abstract class Player {
    abstract void play(int pos);
    abstract void stop();
}

class AudioPlayer extends Player {
    void play(int pos) {/*내용*/}
    void stop() {/*내용*/}
}

abstract class AbstractPlayer extends Player {
    void play(int pos) {/*내용*/}
}

추상 클래스를 상속 받아도 일부만 구현해도 되는데 이 경우 abstract 키워드를 붙여줘야 한다. 추상 메소드를 모두 구현해야 객체를 생성할 수 있다.

추상 메소드 호출이 가능(호출할 때는 선언부만 필요)

abstract class Player {
    boolean pause;
    int currentPos;

    Player() {
        pause = false;
        currentPos = 0;
    }

    abstract void play(int pos);
    abstract void stop();

    void play() {
        play(currentPos);   // 추상 메소드 사용 가능
    }
}

추상 클래스의 작성

여러 클래스에 공통적으로 사용될 수 있는 추상 클래스를 바로 작성하거나 기존 클래스의 공통 부분을 뽑아서 추상 클래스를 만든다.

// 원본
class Marine {
    int x, y;
    void move(int x, int y) {}
    void stop() {}
    void stimPack() {}
}

class Tank {
    int x, y;
    void move(int x, int y) {}
    void stop() {}
    void changeMode() {}
}

// 공통 부분을 뽑아 추상클래스로 변환
abstract class Unit {
    int x, y;
    abstract void move(int x, int y);
    void stop() {}
}

class Marine extends Unit {
    void move(int x, int y) {}
    void stimPack() {}
}

class Tank extends Unit {
    void move(int x, int y) {}
    void changeMode() {}
}

중복 제거에 효과적이고, 배열로 관리하기도 편해진다.

추상화된 코드는 구체화된 코드보다 유연하다. 즉, 변경에 유리하다.

인터페이스(interface)

인터페이스는 추상 메소드의 집합이다.
구현된 것이 전혀 없는 설계도. 모든 멤버가 public

interface 인터페이스이름 {
    public static final 타입 상수이름 = 값;
    public abstract 메소드이름(매개변수목록);
}

인터페이스는 변수는 가질 수 없다.
인터페이스 내부의 상수는 항상 public, static, final이 적용되어 생략할 수 있고, 메소드도 항상 public, abstract가 적용되어 생략할 수 있다.

interface PlayingCard {
    public static final int SPADE = 4;
    int CLOVER = 1; // public static final 생략

    public abstract String getCardNumber();
    String getCardKind(); // public abstract 생략
}

인터페이스의 상속

인터페이스의 조상은 인터페이스만 가능(Object가 최고 조상 아님)
다중 상속이 가능.(추상 메소드는 충돌해도 문제 없음)

interface Fightable extends Movable, Attackable {}

interface Movable {
    void move(int x, int y);
}

interface Attackable {
    void attack(Unit u);
}

인터페이스의 구현

인터페이스에 정의된 추상 메소드를 완성하는 것

class 클래스이름 implements 인터페이스이름 {
    // 인터페이스에 정의된 추상 메소드를 모두 구현해야 한다.
}

일부만 구현하는 경우, 클래스 앞에 abstract를 붙여야 함

인터페이스를 이용한 다형성

인터페이스 타입 매개변수는 인터페이스 구현한 클래스의 객체만 가능

interface Fightable {
    void move(int x, int y);
    void attack(Fightable f); // Fightable 인터페이스를 구현한 클래스의 인스턴스만 가능
}
class Fighter extends Unit implements Fightable {
    public void move(int x, int y) { /*내용*/ }
    public void attack(Fightable f) { /*내용*/ }
}

Unit u = new Fighter();
Fightable f = new Fighter(); // 이러면 Fightable에 선언된 메소드만 사용 가능

인터페이스를 메소드의 리턴타입으로 지정할 수 있다.

Fightable method() { // Fightable 인터페이스를 구현한 클래스의 인스턴스를 반환
    ...
    Fighter f = new Fighter();
    return f;
    // 두 줄을 합쳐서 return new Fighter();도 가능하다.
}

메소드의 반환 타입이 인터페이스이면 인터페이스를 구현한 객체를 반환해야 한다. 호출한 쪽에서는 반환 타입과 일치하거나 자동 형변환이 가능한 타입의 변수에 결과를 받는다.

package ch02;

abstract class Unit {
	int x, y;
	abstract void move(int x, int y);
	void stop() { System.out.println("멈춤"); }
}

interface Fightable {
	void move(int x, int y);
	void attack(Fightable f);
}

class Fighter extends Unit implements Fightable{
	// 오버라이딩 규칙 : 조상보다 접근제어자가 범위가 좁으면 안된다.
	public void move(int x, int y) {
		System.out.println("["+x+","+y+"]로 이동");
	}
	public void attack(Fightable f) {
		System.out.println(f+"를 공격");
	}
}

public class EX3 {
	public static void main(String[] args) {
		// Fighter f = new Fighter();
		Fightable f = new Fighter();
		f.move(100, 100);
		Fighter f2 = new Fighter();
		f.attack(f2);
	}
}

[100,100]로 이동
ch02.Fighter@2f92e0f4를 공격

Fightable이 생략되어 있지만 public이기 때문에 Fighter는 public을 적어줘야 한다.

class Fighter extends Unit implements Fightable{
	// 오버라이딩 규칙 : 조상보다 접근제어자가 범위가 좁으면 안된다.
	public void move(int x, int y) {
		System.out.println("["+x+","+y+"]로 이동");
	}
	public void attack(Fightable f) {
		System.out.println(f+"를 공격");
	}
	
	Fightable getFightable(){
		Fighter f = new Fighter();
		return f; // (Fightable)이 생략된 것
	}
}

public class EX3 {
	public static void main(String[] args) {
		Fighter f = new Fighter();
		Fightable f2 = f.getFightable();
		f.attack(f2);
	}
}

위에서 f2의 타입과 getFightable()의 타입은 일치해야 하고, getFightable()의 타입과 반환값의 타입도 일치해야 한다.

인터페이스의 장점

두 대상(객체) 간의 '연결, 대화, 소통'을 '중간 역할'을 한다. (Ex. GUI)
선언(설계)와 구현을 분리시킬 수 있게 한다.
A가 B를 사용하는 관계의 중간에 인터페이스를 넣으면 B가 변경되어도 A는 바꾸지 않아도 된다. (느슨한 결합)

// 직접적인 관계 (A - B)
class A {
    public void methodA(B b) {
        b.methodB();
    }
}

class B {
    public void methodB() {
        System.out.println("methodB()");
    }
}

class InterfaceTest {
    public static void main(String args[]) {
        A a = new A();
        a.methodA(new B());
    }
}

// 간접적인 관계 (A - I - B)
class A {
    public void methodA(I i) {
        i.methodB();
    }
}

interface I { void methodB(); }

class B implements I {
    public void methodB() {
        System.out.println("methodB()");
    }
}

간접적인 관계의 경우 추후에 B를 C로 변경할 때 A는 변경할 필요는 없다.

개발 시간을 단축할 수 있다.
앞의 경우에서 B가 완성이 되지 않았어도 I를 사용하면 되서 시간을 단축할 수 있다.

변경에 유리한 유연한 설계가 가능하다.

표준화가 가능하다. (JDBC)
JDBC는 Java 표준 인터페이스 집합이다.

서로 관계없는 클래스들을 관계를 맺어줄 수 있다.
상속 계층도

  • Unit
    • GroundUnit
      • Marine
      • SCV
      • Tank
    • AirUnit
      • Dropship

위와 같은 구조에서 수리하는 메소드를 만들려는데 SCV, Tank, Dropship만 수리가 가능하다. 그런데 GroundUnit으로 묶자니 Marine이 들어가고, 오버로딩을 하자니 중복이 심하다.
이럴 때 interface를 이용하는 것이다.

interface Repairable {} // 아무일도 안함
class SCV extends GroundUnit implements Repairable {
    ...
}
class Tank extends GroundUnit implements Repairable {
    ...
}
class Dropship extends AirUnit implements Repairable {
    ...
}

이러면 SCV, Tank, Dropship은 Repairable을 구현했다는 공통점을 가지게 된다.

void repair(Repairable r) {
    if (r instanceof Unit) {
        Unit u = (Unit)r;
        while (u.hitPoint != u.MAX_HP) {
            u.hitPoint++;
        }
    }
}

이렇게 구현이 가능해진다.

디폴트 메소드와 static 메소드

인터페이스에 디폴트 메소드, static 메소드 추가 가능(JDK1.8 부터)

인터페이스에 새로운 메소드(추상 메소드)를 추가하기 어려움.
인터페이스에 추상 메소드를 추가하면 기존에 이 인터페이스를 구현했던 클래스들은 모두 이 메소드를 구현해줘야 하기 때문이다.
해결책으로 디폴트 메소드가 나왔다.

디폴트 메소드는 인스턴스 메소드(인터페이스 원칙 위반)

디폴트 메소드가 기존의 메소드와 충돌할 때의 해결책

  1. 여러 인터페이스의 디폴트 메소드 간의 충돌
    • 인터페이스를 구현한 클래스에서 디폴트 메소드를 오버라이딩해야 한다.
  2. 디폴트 메소드와 조상 클래스의 메소드 간의 충돌
    • 조상 클래스의 메소드가 상속되고, 디폴트 메소드는 무시된다.

내부 클래스(inner class)

클래스 안의 클래스

class A { // 외부 클래스
    ...
    class B { // 내부 클래스
        ...
    }
    ...
}

내부 클래스의 장점

  • 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
    내부에 있으면 B에서 객체 생성 없이도 A의 멤버 접근이 가능하다.
  • 코드의 복잡성을 줄일 수 있다.(캡슐화)
    A 안에서만 B를 사용하면 굳이 B를 밖에 둘 이유가 없다.
package ch02;

class AAA {
	int i = 100;
	BBB b = new BBB();
	
	class BBB {
		void method() {
			System.out.println(i); // 객체 생성 없이 외부 클래스의 멤버 접근 가능
		}
	}
}

public class EX3 {
	public static void main(String[] args) {
		AAA a = new AAA();
		a.b.method();
	}
}

내부 클래스의 종류와 특징

내부 클래스의 종류와 유효범위(scope)는 변수와 동일

내부 클래스 특징
인스턴스 클래스
(instance class)
외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 인스턴스 멤버처럼 다뤄진다. 주로 외부 클래스의 인스턴스 멤버들과 관련된 작업에 사용될 목적으로 선언된다.
스태틱 클래스
(static class)
외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 static 멤버처럼 다뤄진다. 주로 외부 클래스의 static 멤버, 특히 static 메소드에서 사용될 목적으로 선언된다.
지역 클래스
(local class)
외부 클래스의 메소드나 초기화블럭 안에 선언하며, 선언된 영역 내부에서만 사용될 수 있다.
익명 클래스
(anonymous class)
클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스(일회용)

인스턴스 변수와 인스턴스 클래스, 스태틱 변수와 스태틱 클래스, 지역 변수와 지역 클래스는 특징이 같다.

내부 클래스의 제어자와 접근성

내부 클래스의 제어자는 변수에 사용 가능한 제어자와 동일하게 4종 모두 사용할 수 있다.

package ch02;

public class EX3 {
	class InstanceInner {
        int iv = 100;
        static int cv = 100; // 에러, static 변수를 선언할 수 없다.
        final static int CONST = 100; // final static은 상수이므로 허용
    }

    static class StaticInner {
        int iv = 200;
        static int cv = 200; // static 클래스만 static 멤버를 정의할 수 있다.
    }

    void myMethod() {
        class LocalInner {
            int iv = 300;
            static int cv = 300; // 에러, static 변수를 선언할 수 없다.
            final static int CONST = 300; // final static은 상수이므로 허용
        }
    }
    
	public static void main(String[] args) {
		System.out.println(InstanceInner.CONST);
		System.out.println(StaticInner.cv);
//  	System.out.println(LocalInner.CONST); // 에러, 지역 내부 클래스는 메소드 내에서만 사용 가능
	}
}

100
200 내부 클래스에서 스태틱 변수가 필요하다면 내부 클래스도 스태틱으로 선언하면 된다.

스태틱 멤버는 인스턴스 멤버에 직접 접근할 수 없다. 즉, 스태틱 메소드에서 인스턴스 내부 클래스 객체 생성 같은 것을 할 수 없다는 것이다.

내부클래스에서 외부 클래스의 private 멤버도 접근 가능하다.

JDK1.8부터 값이 바뀌지 않는 변수는 상수로 간주한다. 하지만 final을 붙이는 것이 이해하기 편할 것이다.

외부 클래스의 객체를 먼저 생성해야 인스턴스 내부 클래스의 인스턴스를 생성 가능하다.
스태틱 내부 클래스의 인스턴스는 외부 클래스를 먼저 생성하지 않아도 된다. 또한 스태틱 내부 클래스의 스태틱 변수는 외부 클래스 객체 생성 없이도 사용 가능하다.

class Outer1 {
    int value = 10; // Outer.this.value

    class Inner {
        int value = 20; // this.value

        void method1() {
            int value = 30;
            System.out.println(value);
            System.out.println(this.value);
            System.out.println(Outer1.this.value);
        }
    }
}

익명 클래스(anonymous class)

이름이 없는 일회용 클래스. 정의와 생성을 동시에

new 조상클래스이름() {
    // 멤버 선언
}

// 또는

new 구현인터페이스이름() {
    // 멤버 선언
}
import java.awt.*;
import java.awt.event.*;

class Ex3 {
    public static void main(String[] args) {
        Button b = new Button("Start");
        b.addActionListener(new EventHandler());
    }
}

class EventHandler implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        System.out.println("ActionEvent occurred");
    }
}

익명 클래스를 사용하면 다음과 같이 바꿀 수 있다.

import java.awt.*;
import java.awt.event.*;

class Ex3 {
    public static void main(String[] args) {
        Button b = new Button("Start");
        b.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                System.out.println("ActionEvent occurred");
            }
        });
    }
}

프로그램 오류

  • 컴파일 에러(compile-time error) : 컴파일 할 때 발생하는 에러
  • 런타임 에러(runtime error) : 실행 할 때 발생하는 엘러
  • 논리적 에러(logical error) : 작성 의도와 다르게 동작

자바 컴파일러

  • 구문체크
  • 번역
  • 최적화
  • 생략된 코드 추가

자바의 런타임 에러

  • 에러(error) : 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
  • 예외(exception) : 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류

에러는 어쩔 수 없지만, 예외는 처리하자.

예외처리의 정의와 목적

  • 정의 : 프로그램 실행 시 발생할 수 있는 예외의 발생에 대비한 코드를 작성하는 것
  • 목적 : 프로그램의 비정상 종료를 막고, 정상적인 실행상태를 유지하는 것

예외 클래스의 계층 구조
Object

  • Throwable (클래스, 모든 오류의 조상)
    • Exception
      • RuntimeException
        • NullPointerException
        • ArithmeticException
        • ...
      • IOException
      • ...
    • Error
      • OutOfMemoryError
      • ...

Exception 클래스와 자손 클래스들 : 사용자의 실수와 같은 외적인 요인에 의해 발생하는 예외
RuntimeException 클래스와 자손 클래스들 : 프로그래머의 실수로 발생하는 예외

예외 처리하기. try-catch 문

try {
    // 예외가 발생할 가능성이 있는 문장들
} catch (Exception1 e1) {
    // Exception1이 발생했을 경우, 이를 처리하기 위한 문장
} catch (ExceptionN eN) {
    // ExceptionN이 발생했을 경우, 이를 처리하기 위한 문장
}

try-catch 문의 {}는 생략할 수 없다.

try-catch 문에서의 흐름

  1. try 블럭 내에서 예외가 발생한 경우
    1. 발생한 예외와 일치하는 catch 블럭이 있는지 확인
    2. 일치하는 catch 블럭을 찾으면, 그 catch 블럭 내의 문장들을 수행하고 전체 try-catch문을 빠져나가서 그 다음 문장을 계속해서 수행한다. 만일 일치하는 catch 블럭을 찾지 못하면, 예외는 처리되지 못한다.
  2. try 블럭 내에서 예외가 발생하지 않은 경우
    1. catch 블럭을 거치지 않고 전체 try-catch 문을 빠져나가서 수행을 계속한다.

Exception은 모든 예외의 최고 조상이라 모든 예외 처리가 가능하다.
그래서 이 아래 있는 catch 블럭은 의미가 없으므로 제일 마지막 catch 블럭에 사용하는 것이 일반적이다.

try{
    ...
} catch (ArithmeticException ae) {
    ...
} catch (Exception e) { // ArithmeticException을 제외한 모든 예외 처리
    ...
}

printStackTrace()와 getMessage()

printStackTrace() : 예외발생 당시의 호출스택에 있었던 메소드의 정보와 예외 메세지를 화면에 출력한다.
getMessage() : 발생한 예외 클래스의 인스턴스에 저장된 메세지를 얻을 수 있다.

예외가 발생하면 예외 객체가 생성된다.
그 후에 catch 블럭에서 참조변수에 객체의 주소가 대입된다.

멀티 catch 블럭

내용이 같은 catch 블럭을 하나로 합친 것

// 내용이 같은 catch 블럭이 있어서 중복이 발생
try {
    ...
} catch (ExceptionA e) {
    e.printStackTrace();
} catch (ExceptionB e2) {
    e.printStackTrace();
}
// 내용이 같은 catch 블럭을 하나로 합침
try {
    ...
} catch (ExceptionA | ExceptionB e) {
    e.printStackTrace();
}

멀티 catch 블럭을 사용할 때 부모 자식 관계는 사용하면 안된다.
이럴 때는 그냥 부모 예외 클래스만 적는 것이 낫다.

try {
    ...
} catch (ExceptionA | ExceptionB e) {
    e.methodA(); // 에러 ExceptionA에 선언된 methodA()는 호출 불가

    if (e instanceof ExceptionA) {
        ExceptionA e1 = (ExceptionA)e;
        e1.methodA(); // 가능
    }
}

멀티 catch 블럭에서는 공통 멤버만 사용 가능하다. 공통 멤버가 아닌 것을 사용하려면 instanceof로 체크 후에 형변환해서 호출해야 한다.

예외 발생시키기

순서

  1. 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든다.
Exception e = new Exception("고의로 발생시킴");
  1. 키워드 throw를 이용해서 예외를 발생시킨다.
throw e;

위의 두 줄을 한 줄로 줄이는 것도 가능

throw new Exception("고의로 발생시킴");

checked 예외, unchecked 예외

  • checked 예외 : 컴파일러가 예외 처리 여부를 체크(예외 처리 필수)
  • unchecked 예외 : 컴파일러가 예외 처리 여부를 체크 안함(예외 처리 선택)

checked 예외는 Exception과 그 자손들, unchecked는 RuntimeException과 그 자손들이다.

메소드에 예외 선언하기

메소드가 호출시 발생가능한 예외를 호출하는 쪽에 알리는 것

void method() throws Exception1, Exception2, ... {
    // 내용
}

void method() throws Exception { // 모든 종류 예외가 발생 가능
    // 내용
}

throw가 아니라 throw를 사용하는 것에 유의할 것
throws에는 checked 예외만 적는 것이 정석이다.

class Ex1 {
    public static void main(String[] args) throws Exception {
        method1();
    }

    static void method1() throws Exception {
        method2();
    }

    static void method2() throws Exception {
        throw new Exception();
    }
}

프로그램 흐름

  1. main에서 method1을 호출
  2. method1에서 method2를 호출
  3. method2에서 예외 발생
  4. method2에서 예외를 처리할 수 없어 method1에 넘김
  5. method1에서 예외를 처리할 수 없어 main에 넘김
  6. main에서 예외를 처리할 수 없어 main이 죽고 비정상 종료되고, JVM에 예외를 넘김
  7. JVM이 예외 처리

예외를 발생시켰으면 처리할 수 있는 try-catch문이 필요하다.

finally 블러

예외 발생여부와 관계 없이 수행되어야 하는 코드를 넣는다.

try{
    ...
} catch (Exception1 e1) {
    ...
} finally {
    ...
}

finally 블럭은 try-catch 문의 마지막에 위치한다.

사용자 정의 예외 만들기

우리가 직접 예외 클래스를 정의할 수 있다.
조상은 Exception과 RuntimeException 중에서 선택한다.

class MyException extends Exception {
    MyException(String msg) {
        super(msg)
    }
}

String 매개변수가 있는 생성자를 넣어준다.

예외 되던지기(exception re-throwing)

예외를 처리한 후에 다시 예외를 발생시키는 것
호출한 메소드와 호출된 메소드 양쪽 모두에서 예외처리하는 것

class Ex1 {
    public static void main(String[] args) {
        try {
            method1();
        } catch (Exception e) {
            System.out.println("main메소드에서 예외 처리");
        }
    }

    static void method1() throws Exception {
        try {
            throw new Exception();
        } catch (Exception e) {
            System.out.println("method1메소드에서 예외 처리");
            throw e;
        }
    }
}

method1메소드에서 예외 처리
main메소드에서 예외 처리

예외를 분산처리할 때 사용

연결된 예외(chained exception)

한 예외가 다른 예외를 발생시킬 수 있다.
예외 A가 예외 B를 발생시키면 A는 B의 원인 예외(cause exception)

Throwable initCause(Throwable cause) : 지정한 예외를 원인 예외로 등록
Throwable getCause() : 원인 예외를 반환

void install() throws InstallException {
    try {
        startInstall(); // SpaceException 발생
        copyFiles();
    } catch (SpaceException e) {
        InstallException ie = new InstallException("설치중 예외발생"); // 예외 생성
        ie.initCause(e); // InstallException의 원인 예외를 SpaceException으로 지정
        throw ie; // InstallException을 발생시킴
    } catch (MemoryException me) {
        ...
    }
}

새로운 예외에 발생한 예외를 포함시키는 것

연결된 예외를 사용하는 이유
여러 예외를 하나로 묶어서 다루기 위해서

try {
    install();
} catch (SpaceException e) {
    e.printStackTrace();
} catch (MemoryExceptin e) {
    e.printStackTrace();
}
try {
    install();
} catch(InstallException e) {
    e.printStackTrace();
}

checked 예외를 unchecked 예외로 변경하려 할 때 (필수 처리에서 선택 처리로)

static void startInstall() throws SpaceException, MemoryException {
    if (!enoughSpace())
        throw new SpaceException("설치할 공간이 부족");
    
    if (!enoughMemory())
        throw new MemoryException("메모리가 부족");
}
static void startInstall() throws SpaceException {
    if (!enoughSpace())
        throw new SpaceException("설치할 공간이 부족");
    
    // MemoryException을 RuntimeException으로
    if (!enoughMemory())
        throw new RuntimeException(new MemoryException("메모리가 부족"));
}