初心者向けのJavaの解説を書き始めたのは2003年。Javaのバージョンは1.4でした。ファイルからの入力で次のやり方を紹介しました。
もちろんFileReaderとBufferedReaderを使うものも扱いましたが、文字コードを指定できるのはこれです。
package iotest; import java.io.*; public class FileRead01 { public static void main(String[] args){ String fname =args[0]; int ct = 0; try { FileInputStream is = new FileInputStream(fname); InputStreamReader in = new InputStreamReader(is,"UTF-8"); BufferedReader inb = new BufferedReader(in); String line; ct = 0; while ((line = inb.readLine()) != null) { ct++; System.out.println(line); } System.out.println("以上" + ct + "行"); inb.close(); in.close(); is.close(); } catch (IOException e) { System.err.println( fname + " がないのでは?" ); System.err.println( e); } } }
例外が起こった時にcloseされないのでfainally節を書いて...というのは最近見かけるようになりました。これに言及すると長くなりそうなので後回しにして、try-with-resources文 や Files.readAllLines()メソッドを使ってみます。
いままでのやり方でListに読んでみます。
package iotest; import java.io.*; import java.util.LinkedList; public class FileRead02 { public static void main(String[] args){ String fname =args[0]; LinkedList<String> list = new LinkedList<>(); try { FileInputStream is = new FileInputStream(fname); InputStreamReader in = new InputStreamReader(is,"UTF-8"); BufferedReader inb = new BufferedReader(in); String line; while ((line = inb.readLine()) != null) { list.add(line); } inb.close(); in.close(); is.close(); } catch (IOException e) { System.err.println( e); } for (String line : list) { System.out.println(line); } System.out.println("以上" + list.size() + "行"); } }
Java SE 7 以降 try−with−resources文が使えるようになったということです。tryの後ろに( )が加わって、そこに書くのがresourceということらしいのですが、resourceという記述はプログラムには出現しません。
ListやMapに型変数をつけなければならなくなったのも、Java SE 7 以降 ですが、こちらはコンパイルが通らないので勉強しましたが、 try−with−resources文は意識していなかったですね。
package iotest; import java.io.*; import java.util.LinkedList; public class Resource1 { public static void main(String[] args){ String fname =args[0]; LinkedList<String> list = new LinkedList<>(); try ( FileInputStream is = new FileInputStream(fname); InputStreamReader in = new InputStreamReader(is,"UTF-8"); BufferedReader inb = new BufferedReader(in); ){ String line; while ((line = inb.readLine()) != null) { list.add(line); } } catch (IOException e) { System.err.println( e); } for (String line : list) { System.out.println(line); } System.out.println("以上" + list.size() + "行"); } }
ファイルを読んでList<String>として返します。close()は不要ですが、tryは必要です。
ファイルのデータが少ない時には便利です。
package iotest; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.nio.charset.StandardCharsets; public class ReadAll1 { public static void main(String[] args){ String fname =args[0]; List<String> list = null; //Collections.emptyList(); Path path = Paths.get(fname); try { list = Files.readAllLines(path, StandardCharsets.UTF_8 ); } catch (IOException e) { System.err.println( e); } for (String line : list) { System.out.println(line); } System.out.println("以上" + list.size() + "行"); } }
Listの初期化については
List<String> lines = Collections.emptyList();
という書き方をする例もありました。Listはインターフェースなので new List<>()というわけにはいかないのですね。でも、どうせFiles.readAllLines()で代入されてしまいますし、拡張for文でnullのままでも文句は言われません。
public static final <T> List<T> emptyList() なので、emptyList()にはaddできません。
readAllLines()の周りは見慣れないものが多くあります。
readAllLines(java.nio.file.Path, java.nio.charset.Charset) readAllLines()は java.nio.file.Filesのstaticメソッド java.nio.file.Path はインターフェース java.nio.file.Paths は Path を返す static メソッド群 java.nio.charset.Charset はクラス java.nio.charset.StandardCharsets は 標準 Charsets 用の定数定義
StandardCharsets.UTF_8 は長く、importも必要でお得感がありませんが、
import java.nio.charset.StandardCharsets; list = Files.readAllLines(path, StandardCharsets.UTF_8 );
の他に
import static java.nio.charset.StandardCharsets.*; list = Files.readAllLines(path, UTF_8 );
という使い方もできます。
InputStreamReader でCharsetの指定は、Charsetでも文字列でもできます。
InputStreamReader(InputStream in, Charset cs) InputStreamReader(InputStream in, String charsetName)
"UTF-8"や"windoes-31j"は String charsetName での指定でした。
readAllLines() への指定は Charsetに限られます
java.nio.charset.StandardCharsetsに定数として用意されていますが、限られています。
ISO_8859_1 US_ASCII UTF_16 UTF_16BE UTF_16LE UTF_8
だけです。windoes-31jを使うときには
Charset.forName("Windows-31j")
とする必要があります(大文字小文字は無視)。この文字列がAPI仕様で記述が見つからず、次のメソッドで調べてみました。
static SortedMap<String,Charset> availableCharsets()
具体的には
System.out.println(java.nio.charset.Charset.availableCharsets().keySet());
[Big5, Big5-HKSCS, EUC-JP, EUC-KR, GB18030, GB2312, GBK, IBM-Thai, IBM00858, IBM01140, IBM01141, IBM01142, IBM01143, IBM01144, IBM01145, IBM01146, IBM01147, IBM01148, IBM01149, IBM037, IBM1026, IBM1047, IBM273, IBM277, IBM278, IBM280, IBM284, IBM285, IBM290, IBM297, IBM420, IBM424, IBM437, IBM500, IBM775, IBM850, IBM852, IBM855, IBM857, IBM860, IBM861, IBM862, IBM863, IBM864, IBM865, IBM866, IBM868, IBM869, IBM870, IBM871, IBM918, ISO-2022-CN, ISO-2022-JP, ISO-2022-JP-2, ISO-2022-KR, ISO-8859-1, ISO-8859-13, ISO-8859-15, ISO-8859-2, ISO-8859-3, ISO-8859-4, ISO-8859-5, ISO-8859-6, ISO-8859-7, ISO-8859-8, ISO-8859-9, JIS_X0201, JIS_X0212-1990, KOI8-R, KOI8-U, Shift_JIS, TIS-620, US-ASCII, UTF-16, UTF-16BE, UTF-16LE, UTF-32, UTF-32BE, UTF-32LE, UTF-8, windows-1250, windows-1251, windows-1252, windows-1253, windows-1254, windows-1255, windows-1256, windows-1257, windows-1258, windows-31j, x-Big5-HKSCS-2001, x-Big5-Solaris, x-COMPOUND_TEXT, x-euc-jp-linux, x-EUC-TW, x-eucJP-Open, x-IBM1006, x-IBM1025, x-IBM1046, x-IBM1097, x-IBM1098, x-IBM1112, x-IBM1122, x-IBM1123, x-IBM1124, x-IBM1364, x-IBM1381, x-IBM1383, x-IBM300, x-IBM33722, x-IBM737, x-IBM833, x-IBM834, x-IBM856, x-IBM874, x-IBM875, x-IBM921, x-IBM922, x-IBM930, x-IBM933, x-IBM935, x-IBM937, x-IBM939, x-IBM942, x-IBM942C, x-IBM943, x-IBM943C, x-IBM948, x-IBM949, x-IBM949C, x-IBM950, x-IBM964, x-IBM970, x-ISCII91, x-ISO-2022-CN-CNS, x-ISO-2022-CN-GB, x-iso-8859-11, x-JIS0208, x-JISAutoDetect, x-Johab, x-MacArabic, x-MacCentralEurope, x-MacCroatian, x-MacCyrillic, x-MacDingbat, x-MacGreek, x-MacHebrew, x-MacIceland, x-MacRoman, x-MacRomania, x-MacSymbol, x-MacThai, x-MacTurkish, x-MacUkraine, x-MS932_0213, x-MS950-HKSCS, x-MS950-HKSCS-XP, x-mswin-936, x-PCK, x-SJIS_0213, x-UTF-16LE-BOM, X-UTF-32BE-BOM, X-UTF-32LE-BOM, x-windows-50220, x-windows-50221, x-windows-874, x-windows-949, x-windows-950, x-windows-iso2022jp]
newBufferedReader()も見つけたので使ってみます
package iotest; import java.io.*; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.LinkedList; import static java.nio.charset.StandardCharsets.*; public class ReadAll2 { public static void main(String[] args){ String fname =args[0]; LinkedList<String> list = new LinkedList<>(); // List<String> list = null; //Collections.emptyList(); Path path = Paths.get(fname); try { BufferedReader inb = Files.newBufferedReader(path, UTF_8 ); String line; while ((line = inb.readLine()) != null) { list.add(line); } }catch (IOException e) { System.err.println( e); } for (String line : list) { System.out.println(line); } System.out.println("以上" + list.size() + "行"); } }
拡張for文で使えばListのままで使えますが、ArrayListやLinkedListにしたい場合もあるかも知れません。引数としてLinkedListを使うようになっている場面とか。そんな場合の対処です。
1. 新しい目的のListを生成する方法はどんな時でも使えます
targetMethod(new ArrayList<String>(list));
2. もし、listがArrayListのインスタンスとわかっている時はキャストできます
targetMethod((ArrayList<String>)list);
3. 確証が持てなければ、
if (list instanceof ArrayList){ targetMethod((ArrayList<String>)list); }else{ targetMethod(new ArrayList<String>(list)); }
Files.readAllLines()で得られるListはArrayListでした。
こんな場合に備えて、メソッドを作るときには可能ならばListを引数にすれば良いという事ですね。
try-with-resources文 や Files.readAllLines()で、close()を排除できるので、過去の話ではあります。
2003年あたりでfinallyを扱った書籍や解説サイトなどを見た記憶がないのですが、確信はありません。try-with-resources文の解説で、昔はclose()をfinallyに書かなければならなかったけど...という記述を目にするようになりました。
多くの場合、IOExceptionはファイルがない場合で、その場合はcloseする必要がないし、開いた後の例外発生の場合でも、Javaのシステムが終了する時に処理をしてくれることが期待できますから、プログラム中でたくさんのファイルを開くのでなければ問題はないと考えられます。
でも礼儀正しくcloseしようとすれば、たしかにこれは面倒でした。
tryの中で BufferedReader inb= new .. のままではfinallyで inb.close(); とはできません。tryの前に BufferedReader inb; の宣言が必要になります。
BufferedReader inb; try{ inb = new BufferdReader... }catch(IOException e){ ... }finally{ inb.close(); }
さらに、「inbは初期化されていない可能性があります」といわれます。初期化しなければ参照型はnullになるはずだから=nullでは変わらないのでは...と思いながらも=nullとすると通ります。
加えて、そもそもopenしていないならcloseは必要ないので、そこも書いておきます
BufferedReader inb = null; try{ inb = new BufferdReader... }catch(IOException e){ ... }finally{ if(inb!=null) inb.close(); }
さらに、inb.close()で「例外IOExceptionは報告されません。」とコンパイラから注意を受けます。ここまで言及していない解説記事も多いので、コンパイラがより厳密に変わったのかも知れません。それに対処するにはこうなります。
BufferedReader inb = null; try{ inb = new BufferdReader... }catch(IOException e){ ... }finally{ if(inb!=null){ try{ inb.close(); }catch(IOException e){ ... } } }
プログラムとして美しくないです。そもそもopen()でなくなったものに、close()をそのままあてがっているところから問題があるといえます。openしていないのにcloseするのは、Javaからプログラム言語の学習を始める人たちには違和感は大きいと思います。disposeとかremoveとかにするべきだったでしょう。