20191011のJavaに関する記事は5件です。

SparkとCassandraでデータ連携

はじめに

データ分析では、Apache Spark + Cassandraの組み合わせて実装する選択肢もよくあります。

Apache Sparkとは

Apache Sparkはとても有名なデータ分析ツールです。
image.png

Access data in HDFS, Alluxio, Apache Cassandra, Apache HBase, Apache Hive, and hundreds of other data sources.

RDD(Resilient Distributed Dataset)とDataFrameとDataSetなど。。。

出典:https://spark.apache.org/

Cassandraとは

CassandraはNoSQLのワイドカラム型のデータベースです。
image.png

Manage massive amounts of data, fast, without losing sleep

出典:http://cassandra.apache.org/

特に最初からスケーラビリティの考慮していますので、クラスターが簡単にできます。

CSVファイルデータをCassandraに保存するサンプル

Sparkはいろんな機能がありますが、CSVをCassandraに保存するサンプル作成してみます。

users.csvというサンプルファイルを作成

image.png

Gradleのプロジェクトにライブラリ導入

build.gradle
dependencies {
    // https://mvnrepository.com/artifact/org.scala-lang/scala-library
    compile group: 'org.scala-lang', name: 'scala-library', version: '2.11.12'

    // https://mvnrepository.com/artifact/org.apache.spark/spark-core
    compile group: 'org.apache.spark', name: 'spark-core_2.11', version: '2.3.4'

    // https://mvnrepository.com/artifact/com.datastax.spark/spark-cassandra-connector
    compile group: 'com.datastax.spark', name: 'spark-cassandra-connector_2.11', version: '2.4.1'

    // https://mvnrepository.com/artifact/org.apache.spark/spark-sql
    compile group: 'org.apache.spark', name: 'spark-sql_2.11', version: '2.3.4'

    // https://mvnrepository.com/artifact/org.apache.spark/spark-streaming
    compile group: 'org.apache.spark', name: 'spark-streaming_2.11', version: '2.3.4'

}

CSVからCassandraに保存し、DBから取得してみる。

CsvReader.java
package com.test.spark;

import com.datastax.driver.core.Session;
import com.datastax.spark.connector.cql.CassandraConnector;

import java.util.List;

import org.apache.log4j.Logger;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.Column;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SaveMode;
import org.apache.spark.sql.SparkSession;

public class CsvReader {

    private static final Logger logger = Logger.getLogger(CsvReader.class);

    public static void main(String[] args) {

        // Spark設定
        SparkConf conf = new SparkConf();
        conf.setAppName("CSVReader");
        conf.setMaster("local[*]");
        conf.set("spark.cassandra.connection.host", "192.168.10.248");
        conf.set("spark.cassandra.connection.port", "9042");

        // Cassandraのkeyspaceとテーブル名
        String keyspace = "sample";
        String tableUser = "user";
        String userCsv = "C:\\data\\spark\\users.csv";

        JavaSparkContext sc = new JavaSparkContext(conf);
        try {
            SparkSession sparkSession = SparkSession.builder().master("local").appName("CSVReader")
                    .config("spark.sql.warehouse.dir", "file:////C:/data/spark").getOrCreate();

            // Cassandraのコネクション
            CassandraConnector connector = CassandraConnector.apply(sc.getConf());

            try (Session session = connector.openSession()) {
                // keyspaceある場合は削除する
                session.execute("DROP KEYSPACE IF EXISTS " + keyspace);

                // keyspaceを作成する
                session.execute("CREATE KEYSPACE " + keyspace
                        + " WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}");

                // テーブルを作成する
                session.execute("CREATE TABLE " + keyspace + "." + tableUser
                        + "(user_id TEXT PRIMARY KEY, user_name TEXT, email_address TEXT, memo TEXT)");
            }

            // CSVからデータを取得する
            // テーブル定義に合わせるため、カラムのASも重要
            Dataset<Row> csv = sparkSession.read().format("com.databricks.spark.csv").option("header", "true")
                    .option("encoding", "UTF-8").load(userCsv).select(new Column("ユーザーID").as("user_id"),
                            new Column("氏名").as("user_name"), 
                            new Column("メールアドレス").as("email_address"),
                            new Column("備考").as("memo"));

            // Cassandraに保存
            csv.write().format("org.apache.spark.sql.cassandra")
                    .option("header", "true")
                    .option("keyspace", keyspace)
                    .option("table", tableUser)
                    .option("column", "user_id")
                    .option("column", "user_name")
                    .option("column", "email_address")
                    .option("column", "memo")
                    .mode(SaveMode.Append)
                    .save();

            // Cassandraからデータを読み出す
            Dataset<Row> dataset = sparkSession.read().format("org.apache.spark.sql.cassandra")
                    .option("keyspace", keyspace)
                    .option("table", tableUser).load();

            // データセットから配列を取得する
            List<Row> asList = dataset.collectAsList();
            for (Row r : asList) {
                logger.info(r);
            }
        } catch (Exception e) {
            logger.error(e);
        } finally {
            sc.stop();
            sc.close();
        }
    }
}

