【Java】例外の種類

## 初めに
Javaでは実行時エラー(例外)を処理する方法が存在する。特に例外は正しくキャッチしないとシステムが上手く動かず重大な問題が発生する恐れがある。まずは例外の種類について覚えておきたい。

## 例外とは
コンパイルが通っても実行時に発生するエラーのこと。また例外が発生することを**例外がスローされる**という。
まずJavaのエラーや例外に関するクラスは**Throwableクラス**配下にあり、**Errorクラス**と**Exceptionクラス**が存在する。Exceptionクラスはさらに**Runtime Exceptionクラス**とその他のクラスに分かれる。

## 非検査例外
Runtime Exceptionクラス系の例外を非検査例外と呼ぶ。これはtry-catchで例外ハンドリングしなくてもコンパイルエラーにならない。

```ruby:Java(Main.java)
public class Main {
	public static void foo() {
		throw new NullPointerException();
	}
	public static void main(String[] args) {
		Main.foo();
	}
}
```

結果
```
Exception in thread "main" java.lang.NullPointerException
	at Main.foo(Main.java:3)
	at Main.main(Main.java:6)
```

上記のようにコンパイルエラーにならず、実行時エラーが発生する。

## 検査例外
対して検査例外はExceptionクラス配下のRuntime Exception以外のクラスが対象。同様にtry-catchせずに実行してみる。
```ruby:Java(Main.java)
import java.io.IOException;

public class Main {
	public static void foo() {
		throw new IOException();
	}
	public static void main(String[] args) {
		Main.foo();
	}
}
```

結果
```
Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
	処理されない例外の型 IOException

	at Main.foo(Main.java:5)
	at Main.main(Main.java:8)
```

今度はコンパイルエラーが発生した、これをtry-catchで囲ってみよう。

```ruby:Java(Main.java)
import java.io.IOException;

public class Main {
	public static void foo() {
		throw new IOException();
	}
	public static void main(String[] args) {
		try {
			Main.foo();
		}catch(IOException e) {
			System.out.println("エラー");
		}
	}
}
```

結果
```
Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
	IOException の到達不可能な catch ブロック。この例外は、try ステートメント本文からはスローされません

	at Main.main(Main.java:10)
```

またもやエラーになった。これは**throw句**が足りないからだ。検査例外の場合、メソッドが実行中にどの例外をスローする可能性があるか宣言する必要がある。以下を実行してみよう。
```ruby:Java(Main.java)
import java.io.IOException;

public class Main {
	public static void foo() throws IOException {
		throw new IOException();
	}
	public static void main(String[] args) {
		try {
			Main.foo();
		}catch(IOException e) {
			System.out.println("エラー");
		}
	}
}
```

結果
```
エラー
```

今度は正しく表示できた。
## 検査例外の種類
### IOException
プログラムがファイル操作やネットワーク通信など、I/O（入力/出力）操作を行う際に、何らかのエラーがあった場合に発生。

### FileNotFoundException
IOExceptionのサブクラス。特にファイルが見つからない場合に発生。

## 非検査例外の種類
### NullPointerException
```ruby:Java(Main.java)
public class Main {
	static String str;
	public static void main(String[] args) {
		str.replace("null","b");
	}
}
```

結果
```
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.replace(java.lang.CharSequence, java.lang.CharSequence)" because "Main.str" is null
	at Main.main(Main.java:4)
```

上記ではクラス変数strの初期値にnullが設定されており、nullに対してメソッドを実行しようとしたために発生した例外である。また以下の場合も例外が発生する。

```ruby:Java(Main.java)
public class Main {
	static String str;
	public static void main(String[] args) {
		switch(str) {
		case "a":
			System.out.println("a");
			break;
		case "b":
			System.out.println("b");
			break;
		default:
			System.out.println("C");
		}
	}
}
```

ここではswitch文にnullを指定したためにNullPointerExceptionが発生する。

