CSVファイルから読み込んで郵便番号から住所を検索したい
解決済
回答 5
投稿
- 評価
- クリップ 0
- VIEW 4,856

退会済みユーザー
前提・実現したいこと
現在Javaでデータフォーマットの勉強をしています。
CSVファイルから郵便番号を読み込んで、一致する住所を表示するプログラムを書いたのですが、もっとこうすれば分かりやすいなど改善点があれば教えていただきたいです。
該当のソースコード
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Scanner;
public class CsvPractice {
static HashMap<Integer, String> zipCodeDataMap = new HashMap<>();
public static void main(String[] args) {
CsvPractice cp = new CsvPractice();
cp.readCSV();
while (true) {
System.out.print("郵便番号:");
Scanner scanner = new Scanner(System.in);
try {
String postalCodeStr = scanner.next();
if (postalCodeStr.equals("exit")) {
System.out.println("終了します");
return;
}
Integer postalCode = Integer.valueOf(postalCodeStr);
String Address = zipCodeDataMap.get(postalCode);
if (Address != null) {
System.out.println("郵便番号 : " + postalCode);
System.out.println("住所 : " + Address);
} else {
System.out.println("郵便番号に対応する住所はありません");
}
} catch (NumberFormatException e) {
System.out.println("郵便番号を入力してください");
continue;
}
}
}
/**
* csvファイルからの読み込み
*
*/
void readCSV() {
try {
List<String> readLineList = Files.readAllLines(Paths.get("data/40FUKUOK.CSV"), Charset.forName("SJIS"));
for (String readLine : readLineList) {
String[] values = readLine.split(",");
if (values == null || values.length != 15) {
System.out.println("エラー");
continue;
}
Integer PostalCode = Integer.valueOf(values[2].replaceAll("\"", ""));
String Address = values[6].replaceAll("\"", "") + values[7].replaceAll("\"", "")
+ values[8].replaceAll("\"", "");
zipCodeDataMap.put(PostalCode, Address);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
補足情報(言語/FW/ツール等のバージョンなど)
Java eclipse
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+3
「分かりやすい」
という観点からは、特に問題ないと思います。
以下、その他の観点について気づいた点をコメントさせていただきます。
scanner 変数について
Scanner.next()
メソッドは繰り返し実行できますので、while
文の内部でnew Scanner(System.in)
を実行する必要はありません。
while
文の直前に 1度だけ実行すれば十分です。
また、現状のプログラムはScanner.close()
を実行していないため、リソースリークを引き起こす可能性があります。
https://en.wikipedia.org/wiki/Resource_leak
JDK1.7以上を使用しているのであれば、以下のようにtry-with-resources
文を使用すると良いでしょう。
try (Scanner scanner = new Scanner(System.in)) {
while (true) {
System.out.print("郵便番号:");
try {
String postalCodeStr = scanner.next();
...
} catch (NumberFormatException e) {
System.out.println("郵便番号を入力してください");
continue;
}
}
}
String.split() メソッドについて
CSVファイルから読み込んだ行のなかに
,,1300015,,,,東京都,墨田区,横網,,,,,,
のように、15番目の要素(最後の要素)が空の行があると、
このプログラムは「エラー」と表示してその行を読み飛ばしてしまいます。
なぜなら、引数を 1つだけ取るsplit()
メソッドは、
左から見て最後の空でない要素(上の例の場合、「横網」)以降の空の要素を、結果の配列に含まないからです。
https://docs.oracle.com/javase/jp/7/api/java/lang/String.html#split(java.lang.String)
結果として得られる配列には後続の空の文字列は含まれません。
最後の要素に空文字を許容しない場合は今のままで問題ありませんが、
そうでない場合、以下のように第二引数に負の値を指定してやると良いでしょう。
String[] values = readLine.split(",", -1);
https://docs.oracle.com/javase/jp/7/api/java/lang/String.html#split(java.lang.String, int)
limit パラメータは、このパターンの適用回数を制御するため、結果として得られる配列の長さに影響を及ぼします。
(中略)
n が負の値の場合、このパターンの適用回数と配列の長さは制限されません。
また、split() メソッドの戻り値が null になることはありません。
上と同じリンク
入力されたどの部分とも式が一致しない場合、配列は 1 つの要素 (つまり、この文字列) だけを保持します。
そのため、入力チェックのvalues == null
は不要です。
String.replaceAll() メソッドについて
このメソッドは、指定した正規表現に一致する文字列を置換するメソッドです。
https://docs.oracle.com/javase/jp/7/api/java/lang/String.html#replaceAll(java.lang.String, java.lang.String)
指定された正規表現に一致する、この文字列の各部分文字列に対し、指定された置換を実行します。
このプログラムでは単に"
を削除するだけで 正規表現が必要なわけではないので、String.replace()
メソッドで十分です。
https://docs.oracle.com/javase/jp/7/api/java/lang/String.html#replace(java.lang.CharSequence, java.lang.CharSequence)
リテラルターゲットシーケンスに一致するこの文字列の部分文字列を、指定されたリテラル置換シーケンスに置き換えます。
readCSV() メソッド内の catch句について
FileNotFoundException
などの例外をcatch
していますが、結局e.printStackTrace()
を実行するだけのため、
このメソッドの呼び出し側(このプログラムの場合、main()
メソッド)からは、メソッドが正常に実行できたかどうかを知る術がありません。
実際、readCSV()
メソッド内で例外が発生しても、このプログラムは何事もなかったかのように郵便番号の入力を要求してしまいます。
以下のように、発生した例外をthrow
して、メソッドの呼び出し側にエラーが起きたことを検知できるようにしてやったほうが、
メソッドの設計として優れていると私は考えます。
void readCSV() throws Exception {
try {
...
} catch (IOException | NumberFormatException e) {
e.printStackTrace();
throw e;
}
}
zipCodeDataMap フィールドについて
同じクラスの中に定義してあるとはいえ、インスタンスメソッド(readCSV()
)がスタティックフィールド(zipCodeDataMap
)の状態を変更するのは、
クラス設計の観点からはよろしくありません。
http://findbugs.sourceforge.net/bugDescriptions.html#ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD
This is tricky to get correct if multiple instances are being manipulated, and generally bad practice.
このプログラム自体は問題なく動作しますが、今後、別のプログラムを作成する際には注意してみることをお勧めします。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+3
CSVファイルの名称を見るに、日本郵便(郵便局)の提供している郵便番号データを使用していると推察します。
あれ、単純に1行で収まりきらないことがあるので、それも加味しないと正確性が落ちます。
対象が福岡県だと、826-0043 とか複数行になってなかったかな。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+3
そんなに問題ないと思います。
私が変更をするとしたら。
リストファイル名の固定をやめる。
コンストラクタか、readCSVの引数で設定する。CSVの構造のマジックナンバーを削除
郵便番号のデータファイルの形式等
にある定義をして、15や2、6、7、8を利用しないようにします。Addressの保持方法
連結したものをもたずに生のString配列で保持をして、取得時に加工するようにします。
ちなみに、変数名(address)のように小文字から始めることが慣例的に多いと思います。zipCodeDataMapへのアクセッサー
生データを返す場合(get)や、元の実装のように加工して出す場合(getAddress)用に、生のHashMap
をさわらせるのでなく、アクセッサーを用意します。
まぁクラス名がPracticeなところから汎用性は考えてないとは思いますが・・・ダブルクォートの削除について
まぁ、みればわかるのですが、メソッドを切り出すとロジックに名前がつけれます。
String address = values[6].replaceAll("\"", "") + ...
よりも
String address = cutQuote(values[CsvPractice.PREFECTURES])
+ cutQuote(values[CsvPractice.xxxxx]) ...
みたいな感じです。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+2
斜め下の回答ですが・・・
CharsetにSJISを使っておられます。日本の住所にはJIS第一・第二水準に含まれない漢字を用いたものもあると聞きます。よってCharsetは使えない漢字があるかどうか気にしなくても済むUTF-8、もしくはせめてWindows_31Jにしておいてはいかがでしょうか。
訂正:tacsheavenさんにご指摘いただいたように、郵便局提供のCSVならSJISでよさそうです。失礼いたしました。
このままでは回答が空になるので、さらに斜め下のトリビア的な話を・・・
郵便局の住所ファイルには若干落とし穴があると聞いたことがあります。住所が「XX周辺」というような変則的な表記が一部にあるそうです。どのみち町名までしか出ない入力補助的な意味合いなので、人が編集する際に訂正すれば済むだけの話ではあります。また郵便番号に確か一部重複があったと思います。あまり役に立つ情報でなくて恐縮ですが・・・
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+2
同じく斜め下に(ω・
readCSV() の エラー判定で、エラーが出た時に、CSVのどの行でエラーになっているかがわからなくなりますので、CSVのエラー行を出すと良いでしょうか。
あとは readCSVのreturnする結果は void ではなく、HashMap<Integer, String> で返すようにし、実行側でこれを受け取るほうが何かと拡張しやすいと思います。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.10%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2017/06/12 19:50
インスタンスごとにcsvのデータを持てないので部品として使えないなぁと思ってました。
とはいえ、Practiceなので・・・まぁいっかとも。
2017/06/14 22:28