Cassandraのデータ

image.png

JAVA側の取得したユーザーデータ

19/10/11 23:18:27 INFO CsvReader: [A000002,yamada.bb@test.com,入社10年目,山田 三郎]
19/10/11 23:18:27 INFO CsvReader: [A000004,tanaka.bb@test.com,入社3年目,田中 次郎]
19/10/11 23:18:27 INFO CsvReader: [A000003,tanaka.aa@test.com,入社5年目,田中 一郎]
19/10/11 23:18:27 INFO CsvReader: [A000001,yamada.aa@test.com,入社1年目,山田 太郎]

基本操作などの詳しい資料はガイドにあります。
Spark Programming Guide: https://spark.apache.org/docs/2.2.0/rdd-programming-guide.html

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SpringBootのREST APIでRequestBodyでの受け取り方を調べてみる

環境

Java8
SpringBoot 2.1.9.RELEASE

前提と共有

  • Jacksonでデシリアライズを行うが、基本Setterは使わない方針
  • Jacksonでデシリアライズを行う場合、オブジェクトに定義したフィールド可視性がpublicな場合Getter/Setterは不必要で、protected以下の場合は必要だが、今回はpublicで定義しない方針
  • 出来るだけイミュータブルを目指す

サンプルコード

Stringのフィールド定義のみ

送信JSON: {"id": "1", "name": "名前"}

/**
 * Getterで書く
 */
public class Sample {

    String id;
    String name;

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}
/**
 * コンストラクタで書く
 */
public class Sample {

    String id;
    String name;

    public Sample(String id, String name) {
        this.id = id;
        this.name = name;
    }
}

intのフィールド定義のみ

送信JSON: {"id": 1, "age": 20}

/**
 * Getterで書く
 */
public class Sample {

    int id;
    int age;

    public int getId() {
        return id;
    }

    public int getAge() {
        return age;
    }
}
/**
 * コンストラクタで書く
 */
public class Sample {

    int id;
    int age;

    public Sample(int id, int age) {
        this.id = id;
        this.age = age;
    }
}

ちなみにInteger型を使用すると、null許容ができます(憤怒)
{"id": null, "age": 20}

public class Sample {

    Integer id;
    int age;

    public Sample(Integer id, int age) {
        this.id = id;
        this.age = age;
    }
}

結果: IntSample{id=null, age=20}

booleanのフィールド定義のみ

Getterで実装することはできなかった
なのでコンストラクタで書くやり方のみ
送信JSON: {"isHoge": true, "isFuga": true}

/**
 * コンストラクタで書く
 */
public class BooleanSample {

    boolean isHoge;
    boolean isFuga;

    public BooleanSample(boolean isHoge, boolean isFuga) {
        this.isHoge = isHoge;
        this.isFuga = isFuga;
    }
}

Boolean型の場合、デシリアライズできない場合はnullになる

String、int、boolean全部のせ定義

String、int、booleanと検証したが、booleanが混ざるとコンストラクタが必須になるようなので、Getterでの書き方は検討できない。よってコンストラクタの定義しかできない模様。

一応Getterでやってみた
送信JSON: {"name": "きぐり", "age": 24, "isPerson": true}

/**
 * Getterで書く
 */
public class Sample {

    String name;
    int age;
    boolean isPerson;

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public boolean isPerson() {
        return isPerson;
    }
}

結果: Sample{name='きぐり', age=24, isPerson=false}
booleanの値がバインドされず非常に残念な結果です...。

/**
 * コンストラクタで書く
 */
public class Sample {

    String name;
    int age;
    boolean isPerson;

    public Sample(String name, int age, boolean isPerson) {
        this.name = name;
        this.age = age;
        this.isPerson = isPerson;
    }
}

