質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.34%

  • Java

    16766questions

    Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

CSVファイルから読み込んで郵便番号から住所を検索したい

解決済

回答 5

投稿

  • 評価
  • クリップ 0
  • VIEW 2,205
退会済みユーザー

退会済みユーザー

前提・実現したいこと

現在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ページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 5

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.

このプログラム自体は問題なく動作しますが、今後、別のプログラムを作成する際には注意してみることをお勧めします。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/12 19:50

    そうそう。staticで定義してるが気持ち悪いというのを指摘し忘れてました。
    インスタンスごとにcsvのデータを持てないので部品として使えないなぁと思ってました。
    とはいえ、Practiceなので・・・まぁいっかとも。

    キャンセル

  • 2017/06/14 22:28

    細かく説明ありがとうございます。非常に参考になりました。

    キャンセル

+3

CSVファイルの名称を見るに、日本郵便(郵便局)の提供している郵便番号データを使用していると推察します。

あれ、単純に1行で収まりきらないことがあるので、それも加味しないと正確性が落ちます。
対象が福岡県だと、826-0043 とか複数行になってなかったかな。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/12 15:56

    手元のをみたら重複してました!
    40206,"826 ","8260043","フクオカケン","タガワシ","ナラ(アオバチョウ、オオウラ、カイシャマチ、カスミガオカ、ゴトウジニシダンチ、ゴトウジヒガシダンチ、ノゾミガオカ、","福岡県","田川市","奈良(青葉町、大浦、会社町、霞ケ丘、後藤寺西団地、後藤寺東団地、希望ケ丘、",0,0,0,0,0,0
    40206,"826 ","8260043","フクオカケン","タガワシ","マツノキ、ミツイゴトウジ、ミドリマチ、ツキミガオカ)","福岡県","田川市","松の木、三井後藤寺、緑町、月見ケ丘)",0,0,0,0,0,0

    キャンセル

  • 2017/06/12 16:03

    この場合、町域部分だけ次の行と合体させて処理しないといけないんですよ。
    奈良(青葉町、大浦、会社町、霞ヶ丘、後藤寺西団地、後藤寺東団地、希望ヶ丘、松の木、三井後藤寺、緑町、月見ヶ丘)
    となるのですが、本来これは()内を、で分割して、奈良青葉町、奈良大浦…のように分けるべきだったりします(つまり1郵便番号に対して複数町域が該当する)

    キャンセル

  • 2017/06/12 16:07 編集

    自分の回答にも書きましたが、やはり実際に使う場合は入力補助の意味合いが強いですよね・・・(一意に決まるわけではないという点で)

    キャンセル

  • 2017/06/14 22:32

    回答ありがとうございます。仰る通り日本郵便が提供しているファイルです。郵便番号が重複する場合の処理なども今後考え実装していきたいと思います。

    キャンセル

+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]) ...


みたいな感じです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/14 22:30

    回答ありがとうございます。参考にさせていただきました。

    キャンセル

+2

斜め下の回答ですが・・・

CharsetにSJISを使っておられます。日本の住所にはJIS第一・第二水準に含まれない漢字を用いたものもあると聞きます。よってCharsetは使えない漢字があるかどうか気にしなくても済むUTF-8、もしくはせめてWindows_31Jにしておいてはいかがでしょうか。

稀少地名漢字リスト


訂正:tacsheavenさんにご指摘いただいたように、郵便局提供のCSVならSJISでよさそうです。失礼いたしました。

このままでは回答が空になるので、さらに斜め下のトリビア的な話を・・・

郵便局の住所ファイルには若干落とし穴があると聞いたことがあります。住所が「XX周辺」というような変則的な表記が一部にあるそうです。どのみち町名までしか出ない入力補助的な意味合いなので、人が編集する際に訂正すれば済むだけの話ではあります。また郵便番号に確か一部重複があったと思います。あまり役に立つ情報でなくて恐縮ですが・・・

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/12 15:07

    蛇足ながら、UTF-8のテキストを読み込むならBOMコード('\uFEFF')はスキップするようにしておくと無用なトラブルを避けられると思います。UTF-8のBOMの存在は標準仕様としての地位が確立されてないようで残念ながらJavaのIOライブラリーでは無視してくれないのです。

    キャンセル

  • 2017/06/12 15:12

    これ、元の CSV が日本郵便の提供しているやつでしょう。
    提供データ自体がシフトJIS(MS漢字)で、しかも文字セットとしては JIS X0208-1983 を使用していると明言されています(表現不能な場合はひらがなで書いている)

    キャンセル

  • 2017/06/12 15:33

    ご指摘ありがとうございます。
    郵便局のやつなんですね。あいにくKEN_ALL.CSVしか知らなかったので自前のファイルなのかなと勘違いしていました。JIS X 0208の範囲ならSJISでよいですね!

    キャンセル

  • 2017/06/14 22:34

    回答ありがとうございます。仰る通り、このファイル自体なかなか取り扱いが難しくて難儀しておりました。今後参考にさせていただきます。

    キャンセル

+2

同じく斜め下に(ω・

readCSV() の エラー判定で、エラーが出た時に、CSVのどの行でエラーになっているかがわからなくなりますので、CSVのエラー行を出すと良いでしょうか。

あとは readCSVのreturnする結果は void ではなく、HashMap<Integer, String> で返すようにし、実行側でこれを受け取るほうが何かと拡張しやすいと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/14 22:32

    回答ありがとうございます。今後実務で使う上で拡張性なども意識していきたいと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.34%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • Java

    16766questions

    Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。