S-JIS[2007-02-10/2022-03-23] 変更履歴
Java(JDK1.6まで)でファイルやディレクトリー関連の操作や情報を取得する方法について。
JDK1.7(Java7)以降は新しいクラスを使う。→new I/O2(nio2) [2011-07-30]
|
|
(JDK1.6以前では)ファイル・ディレクトリー関連の操作や情報保持はFileクラスで行う。[/2012-05-18]
ディレクトリ内のファイルの一覧の取得やファイルを削除するのにもFileクラスを使う。
一時ファイルを作ることも出来る。[2008-08-23]
バイナリファイルの読み書きには、基本的にInputStream・OutputStreamを用いる。[/2011-07-30]
テキストファイルの読み書きには、基本的にReader・Writerを用いる。[/2011-07-30]
プロパティーファイルの読み書きにはPropertiesが使える。
画像ファイルの読み書きにはImageIO#read()・write()が使える。[2010-01-08]
(ファイルじゃないけど)
出力した内容を入力に使いたい場合はパイプクラスを用いる。[2007-12-01]
ファイル内の位置を指定して読み書きしたい場合はRandomAccessFileを用いる。[/2007-12-21]
ファイルのクローズやフラッシュだけは全クラスで共通になっている。[/2011-07-30]
ファイルのコピーにはFileChannelを用いる。[/2011-07-30]
ファイル名の変更にはrenameToを呼び出す。[2011-12-22]
ファイルのロックにはFileLockを使用する。[2013-12-20]
GUIで(ドラッグして)ドロップされたファイルを受け取るにはAWTのDropTargetを用いる。
拡張子に応じて開いたり編集したりするにはAWTのDesktopを用いる。[2008-04-26]
ファイル名やディレクトリ名の保持は、Fileクラスで行う。 (→JDK1.7ではPathを使う)
import java.io.File;
そのファイルの存在有無確認や属性の取得/変更、ディレクトリの場合はその中のファイル一覧の取得といった操作も行える。
→操作の概要(他言語との比較)
ファイル名を取得する系統のメソッドでは、大抵はこのFileクラスで値を返す。
パスの区切りは、通常は環境に応じた文字を使用するが、Windowsの場合、Fileクラスは「\」でも「/」でも受け付ける。[2007-06-21]
例 | File#toString() | 備考 |
---|---|---|
new File("C:\\dir") |
C:\dir |
|
new File("C:\\dir\\abc.txt") |
C:\dir\abc.txt |
|
new File("C:/dir/abc.txt") |
C:\dir\abc.txt |
「/」で指定しても「\」になる。 |
new File("C:/dir", "abc.txt"); |
C:\dir\abc.txt |
|
new File("", "C:") |
\C: |
parentに空文字列を指定した場合、頭に「\」が付く。 parentからの相対パス扱いで区切り文字が入るのだろうが…(苦笑) |
new File("", "C:/dir/abc.txt") |
\C:\dir\abc.txt |
|
new File((String)null, "C:") |
C: |
parentがnullだと大丈夫。 |
new File((String)null, "C:/dir/abc.txt") |
C:\dir\abc.txt |
|
new File("\\C:\\dir", "abc.txt"); |
C:\dir\abc.txt |
parentの先頭に「\」が付いていたら、その文字は削除される。 |
Fileインスタンスからjava.net.URLやjava.net.URIを取得することが出来る。[2008-07-26]
File file = new File(parent_dir, fileName); URI uri = file.toURI(); URL url = uri.toURL(); //throws MalformedURLException
Fileには URLに直接変換するメソッド(File#toURL())もあるが、JDK1.6から非推奨になった。
例えば「abc%.txt」というファイルは、URIでは「%」そのものはエスケープしないと使えないので、「abc%25.txt」となるのが正しい。[2010-01-22]
しかしFile#toURL()ではそういう変換をしてくれない。File#toURI()ではそういう変換を行ってくれる。
URLやURIからFileインスタンスを生成するには以下のようにする。[2008-10-14]
URL url = new URL("file:/C:/temp/"); //throws MalformedURLException URI uri = url.toURI(); //throws URISyntaxException //JDK1.5 File file = new File(uri);
一時的に使用するファイル(ファイル名)を生成するには、createTempFile()を使う。[2008-08-23]
また、そのファイルに対してdeleteOnExit()を呼び出しておくと、JavaVMの終了時に自動的に削除される。
(renameTo()などでリネームして その一時ファイルが無くなっていても VM終了時にエラーになったりはしないので、事前に明示的に消しても問題なし)
File tempDir = new File("C:/temp"); File temp = File.createTempFile("zzz", ".txt", tempDir); //"C:\temp\zzz123.txt"といったファイルが作られる temp.deleteOnExit(); OutputStream os = new FileOutputStream(temp); os.write("test".getBytes()); os.close(); // temp.renameTo(new File(temp.getParentFile(), "zzz.txt"));
createTempFile()の第3引数(tempDir)を指定しない(あるいはnullを渡す)場合、デフォルトの場所(システムプロパティーjava.io.tmpdir
で指定される場所)に作られる。
JDK1.6から、ファイルの権限(パーミッション)を変更するメソッドが追加された。[2012-05-18]
しかし、これらはUNIXではちゃんと動作するようだが、Windowsでは一部のメソッドは動作しない。
メソッド | 戻り値 | 動作 | 権限確認結果 | |
---|---|---|---|---|
setReadable(true) |
true |
読み取り不可には出来ない。 | canRead() |
true |
setReadable(false) |
false |
true |
||
setWritable(true) |
true |
書き込み権限はきちんと動作する。 書き込み不可=読み取り専用 |
canWrite() |
true |
setWritable(false) |
true |
false |
||
setExecutable(true) |
true |
実行不可には出来ない。 | canExecute() |
true |
setExecutable(false) |
false |
true |
参考: More Enhancements in Java SE 6
バイナリーデータの読み込みにはInputStream、書き込みにはOutputStreamを用いる。
入出力先の種類によって、InputStream・OutputStreamの具象クラスには いくつもの種類がある。
JDK1.6以前でバイナリーファイルから全データをバイト配列に読み込むには、以下のようにする。
public static byte[] readBinaryFile(String fileName) { File f = new File(fileName); int len = (int) f.length(); //ファイルの長さを取得 byte[] buf = new byte[len]; InputStream is = null; try { is = new FileInputStream(f); //return readBinaryFull(is, len); int l = is.read(buf); System.out.println("読み込んだ長さ:" + l); } catch (Exception e) { throw new RuntimeException(e); } finally { if (is != null) try { is.close(); } catch (IOException e) {} } return buf; }
ファイルならともかく、一般的には、read()は必ずしも指定した全サイズが読めるとは限らない。InputStreamの実装依存になる。[2008-10-19]
(例えばソケット通信なら、まだ届いていないデータは読み込めず、届いている分だけread()できる)
そういった場合でも(ウェイト(待ち)が発生しても)指定したサイズを読み込みたいなら、以下のようにreadFully()が使える。
public static byte[] readBinaryFull(InputStream is, int len) throws IOException { DataInputStream dis = new DataInputStream(is); byte[] buf = new byte[len]; dis.readFully(buf); return buf; }
JDK1.7でバイナリーファイルから全データをバイト配列に読み込むには、Files.readAllBytes()を使うのが便利。[2017-09-23]
import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths;
public static byte[] readAllBytes(String fileName) throws IOException { Path path = Paths.get(fileName); return Files.readAllBytes(path); }
Java9で、InputStreamに全データを読み込むメソッドが追加された。[2017-09-23]
import java.io.InputStream; import java.io.IOException;
public static byte[] readAllBytes(InputStream is) throws IOException { try (is) { return is.readAllBytes(); } }
readAllBytesの中は、最初にある程度のサイズのバイト配列を用意し、足りなければ拡張し、最後にデータサイズぴったりの配列にコピーしている。
ファイルから全データを読み込むFiles.readAllBytes()はファイルサイズを取得してバイト配列を用意するので、ファイルから読み込む場合はFiles.readAllBytes()を使う方が良い。
nバイトだけ読み込むreadNBytesメソッドもある。
byte[] buf = new byte[100]; int len = is.readNBytes(buf, 0, 100);
// Java11 [2018-10-01]
byte[] buf = is.readNBytes(100);
バイト配列の全データをバイナリーファイルに書き込むには、以下のようにする。
public static void writeBinaryFile(String fileName, byte[] data) { File f = new File(fileName); OutputStream os = null; try { os = new FileOutputStream(f); os.write(data); } catch (Exception e) { throw new RuntimeException(e); } finally { if (os != null) try { os.close(); } catch (IOException e) {} } }
JDK1.7でバイト配列の全データをバイナリーファイルに書き込むには、Files.write()を使うのが便利。[2017-09-23]
import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths;
public static void writeBinaryFile(String fileName, byte[] data) throws IOException { Path path = Paths.get(fileName); Files.write(path, data); }
Java9で、InputStreamで読み込んだ全データをOutputStreamに書き込むメソッドが追加された。[2017-09-23]
public static void transfer(InputStream is, OutputStream os) throws IOException { try (is; os) { is.transferTo(os); } }
テキストデータの読み込みにはBufferedReader、書き込みにはBufferedWriterを用いる(改行付きで出力したいならPrintWriter)。
入出力先の種類によって、Reader・Writerの具象クラスにも いくつもの種類がある。
種類 | 入力 | 出力 | 主な取得方法 | 関連 | 更新日 |
---|---|---|---|---|---|
基本的な抽象クラス | Reader | Writer | 具象クラスを使用 | ||
空データ | Reader.nullReader() | Writer.nullWriter() | staticメソッド | 2018-10-01 | |
ファイル | FileReader | FileWriter | コンストラクター | ||
文字列(String ) |
StringReader (中身はString) |
StringWriter (中身はStringBuffer) |
コンストラクター | →使用例 | |
InputStream/OutputStream | InputStreamReader | OutputStreamWriter | コンストラクター | ||
出力した内容を読み込み | PipedReader | PipedWriter | コンストラクター | 2007-12-01 | |
他のReader/Writerをバッファリング | BufferedReader | BufferedWriter | コンストラクター | ||
行番号を付けるReader | LineNumberReader | コンストラクター | 2009-01-01 | ||
後戻りが出来るReader | PushbackReader | コンストラクター | 2007-12-21 | ||
PrintStream(System.out)と似たWriter | PrintWriter | コンストラクター | →使用例 | 2007-11-30 | |
他のReader/Writerに委譲 | FilterReader | FilterWriter | 継承 | 2008-12-22 |
テキストファイルから全データを読み込むには、以下のようにする(JDK1.5)。
import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.List;
public static List<String> readTextFile(String fileName) { List<String> textList = new ArrayList<String>(); File f = new File(fileName); InputStream is = null; Reader r = null; BufferedReader br = null; try { is = new FileInputStream(f); r = new InputStreamReader(is, "MS932"); br = new BufferedReader(r); for (;;) { String text = br.readLine(); //改行コードは含まれない if (text == null) break; textList.add(text); } } catch (Exception e) { throw new RuntimeException(e); } finally { if (br != null) try { br.close(); } catch (IOException e) {} if (r != null) try { r .close(); } catch (IOException e) {} if (is != null) try { is.close(); } catch (IOException e) {} } return textList; }
InputStreamReaderでは、キャラクターセット(エンコード)を指定できる。
(キャラクターセットを指定しなかった場合は、Java17以前は環境依存、Java18以降はUTF-8になる。[2022-03-23])
InputStreamで読み込まれるバイト列が“そのエンコードである”として、UNICODEへの文字コード変換を行う。
すなわち、JavaVMの内部では文字列はUNICODEで扱われているので、テキストファイルがシフトJISなら「SJIS→UNICODE」の変換が必要になる。MS932がJavaにおけるSJISの指定だと思っていれば大体OK。
デフォルトのキャラセットでいい場合は、FileInputStream・InputStreamReaderを使わずに「r = new
FileReader(f);
」でよい。
Readerがネストしているときは、一番外側だけクローズしてやれば、ネストしている内側のReaderもクローズされる。[2007-05-03]
(br = new BufferedReader(new
InputStreamReader(is));ならば、br.close()だけでInputStreamReaderもisもクローズされる)
上記の例ではbr・r・isの全てに対しそれぞれfinallyでクローズしており、冗長に見える。finallyでbrのクローズだけすればよいと。
しかし、もしisは作られてrを作るときに例外が発生したら、brのクローズだけではrもisもクローズされない。(InputStreamReaderのエンコード指定を間違ったりしたら、例外は出るだろうな〜)
厳密には、やはりそれぞれクローズすべきだろう。
JDK1.7では以下のようにしてBufferedReaderを使用する。[2014-04-15]
import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.util.ArrayList; import java.util.List;
public static List<String> readTextFile(String fileName) { List<String> textList = new ArrayList<>(); File file = new File(fileName); try (BufferedReader br = Files.newBufferedReader(file.toPath(), Charset.forName("MS932"))) { for (;;) { String text = br.readLine(); // 改行コードは含まれない if (text == null) { break; } textList.add(text); } } catch (IOException e) { throw new RuntimeException(e); } return textList; }
リソース付きtry文を使って、クローズ処理の記述を省略している。
実際には、テキストファイルの全内容を読み込んでList<String>にするには、そのものずばりのFiles.readAllLines()を使う方が便利。
JDK1.8では、BufferedReaderにlines()という(Stream<String>を返す)メソッドが追加された。[2014-04-15]
これを使うと、レコード読み込みの終了判定(readLine()のnullチェック)が不要になる。
import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.util.List; import java.util.stream.Collectors;
public static List<String> readTextFile(String fileName) { File file = new File(fileName); try (BufferedReader br = Files.newBufferedReader(file.toPath(), Charset.forName("MS932"))) { return br.lines().collect(Collectors.toList()); } catch (IOException e) { throw new UncheckedIOException(e); } }
以下のような使い方をすることも出来る。
// テキストファイルの内容を1行ずつ表示する例 br.lines().forEach(text -> { System.out.println(text); });
実際には、Files#lines()を使う方がBufferedReaderが出てこなくて便利。[2014-04-29]
テキストファイルに出力するには、以下のようにする(JDK1.5)。
import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.List;
public static void writeTextFile(String fileName, List<String> textList) { File f = new File(fileName); OutputStream os = null; Writer w = null; BufferedWriter bw = null; try { os = new FileOutputStream(f); w = new OutputStreamWriter(os, "MS932"); bw = new BufferedWriter(w); for (String text: textList) { bw.write(text); bw.newLine(); //改行 //bw.flush(); //途中で実際に書き込みを実行したい場合はフラッシュする } } catch (Exception e) { throw new RuntimeException(e); } finally { if (bw != null) try { bw.close(); } catch (IOException e) {} if (w != null) try { w .close(); } catch (IOException e) {} if (os != null) try { os.close(); } catch (IOException e) {} } }
OutputStreamWriterもキャラクターセット(エンコード)を指定できる。(上記のMS932)
(キャラクターセットを指定しなかった場合は、Java17以前は環境依存、Java18以降はUTF-8になる。[2022-03-23])
(JavaVM内部の表現である)UNICODEから“指定したキャラセット”に変換して出力される。
デフォルトのキャラセットでいい場合は、FileOutputStream・OutputStreamWriterを使わずに「w = new
FileWriter(f);
」でよい。
Writerがネストしているときは、一番外側だけクローズしてやれば、ネストしている内側のWriterもクローズされる。[2007-05-03]
(bw = new BufferedWriter(new
OutputStreamWriter(os));ならば、bw.close()だけでOutputStreamWriterもosもクローズされる)
上記の例ではbw・w・osの全てに対しそれぞれfinallyでクローズしており、冗長に見える。finallyでbwのクローズだけすればよいと。
しかし、もしosは作られてwを作るときに例外が発生したら、bwのクローズだけではwもosもクローズされない。(OutputStreamWriterのエンコード指定を間違ったりしたら、例外は出るだろうな〜)
厳密には、やはりそれぞれクローズすべきだろう。
JDK1.7では以下のようにしてBufferedWriterを使用する。[2014-04-15]
import java.io.BufferedWriter; import java.io.File; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.util.List;
public static void writeTextFile(String fileName, List<String> textList) { File file = new File(fileName); try (BufferedWriter bw = Files.newBufferedWriter(file.toPath(), Charset.forName("MS932"), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { for (String text : textList) { bw.write(text); bw.newLine(); // 改行 } } catch (Exception e) { throw new RuntimeException(e); } }
リソース付きtry文を使って、クローズ処理の記述を省略している。
実際には、List<String>をテキストファイルに書き込むには、そのものずばりのFiles.write()を使う方が便利。
一時的にOutputStream(又はWriter)に出力し、その内容を別のInputStream(Reader)への入力としたい場合は、パイプ(Piped)の名を冠したクラスを使う。[2007-12-01]
パイプ(pipe)という名が付いているが、“UNIXやDOSで標準出力の内容を次のコマンドの標準入力に渡すパイプ”とは関係ない。
パイプは、入力と出力をペアでインスタンス化する。すると、その出力インスタンスに出力した内容が、入力インスタンスで取得できる。
参考: SUGAI, ManabuさんのJava 入門 | Pipe Streams
ストリームの例:
public static void testStream() throws IOException { PipedOutputStream pos = new PipedOutputStream(); PipedInputStream pis = new PipedInputStream(pos); writeStream(pos); //OutputStreamに値を出力する dumpStream(pis); //InputStreamの内容を表示する } private static void writeStream(OutputStream os) throws IOException { os.write(new byte[] { 0x11, 0x22, 0x33 }); os.close(); } private static void dumpStream(InputStream is) throws IOException { int len = is.available(); System.out.println(len); for (int i = 0; i < len; i++) { System.out.println(Integer.toHexString(is.read())); } is.close(); }
なお、パイプの入出力のインスタンス生成の順番はどちらが先でもよい。
(コンストラクター内部で「相手.connect(this);
」を呼び出して、指定された相手に自分を登録している為)
pos = new PipedOutputStream(); pis = new PipedInputStream(pos);
pis = new PipedInputStream(); pos = new PipedOutputStream(pis);
あと、JDK1.6からは、バッファサイズを引数に取るコンストラクターも追加されている模様。
テキストの例:
public static void testText() throws IOException { PipedWriter pw = new PipedWriter(); PipedReader pr = new PipedReader(pw); writeWriter(pw); //Writerに値を出力する dumpReader(pr); //Readerの内容を表示する } private static void writeWriter(Writer w) throws IOException { w.write("abc\n"); w.write("def\n"); w.close(); } private static void dumpReader(Reader r) throws IOException { while (r.ready()) { System.out.print((char) r.read()); } r.close(); }
バッファリングの例:
public static void testBuffer() throws IOException { PipedWriter pw = new PipedWriter(); PipedReader pr = new PipedReader(pw); writeBuffer(pw); dumpBuffer(pr); } private static void writeBuffer(Writer w) throws IOException { PrintWriter bw = new PrintWriter(new BufferedWriter(w)); bw.println("abc"); bw.println("def"); bw.close(); } private static void dumpBuffer(Reader r) throws IOException { BufferedReader br = new BufferedReader(r); while (br.ready()) { System.out.println(br.readLine()); } br.close(); }
PipedWriterやPipedReaderもWriter/Readerである事に変わりは無いので、他のWriter/Reader(例えばBuffered系)のクラスでラップすることが出来る。
ただし、Piped系のクラスをBuffered系のクラスでバッファリングする場合は、書き込みのフラッシュや読み込みのブロッキングに注意を払う必要がある。
よくある実装だが、ブロッキング(永久待ち)してしまう例:
private static void writeBuffer(Writer w) throws IOException {
PrintWriter bw = new PrintWriter(new BufferedWriter(w));
bw.println("abc");
bw.println("def");
bw.flush();
//bw.close(); クローズしない
}
private static void dumpBuffer(Reader r) throws IOException {
BufferedReader br = new BufferedReader(r);
for(;;) {
String str = br.readLine();
if (str == null) break;
System.out.println(str);
}
br.close();
}
readLine()は次の入力があるまで待つので、この例ではパイプへの書き込み側でクローズしていないから(永久待ちで)無限ループになってしまう。
「readLine()がnullだったら終了する」というコーディングはよく見かけるが、このようにReaderには色々な実装(具象クラス)があるので、br.ready()(読み込めるデータがあるかどうかを判断する)を使う方が安全だと思う。
ただ、BufferedWriterを使っていてflush()しないと、br.ready()はfalseになってしまう(br.readLine()も何も読み込まない)。
ランダムアクセスというのは、ファイル内の位置を自由に(ランダムに)指定して読み書き(アクセス)できること。[2007-12-21]
ストリームはシーケンシャル(順序に従う)なので、後戻りすることが出来ない。
C言語のFILE構造体なんかはランダムアクセス。
JavaではRandomAccessFileクラスを使う。
→RandomAccessFileの使用例 [2007-11-30]
例えば InputSream・OutputSreamとRandomAccessFileは、read()・write()メソッドがそっくりに作られているが、クラス関係(インターフェースによる継承とか)としては無関係。[2007-12-21]
ただ、ファイル(ストリーム)を閉じるclose()メソッドは、JDK1.5からCloseableというインターフェースが導入され、どのクラスでもこのインターフェースを使って共通に扱えるようになった。
同様に、flush()できることを意味するFlushableというインターフェースもJDK1.5で追加された。[2008-05-17]
JDK1.7では自動的にクローズできることを表すAutoCloseableインターフェースが追加された。[2011-07-30]
JDK1.7以降ではFiles.copy()を使ってファイルをコピーできる。[2011-07-30]
JDK1.6以前では、DOSのcopyやUNIXのcpのような、直接ファイルをコピーする命令は用意されていない。[2007-05-31]
自分でファイルを読み込み、内容を別のファイルに書き込むしかない。
しかし、その為にFileChannelというクラス(java.nio.channelsパッケージのファイルチャネル)が用意されている。
FileChannelは実装がOS依存なので、自分でバイト配列に読み込んで書き込んだりするよりも速いことが期待される。
import java.nio.channels.FileChannel;
public static void fileCopy(File sf, File df) { FileChannel sc = null, dc = null; try { sc = new FileInputStream(sf).getChannel(); dc = new FileOutputStream(df).getChannel(); dc.transferFrom(sc, 0, sc.size()); //sc.transferTo(0, sc.size(), dc); } catch (IOException e) { e.printStackTrace(); } finally { if (dc != null) try { dc.close(); } catch (IOException e) {} if (sc != null) try { sc.close(); } catch (IOException e) {} } }
→ファイルチャネルからバッファーにデータを読み込む例
→ファイルチャネルからバッファーを取得する例
→ファイルチャネルを使ってファイルをロックする例
JDK1.6以前では、File#renameTo()を使ってファイルの改名を行う。[2011-12-22]
JDK1.7以降ではFiles.move()を使う。
File#renameTo()の動作は、プラットフォーム依存。つまりWindowsとUNIXでは挙動が異なることがある。
例えば、変更先のファイルが存在している場合、UNIXでは上書きするがWindowsではリネーム失敗となる。
(ファイル操作に限らず、)入出力を伴う処理(メソッド)では、IOExceptionがthrows宣言されていることが多い。[2014-03-21]
IOExceptionはチェック例外なので、catchして処理する必要がある。
ただ単に呼び出し元メソッドに再スローするだけなことも多いが、その場合はthrows宣言を書かなければならない。
import java.io.IOException;
public void readPrint(BufferedReader br) throws IOException { for(;;) { String str = br.readLine(); //throws IOException if (str == null) break; System.out.println(str); } }
JDK1.8で、非チェック例外なIOExceptionクラスが追加された。[2014-03-21]
UncheckedIOExceptionはRuntimeExceptionを継承しており、throwsで宣言しなくても呼び出し元にスローすることが出来る。
import java.io.IOException; import java.io.UncheckedIOException;
public void readPrint8(BufferedReader br) { for (;;) { try { String str = br.readLine(); //throws IOException if (str == null) break; System.out.println(str); } catch (IOException e) { throw new UncheckedIOException("実験", e); } } }
UncheckedIOExceptionは(IOExceptionと継承関係があるわけではなく、) コンストラクターでIOExceptionをラップする形式になっている。
で、UncheckedIOException#getCause()は(共変戻り値型により)ちゃんとIOExceptionを返すようになっている。