結果: SIBSample{name='きぐり', age=24, isPerson=true}
にっこりです。

罠だと思うところ(未解決)

今までのサンプルコードは共通してフィールド定義を2つ用意してきたことにお気づきでしょうか。このフィールド定義が1つ且つコンストラクタでデシリアライズする場合には、本来の使用の仕方とは違いますが、@JsonCreator@JsonPropertyを使用しないと例外になってしまう。

例外パターン

送信JSON: {"name": "きぐり"}

public class Sample {

    String name;

    public Sample(String name) {
        this.name = name;
    }
}

例外内容

{
    "timestamp": "2019-10-11T03:25:50.724+0000",
    "status": 400,
    "error": "Bad Request",
    "message": "JSON parse error: Cannot construct instance of `sandbox.Sample` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `sandbox.Sample` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)\n at [Source: (PushbackInputStream); line: 1, column: 2]",
    "path": "/s"
}

成功パターン

送信JSON: {"name": "きぐり"}

public class Sample {

    String name;

    @JsonCreator
    public Sample(@JsonProperty("name") String name) {
        this.name = name;
    }
}

まとめ

完全にJacksonの説明をしていた、タイトル詐欺にならないかなこれ。
あと罠の部分、詳しい人誰か教えて下さい。
「コンストラクタなど使わなくとも、@JsonPropertyをフィールドに付与すればいいのでは?」と言われそうですが、インスタンス生成の方法は一つに絞る且つ見易さなどで僕はコンストラクタ引数につけてます。もっといい方法あれば募集中!!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Beginner Contest 132 D問題について

はじめに

今回は、AtCoderコンテンツのABC132_D問題において、思案したもののWA(不正解)が覆らず、なぜ自分の実装では誤った出力になってしまうか、その根本的なところを解決したいと思い、投稿致しました。なお、問題に掲載されている入力例1、例2についてはクリアしています。

問題文(引用)

引用ページ:https://atcoder.jp/contests/abc132/tasks/abc132_d
---引用始まり---
K 個の青いボールと N−K個の赤いボールがあります。同じ色のボールは区別が不可能です。すぬけ君と高橋君はこれらのボールで遊んでいます。

まず、すぬけ君が、N個のボールを左から右に一列に並べます。

次に、高橋君は、これらのうち K
個の青いボールのみを回収します。高橋君は、1 回の操作で連続して並ぶ青いボールを何個でも回収することができます。高橋君は、常に K個の青いボールを回収するのにかかる操作の回数が最小になるように行動します。

K個の青いボールを回収するために高橋君がちょうど i 回操作をする必要があるボールの並べ方は何通りあるでしょうか。 1≤i≤K をみたす i それぞれについて答えを計算し、10^9+7で割った余りを答えてください。

制約
1≤K≤N≤2000

入力
入力は以下の形式で標準入力から与えられる。

N K

出力
i行目 (1≤i≤K) に高橋君がちょうど i 回操作をする必要があるボールの並べ方の総数を 10^9+7 で割った余りを出力せよ。
---引用終わり---

実装(WA判定)

Main.java
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        int K = sc.nextInt();
        sc.close();

        for(int i = 1;i <= K;i++) {
            long combi1 = combination(N-K+1,i);
            long combi2 = combination(K-1,i-1);
            System.out.println(combi1*combi2%1_000_000_007);
        }
    }

    public static long combination(int i,int j) {
        if(j==0) {
            return 1;
        }
        return combination(i-1,j-1)* i  / j;
    }
}

以上、ご指摘いただけますと嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

これから開発チームのリーダーになるあなたへ~最低限の標準化について~

はじめに

私がこれまでに教えていただいた内容や学んだ内容を元に、
チームの標準化でやってよかった(やればよかった)内容をまとめてみました。

チームをまとめるためにどんなルールが必要なのか、ある程度汎用的に書きました。
これからリーダーをやる方やメンバーの方も、「これは使えそう」と思っていただければ幸いです。
また、「こんなことをやってました」などございましたら、是非コメントいただければと思います。

※あくまで、私の個人的見解です。
プロジェクトによってはマッチしない場合があると思いますが、参考程度に見ていただければと思います。

詳細設計

設計書は外部の目に触れる可能性が高いため、見栄えの面でのブレをなくすのに力を入れましょう。
また、テンプレを作成し、それをベースに作成するようにしたとして、
テンプレがプロジェクトとマッチしていないと感じたならば、定期的な見直しも必要です。