また間違えやすい問題があったため紹介する。以下のコードの結果はどうなるだろうか。
```ruby:Java(Main.java)
public class Main {
	public static void main(String[] args) {
		String[] array = new String[3];
		array[1] = "AAA";
		array[2] = "CCC";
		for(String str : array) {
			System.out.println(str);
		}
		
		Item[] items = new Item[3];
		items[1] = new Item("A");
		items[2] = new Item("B");
		for(Item item : items) {
			System.out.println(item.name);
		}
	}
}
```
```ruby:Java(Item.java)
public class Item {
	String name;
	public Item(String name) {
		this.name = name;
	}
}
```
結果
```
null
AAA
CCC
Exception in thread "main" java.lang.NullPointerException: Cannot read field "name" because "item" is null
	at Main.main(Main.java:14)
```

1つ目のfor文は問題なく表示されるが、2つ目のforで例外が発生している。まず1つ目はString型の配列であり、要素が初期化されない時は自動的にnullが設定される。そのため、null→B→Cの順番で出力された。
次の配列の要素はItem型であり、item[0]じゃ初期化されていないためそれ自体がnullである。nullの要素のitem.nameにアクセスしようとしたために例外が発生した。

### UnsupprtedOperationException
サポートされていない操作を行うと発生する例外

```ruby:Java(Main.java)
import java.util.List;

public class Main {
	public static void main(String[] args) {
		List<Character> list = List.of();
		list.add('a');
		list.add('b');
		list.add('c');
	}
}
```
結果
```
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
	at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:147)
	at Main.main(Main.java:6)
```
ここではListインタフェースのofメソッドを使っているが、ofメソッドは変更不可能なコレクションを作るのが特徴。そのため、addメソッドで変更を加えようとすると上記の例外が発生する。

### DatetimeException
無効な日付操作をした場合に発生
```ruby:Java(Main.java)
import java.time.LocalDate;

public class Main {
	public static void main(String[] args) {
		LocalDate a = LocalDate.of(2015, 0,1);
	}	
}
```

結果
```
Exception in thread "main" java.time.DateTimeException: Invalid value for MonthOfYear (valid values 1 - 12): 0
	at java.base/java.time.temporal.ValueRange.checkValidValue(ValueRange.java:319)
	at java.base/java.time.temporal.ChronoField.checkValidValue(ChronoField.java:718)
	at java.base/java.time.LocalDate.of(LocalDate.java:271)
	at Main.main(Main.java:5)
```
## ConCurrentModificationException
反復処理（イテレート）中にコレクションを変更しようとした場合に発生する例外。

```ruby:Java(Main.java)
import java.util.ArrayList;

public class Main {
	public static void main(String[] args) {
		ArrayList<String> list = new ArrayList<>();
		list.add("A");
		list.add("B");
		list.add("C");
		list.add("D");
		list.add("E");
		for(String str : list) {
			if("C".equals(str)) {
				list.remove(str);
			}
		}
	}	
    }
```

結果
```
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
	at Main.main(Main.java:11)
```

なお、以下の場合はエラーは発生しない。Bを削除した後、Cがそのまま繰り上がってそれ以上読みこみが行われないため。
```ruby:Java(Main.java)
import java.util.ArrayList;

public class Main {
	public static void main(String[] args) {
		ArrayList<String> list = new ArrayList<>();
		list.add("A");
		list.add("B");
		list.add("C");
		for(String str : list) {
			if("B".equals(str)) {
				list.remove(str);
			}
		}
	}	
}
```

## ArrayIndexOutOfBoundsException 
配列のインデックスが有効な範囲を超えてアクセスされた場合に発生。
```ruby:Java(Main.java)
public class Main {
	public static void main(String[] args) {
		System.out.println(args[0].length());
	}	
}
```

結果
```
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
	at Main.main(Main.java:3)
```

これはmainメソッドの引数で算用しているString配列は、mainメソッド実行前にJVMが作成したもので、何も引数が渡されない場合は要素を1つも持たないString配列型オブジェクトとなる。そのため要素外にアクセスしたとして認識される。


## まとめ
* 例外とはコンパイルが通っても実行時に発生するエラーのこと
* 例外にはtry-catchでハンドリングしなくてもコンパイルエラーにならない非検査例外がある
* 逆にハンドリングしないとコンパイルエラーになるのは検査例外
* 検査例外にはIOExceptionやFileNotFoundExceptionなどがある
* 非検査例外にはNullPointerExceptionやUnsupprtedOperationException、DatetimeException、ConCurrentModificationException、ArrayIndexOutOfBoundsExceptionなどがある