rm /blog

IT系技術職のおっさんがIT技術とかライブとか日常とか雑多に語るブログです。* 本ブログに書かれている内容は個人の意見・感想であり、特定の組織に属するものではありません。/All opinions are my own.*

【Java】JavaでEXCELを読み込む(POI実装)

仕事でどうしてもJAVAからExcelの読取をしたくなったのでそのメモ。
POIというライブラリを使って行う。
オープンソースなので自由に使用できる。
ダウンロードは下記より。
http://poi.apache.org/
自分がDLした最新版jarには「20140208」という日付が付いている。
この記事内で記載する実装もそのjarを使っている。

POIに関しては、ググればそれなりの実装例や使い方が出てくるが、
紹介記事が古くて現在のAPI仕様とあってなかったりする。
厳密にはapacheが公開しているAPI Documentを参照するべきなのだろう。
(英語だから個人的には読めないんだけど)

 

◆EXCELからデータを読込

int row_num = 1;
int cell_num = 1;

// 1.POIのオブジェクトを定義
POIFSFileSystem poi = new POIFSFileSystem(new FileInputStream("test_excel.xls"));

// 2.WORKBOOkを定義
HSSFWorkbook wb = new HSSFWorkbook(poi);

// 3.WORKSHEETを定義
HSSFSheet sheet = wb.getSheet("Sheet1");

// 4.行を定義
HSSFRow row = sheet.getRow(row_num);

// 5.セルを定義
HSSFCell cell = row.getCell(cell_num);

// 6.セルの値を取得
String str_col_no = String.valueOf(cell.getNumericCellValue());

基本的な流れは
 行を定義⇒セルを定義⇒セルから値を取得
というかんじのようだ。
EXCELのイメージと実行例は以下の通り。

EXCELのイメージ
test_excel_img.png
◆実行例(MS-DOS)

java -classpath %JAVA_HOME%;%CD%;%CD%\poi-3.10-FINAL-20140208.jar; POITest

Success_img.png


以下注釈。

※POIのオブジェクト(POIFSFileSystem)のコンストラク
コンストラクタの引数にはFileInputStreamを渡す。この例ではカレントディレクトリにある「test_excel.xls」を指定。


※行とセルの定義
EXCELで表示される行数は1~だが、POIで指定する場合は0~から指定する。
セルに関しても同様(RC形式で表示した場合のセル番号-1~)。

ちなみに行に関しては、sheet.getRow(row_num)でHSSFRowオブジェクトを取得するわけだが、
その行内に値をもったセルが存在しない(空白セルだけの)場合はnullになる。
上記の例でいうと6行目(なので指定するrow_numは5)を指定してgetRowをすると、
取得したHSSFRowオブジェクトはnullになる。
当然次の「セル取得」でNullPointerExceptionでおちる。
逆に言うと行内のどこかのセルに何かしら値があればnullにならない。

同じことはcellにも当然いえる。
つまり「行内のどこかのセルに何かしら値がある行」を指定して
「値のないセル」を取得すると、取得したHSSFCellオブジェクトはnullになる。


※セルの値の取得の仕方
上記の例ではcell.getNumericCellValue()でセル値を取得しているが、
cellからの値の取得方法はいくつかメソッドが合って

// 6-1.数値型セルの値を取得
double double_value = cell.getNumericCellValue();

// 6-2.日付型セルの値を取得(java.util.Dateが返却型)
Date date_value = cell.getDateCellValue();

// 6-3.文字列型セルの値を取得
String str_value = cell.getStringCellValue();

// 6-4.数式としてセルの値を取得
String formula_value = cell.getCellFormula();

みたいなものが用意されている。
他にもあるらしいが正直↑くらいのもんで十分である。
ただ、EXCELに設定された「セルの書式」に”著しく”反するメソッドで値を取得しようとするとおちる。
上記の例でいうと、
B2セル(2014/6/30と設定された日付書式のセル)を取得する場合、
getNumericCellValue(数値型で取得)とgetDateCellValue(日付型で取得)に関しては問題ないが
getStringCellValue(文字列で取得)やgetCellFormula(数式として取得)で値を取得しようとすると
java.lang.IllegalStateExceptionで発生して落ちる。
よく調べてないのであるのかどうかはわからないが、
「とりあえず落ちずにひとまず値を取得するメソッド」というのは用意されてないらしい。
添付のJAVAソースには自作してみた残念なメソッドがあるので参考にしてほしい。
「残念な」理由は
 ・1セルに対して複数の型の値が返却され得るので
   本来意図している値の書式は何か?というのが(メソッドの返却値だけからでは)判断しづらい
 ・処理の順番からしてgetNumericCellValueで落ちると空マップが返却される。
といった点だ。

というか自作してから知ったんだけども
HSSFCellクラスにはセルの「型」を示すフィールドが存在しているので(よく考えれば当然か)
取得前にセルの型をチェックして使うメソッドを使い分けるのが本来の使い方なのだろう。

// 空白セル型
int cell_blank = cell.CELL_TYPE_BLANK;
// 真偽値型(TRUEかFALSE)
int cell_boolean = cell.CELL_TYPE_BOOLEAN;
// エラー型(#N/Aみたいなやつかな?)
int cell_error = cell.CELL_TYPE_ERROR;
// 数式型
int cell_formula = cell.CELL_TYPE_FORMULA;
// 数値型
int cell_numeric = cell.CELL_TYPE_NUMERIC;
// 文字列型
int cell_string = cell.CELL_TYPE_STRING;

// そのセルの型を取得
int cell_type = cell.getCellType();

個人的によくやるのだが、
シート全体を文字列にしてドキュメントを記述している場合だとこのやり方では全セル「文字列」になり、
シート内の全セルでStringの値が取得できるようになる一方
取得した値の評価や変換等を全てjava側で実装しなくてはならなくなる。
まあこういうケースは得てして表形式の資料ではなく設計書とか補足資料とかの類なので
あえてPOI使って操作する必要性はないのかもしれないが。


※数式の評価
ちょっと調べてみたのだが、EXCEL上に記述されている数式を
「数式の文字列」ではなく「数式の計算結果」で取得することはできないようだ。
上記の例だとB5セルを「B2 + B3」で取得することはできるが、
B2=2014/6/30、B3=1として2014/6/30+1=「2014/7/1」として値を取得することはできない。
「計算する機能はあくまでEXCELに備わっているものだから」というのが背景にあるらしい。
例えばEXCELを1行ずつ舐めていくようにする場合、
シート内に「最大行番号」みたいのを計算してもっておき、
for文の終了条件にそれを指定しようとしても、「最大行番号の計算式」しか取れない。
このPOI実装を経験する際、結構ほしい機能だったのでこれは残念だった。
頑張れば自作できそうなもんだが、そこまでやる気力は当然ない。
有志による今後のバージョンアップに期待したい。


POIは、使いこなせばEXCELを購入しなくてもEXCELを扱えるようなフリーソフトが作れそうだが、
手間を考えると正直そこまでやるようなもんでもなさそうだ。
個人的にはjavaという点ではマクロより使いやすいのだが
簡単な操作をするくらいならマクロの方が手っ取り早いと感じた。
暇なときにもう少しやり方を突き詰めてみよう。