細かい内容は以下です。

記載内容の統一

設計書間で記載内容が統一取れていない場合、読みづらさに直結してきます。

しかし、がちがちにルールで固めてしまうと現状の設計書では表現できない処理が出てくるため、
ある程度の汎用性が求められます。

その解決策として、繰り返し出てくる処理をフォーマット化してしまうのが有効です。
大体、以下のようなインターフェイスに関わる箇所や処理をフォーマット化しましょう。
・DBアクセス
・ファイル読み込み・書き込み
・API呼び出し
・ループ
・分岐

また、忘れがちな統一をとった方が良い内容の一覧は以下です。
細かいですけど、チーム内の争いの種はこうゆうところから来てると感じます。。。

内容
同じ意味の記載 ・DBに登録する
・テーブルに〇〇をInsertする
・テーブルに〇〇を登録する
⇒できる限り具体的に、かつ設計書寄りに書くという意味では一番下がいいと思います。
汎用的に使える言葉 DAOやUtilクラスのメソッドを"API"と一律で呼ぶ
⇒認識齟齬が生まれる可能性があるため、問題があれば統一しましょう。
クラス名・メソッド名 和名と英名の混在
ex)
和名:会員情報登録
英名:registerCustomerInfo
⇒ここが統一取れていないとリファクタができなくなる。。。

コンポ分割方針

使用する言語の特性を考えてコンポ分割(どの単位でクラスを作成するか)を考えましょう。
例えば、Javaならオブジェクト指向がベースになります。

オブジェクト指向の基本的な考え方は、カプセル化・継承・多態性です。
現実世界のモノ(クラス)に例え、それがどのような情報を持ち(プロパティ)、どのような振る舞い(メソッド)をするのかをイメージし、分割を行っていきます。

しかし、実際に作成するプログラムは、現実世界のモノに例えられないようなものばかりです。
例えば、もらったデータを加工しDBに登録する処理が大半で、あとは外部連携などのバリエーションが加わるだけの場合、
「そんなもん現実になくね?」と絶望すると思います。

そこで一旦、データに着目します。
データはテーブルに蓄えられ、必要な時に取得されます。
そのテーブルは正規化され、論理と物理に分かれた設計がなされています。

この論理的に分割されたテーブルを軸にクラスを作成していくのがベストです。

要は、そのデータを専門に扱う窓口(モノ)として考えるのです。
すべてのケースにはこの例はあてはまりませんが、
適切な機能配置は、能動的なメソッド配置が可能になると思います。

インデント

インデントは、仕様書の骨格になりますが、ある程度は好みでいいと思います。

ただし、以下のインデント例より深くなる場合は、可読性の問題(またはそれ以外の問題)のため、処理の見直しをおすすめします。
また、分岐やループなどは字下げするように定めておくことで、
プログラムとの整合性が取れるようになります。

インデント例
1.
___1).
______(1).
_________➀.
____________a.
_______________a).
__________________(a).

(トピック)
新規着任者がきたら、大項番から処理を書くように指示しましょう、
項番ごとにレビューをしてあげることで手戻りと同一項番におけるタイトルの統一性が図れます。

TODO

TODOをどこに残すかは決めておきましょう、
これを先に決めないとメンバーがTODOを残し忘れる可能性があります。
また、TODOの内容もまとめることが可能ならば、TODOに対してコード採番・管理するのも手です。
これにより、同一起因のTODOに対してのアプローチが機械的にできるメリットがあります。

実装

自分が書いたコードを何十年もあとに解読する、、、

こんなの絶対覚えてないですよねw
覚えられないなら、覚えなくていい仕組みを作りましょう!

リーダブルコードやデザインパターンを読んで、
各言語に合わせたスマート(可読性の高く・変更に強い・各関数の振る舞いが推測可能な)な実装を強制するルールを作ります。

以下はその代表例です。

命名規則

「名前は大事」と耳にタコができるまで指摘された記憶があります。。。
基本的な内容はルール化しましょう!

決めないでバラバラになるより、統一しとけばリファクタで何とかなる!

1.動詞と名詞

メソッド名、クラス名、変数名を付けるにあたり、おそらく使うであろう内容は規約として定義します。

ex)
登録:register
更新:update
登録:insert

2.文字のつながり

ハイフンがあったり、なかったり、、、なんてことは無いようにしましょう。

ex)
定数:スネークケース(かつ、すべて大文字)
⇒ MAX_RANGE
変数名・メソッド名・クラス名:キャメルケース
⇒ getUserInfo
URL・ファイル名:スパイナルケース
⇒ user-info

3.ヘボン式が英語か

日本人が故、誤字や誤用を回避するためにヘボン式を採用することもあるかと思います。
どちらでもいいと思いますが、ヘボン式の誤字は人に見つかると恥ずかしいです。

WEBツールなどあるので有効活用しましょう。

DRY原則(OAOO原則)

一度書いたら、もう書くな(関数化しろ!)
多分こんな内容です。

eclipseなどの開発エディタには、関数化(リファクタリング)の機能が備わっています。
使用しているツールの機能をある程度調べておくといいかもしれません。

関数化・定数化

その処理が、メソッド固有かクラス固有か(はたまた、アプリケーション共通の処理か)を考えてもらいましょう。
先にUtilクラスや定数クラスを作ることで、さぼりに対してアプローチできると思います。

また、その処理固有の機能であっても、詳細設計のインデント大・中項番レベルで関数化するよう指示してもいいと思います。(スパゲッティコードのレビューはとても大変です。)

コメント

詳細設計のインデント大・中項番レベルを丸々張り付けていいと思います。

ただし、大事なのは詳細設計との関連性です。
意味のないコメントはレビュー時に指摘しましょう。

テスト

テストファーストという言葉もあるぐらい、大事なフェーズ。
また、観点が揺らぐと網羅性についてレビューしづらくなります。

基本的に、
自分が書いたコード部分についてはホワイトボックス
呼び出すコンポーネントについてはブラックボックス
をUTやITなどフェーズに合わせて考えていくのがベースになるでしょう。

余力があれば、全データバリエーションを網羅したテストデータをテーブルごとに作成する、
業務有識者にデータを作ってもらうなども有効な手段になります。

レビュー

セルフチェック

セルフチェックシートを作りましょう。
今回ルール化したものをチェックシートに記載するだけで、かなりの数の手戻りが減ります。

クロスチェック

レビュー指摘の内容は大体、同じ内容になります。
また、誰かがやったミスは、ほかの誰かもするでしょう。
必ず全体共有をし、何かしらの方法(ツールなど)で対策するようにしましょう。

特定処理にのみ存在する指摘でも、手戻りが大きければ、セルフチェックシートに記載することを検討してもいいかもしれません。

その他

実際作業に入る前に、忘れてたり、ついつい後回しにしてしまうタスクを片付けておきましょう。

必要資料をまとめる

どこにあるのか探している間に、なんでその資料が欲しかったのかわからなくなった。。。

そんなことありませんでした?(私はありましたw)
フレームワークやライブラリーのドキュメントについて、リンク集を作りましょう。

また、新規参画者用に最低限読むべき内容を色分けしておくといいです。
(あとできっと役に立ちます。)

フォルダを作成する

MECEや時系列で洗い出しを行い、将来使用するであろうフォルダを前もって作りましょう。
(後から作るのは大変です。)

.
├─00_共通
│  ├─
│  │
│  └─
├─01_詳細設計
├─02_実装
├─03_UT
└─04_IT

情報共有の環境を作る

今まで紹介した中で一番大事だと思います。

素敵なツールがあるのであれば、課金してでも使いましょう!
注意として、情報の共有ツールを複数作らないでください。
情報の発信元がメールであれ、一元管理できる仕組みを考えてください。

また、共有内容にはわかりやすいタイトルを付けるのをお勧めします。
※エラー内容など、一言タイトルをつけると会話の面でも楽になります。

まとめ

メンバーが最大限の力を発揮するためには、明確な目標とどこまでの裁量でタスクを行えばいいかを示す必要があります。
そのため、タスクの裁量を決めるためのルール作りはとても大事な工程になります。

また、メンバーの犯したミスは、そもそもの仕組みのせいです。
そのようなルールを作らなかったリーダーに責任があります。

そのため、ルールはめちゃくちゃ大事です。

策定するのはとても難しそうに感じますが、考え方はシンプルです。
ルールによって以下が得られるように作りましょう。
・シンプルになること
・定量的になること

これを突き詰めて守っていけば、大道から大きく外れることはありません!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

StringUtilはどんなものがいいのか

これさえあればなんでもできるみたいなメソッド欲しい

欲しくない?

どんなものがいいのか

型が違うとかはもうどうしようもないので
Stringに関してならなんでも、安全に処理できる
みたいなことを目的とします

とりあえずやってみる

StringUtils.java
package utils;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.Arrays;
import java.util.HashMap;

/**
 * Stringならまかせとけクラス
 * 
 * @author Mr.汎用性の極み
 *
 */
public class StringUtils {

    /** 複数 文字列引数 */
    private static final List<String> MULTIPLE_STRING = Arrays.asList("a", null, "b", "", "3");

    /**
     * 実験用
     * 
     * @param args
     */
    public static void main(String[] args) {
        System.out.println(extraction(MULTIPLE_STRING));
    }

    /**
     * {@code null}をブランクに変換.<br>
     * {@code null}でないものはそのまま.
     * 
     * @param str 入力される文字列
     * @return 変換結果
     */
    private static String nullToBlank(String str) {
        return Objects.toString(str, "");
    }

    /**
     * {@code null}をブランクに変換.<br>
     * {@code null}でないものはそのまま.
     * <p>
     * 引数自体が{@code null}または要素数0の場合,空のリストを返す.
     * 
     * @param list 入力される{@code List<String>}
     * @return 変換結果
     */
    private static List<String> nullToBlank(List<String> list) {
        if (list != null)
            return list.stream().map(StringUtils::nullToBlank).collect(Collectors.toList());
        return Arrays.asList();
    }

    /**
     * 入力された{@code List<String>}において<br>
     * {@code null}と空文字を排除する
     * 
     * @param list 入力される{@code List<String>}
     * @return 変換結果
     */
    private static List<String> nullBlankSmasher(List<String> list) {
        return nullToBlank(list).stream().filter(v -> !v.isEmpty()).collect(Collectors.toList());
    }

    /**
     * {@code Map<String, Integer>}に対して
     * <p>
     * <ul>
     * <li>入力された文字列が数値変換可能な場合<br>
     * {空文字, 変換後の数値}を返す<br>
     * <li>入力された文字列が数値変換不可能な場合<br>
     * {変換前の文字列, {@code null}}を返す
     * </ul>
     * 
     * @param map 格納用のマップ
     * @param str 数値変換対象の文字列
     */
    private static void store(Map<String, Integer> map, String str) {
        try {
            map.put("", Integer.parseInt(str));
        } catch (NumberFormatException e) {
            map.put(str, null);
        }
    }

    /**
     * 数値変換可能なものを変換し、そうでないものは不変のまま仕分ける.
     * <p>
     * <ul>
     * <li>入力された文字列が数値変換可能な場合<br>
     * {空文字, 変換後の数値}を返す<br>
     * <li>入力された文字列が数値変換不可能な場合<br>
     * {変換前の文字列, {@code null}}を返す
     * </ul>
     * 
     * @param list 入力される{@code List<String>}
     * @return 仕分け結果
     */
    private static Map<String, Integer> toInteger(List<String> list) {
        Map<String, Integer> map = new HashMap<String, Integer>();
        nullBlankSmasher(list).forEach(v -> {
            store(map, v);
        });
        return map;
    }

    /**
     * 入力された文字列のリストに対して以下の操作を行う
     * <p>
     * <ul>
     * <li>{@code null}と空文字を除去
     * <li>数値変換可能な文字列を数値に変換
     * <li>変換できなかった文字列のリストと,数値変換後の数値のリストのマップを返す
     * </ul>
     * 
     * @param list 入力される{@code List<String>}
     * @return 仕分け結果
     */
    private static Map<List<String>, List<Integer>> extraction(List<String> list) {
        Map<List<String>, List<Integer>> map = new HashMap<List<String>, List<Integer>>();
        map.put(toInteger(list).keySet().stream().filter(v -> !v.isEmpty()).collect(Collectors.toList()),
                toInteger(list).values().stream().filter(Objects::nonNull).collect(Collectors.toList()));
        return map;
    }

}

なにこれ

とりま作ったのは

入力値:List
出力値;Map, List>
処理:
入力された文字列のリスト内の要素で数値変換可能なものがあった場合数値に変換
変換できないものはそのままに
文字列のリストと数値のリストに分け、マップに格納する

意識したこと

入力値がnull,要素数0でも大丈夫なような対応
一気に作らず、部分ごとに分けてつくり
一部分のみ使いたいときに対応できるようにした

だめなところ

Object型やOptionalを用いてもっと汎用的な窓口にしてみたかったりする

まぁ難しいですね、、

万能薬欲しい

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む