20210121のJavaに関する記事は14件です。

Java基礎 シリアライズとは

前回、ファイルオブジェクトを生成し文字を書き込んだり読み込んだりする処理を書きました。
今回は、プログラマーが作ったクラスで生成したオブジェクトを入出力する方法について書きたいと思います。

シリアライズ

生成したインスタンスをバイト列に変換したり(シリアライズ)、またそのバイト列をインスタンスに戻したり(デシリアライズ)する仕組みです。
この仕組みによって、入出力ストリームを使用してファイルに保存したりネットワークで伝送したりできます。
シリアライズは直列化とも呼ばれます。

オブジェクトをシリアライズ可能にする

通常のクラスをシリアライズ可能にするには、java.io.Serializableインターフェースを実装する必要があります。このインターフェースはマーカーインターフェースのため、オーバーライドすべきメソッドは持ちません。

下のプログラムは、PersonクラスがSerializableインターフェースを実装し、シリアライズ可能なオブジェクトにしています。

Person.java
import java.io.*;

class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name = "";
    private int age = 0;
    private transient String language = "Japanese";

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getInfo() {
        return name + ":" + age;
    }

    public String getLang() {
        return language;
    }
}

入出力するためにストリームを生成する

シリアライズ可能なオブジェクトの入出力は、ObjectOutputStreamクラスObjectOutputStreamクラスを使用します。これらのストリームを使って、インスタンスを外部に保存したり、外部から復元したりできるようになります。

以下のプログラムは、前述のPersonクラスで生成したインスタンスを、ストリーム経由でファイルに保存、そこから復元をしています。

Main.java
public class Main {
    public static void main(String[] args) {
        try (ObjectOutputStream outObject = new ObjectOutputStream(new FileOutputStream("object.txt"));
            ObjectInputStream inObject = new ObjectInputStream(new FileInputStream("object.txt"));) {

            outObject.writeObject(new Person("Taro",22));

            Person exP = (Person)inObject.readObject();
            System.out.println(exP.getInfo());
            System.out.println(exP.getLang());

        } catch (IOException | ClassNotFoundException e) {
        }
    }
}

「object.txt」というパスを持ったFileOutputStream、FileInputStreamを生成し、それらをもとにObjectOutputStream、ObjectInputStreamを生成します。

そして、writeObject()メソッドで指定したオブジェクト(直接"太郎、22歳"というPersonを生成)を、ObjectOutputStreamを経由して「object.txt」に書き込んでいます。

さらに、readObject()メソッドを使用して、「object.txt」に保存されたインスタンスを復元しPersonクラスの変数に格納しています。readObject()メソッドの戻り値はObject型であるため、exPに代入する前にPersonクラス型へキャストしないといけません。

※readObject()メソッドはClassNotFindExceptionをスローする可能性があるため、それに対応した例外処理を書く必要があります。

ちなみに、上記のプログラムを実行すると、以下の結果が出力されます。

Taro:22
null

2行目のnullが出力される理由としては、後述の「transient」キーワードを参照ください。

シリアライズ対象外の変数

Serializableインターフェースを実装したクラスでも、以下の変数はシリアライズできません。
transientで指定された変数
・static変数
・Serializableインターフェースを実装していないクラス型の変数

transientはデシリアライズ時にnullの状態で復元されます。先ほどのプログラムで、nullが返されたのもこれが理由です。
またstatic変数は、static領域に値を持つため、復元時はそこの値が使われます。

シリアライズの継承

シリアライズ可能なクラスのサブクラスは「implements Serializable」を書かなくても暗黙的にシリアライズ可能です。

また、スーパークラスがSerializableインタフェースを実装していない状態で、サブクラスがSerializableインタフェースを実装している場合、デシリアライズの際にスーパークラスのコンストラクタが呼び出されてインスタンス化されます。

シリアルバージョンID

Personクラスのフィールドに、「serialVersionUID」という定数を宣言しています。
この定数はシリアルバージョンIDというもので、復元前後でクラスが変更されていないか識別するために使います。(eclipseではこれを書かないと警告が出ます。)

クラス修正時にシリアルバージョンIDを修正することで、修正前に保存したインスタンスを修正後のクラスで復元しようとすると例外(InvalidClassException)が発生します。
これによって、矛盾した状態での復元がなされた時、それを知らせることができます。


<参考>
書籍
・スッキリわかるJava入門 実践編 第2版
・オラクル認定資格教科書 Javaプログラマ Gold SE 8
ホームページ
・マコトのおもちゃ箱 ~ぼへぼへ自営業者の技術メモ~
 http://piyopiyocs.blog115.fc2.com/blog-entry-205.html

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

Java の UUID は素晴らしい!

ユニークのキーを生成したいと思って、この記事を見つけました。

https://qiita.com/kawasima/items/6b0f47a60c9cb5ffb5c4

まさにこの記事の通りです。素晴らしい。

結論から言うと、UUID version4 が良さそうです。
UUID 以前から知ってましたが、バージョンまであるのは知りませんでした。

じゅあ、どうやってバージョンを調べるの?と調べたら、この記事

http://xawa99.blogspot.com/2013/07/UUID-Version.html

xxxxxxxx-xxxx-Vxxx-xxxx-xxxxxxxxxxxx
なるほど!

Java 8 で検証してみました。

import java.util.UUID;

public class App {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            UUID uuid = UUID.randomUUID();
            System.out.println(uuid);
        }
    }
}

結果

3e817ae3-c770-4e43-afc3-4076e7bf1917
8622a167-d6e0-49db-bf7c-879251d7706d
89edf330-7421-4c71-8066-ced5d3d6669e
8a63ab75-de69-45c2-ab7c-9fd788e21a2d
40fd3de0-b8f6-43a5-bdcb-a6dcb44cabf9
f1be93b0-93e4-44f9-bbec-867502e6c9d9
c26ed1d8-2b79-4dbf-b088-ee7e80752018
5591cacd-3d02-4397-908b-37cedab6f760
8fde8770-7776-4171-9b9f-22fde1db5ab5
3c0b6030-64e4-4c73-a1d7-f56eaa6ecfa0

java 8 は既にバージョン4ですね!

以上!

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

【Spring】@SessionAttributesでセッションオブジェクトでオブジェクトを持ち回りたいのに・・・

No primary or single public constructor found for interface java.util.List

このエラーに5時間位かかりましたね。意味わからんかったー
結局セッションオブジェクトの扱い方がわかっていなくて、コントローラーのメソッドの引数に@ModelAttributeなしでList<Integer> ListInt,みたいに渡してて、これがアカン買ったようです。

大まかなイメージ

  1. セッションに置きたい名前を準備
  2. その名前にオブジェクトを結びつける
  3. model.addAttributeでセッションに保存する
  4. メソッドの引数に@ModelAttributeをつけてセッションから取得

実装する場所

コントローラーの上に

@SessionAttributes( value="〇〇")

〇〇にはオブジェクトの名前(属性名)

コントローラーの中の一番上に@ModelAttributeでオブジェクトを準備

@ModelAttribute( value = "〇〇" )
public △△ setUp〇〇(){
 return new △△();
}

セッションのコンストラクタ?
これで、〇〇という名前の△△型のオブジェクトが準備される

メソッドの引数に@ModelAttributeでセッションから取得

public method(
 @ModelAttribute("〇〇") △△ 〇〇,
 Model model
){

modelも必要でした

メソッド内で

model.addAttribute("〇〇", 〇〇)

ここでセッションの中の"〇〇"という名前でオブジェクトを保存することができます

別のコントローラーでは@ModelAttribute無しでも持ち回れたんだけど・・・

基本がわかっていないからこういうことになるんですけど、まだまだ駆け出しなのでうまく行ったものを正解だと思って同じようにやってるんだけど、ちょっと変わってくるとエラーが出て「さっきはできてたやん!!」って叫びたくなりませんか??!!

まあ勉強にはなるんですけど、、、、

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

KotlinでMapとListをfor文で操作しよう!

Key:Valueが1:1のMapをListにする

  // 1:1
  val map = mapOf("key1" to "val1", "key2" to "val2", "key3" to "val3")
  // mapのすべてのvalueが格納される
  val valueList = ArrayList(map.values)
  var list: MutableList<String>
  list = ArrayList()
  list.addAll(valueList)

1:1のMapを条件分岐でListに格納する

  val map = mapOf("key1" to "val1", "key2" to "val2", "key3" to "val3")
  val valueList = ArrayList(map.values)

  // key1のvalueがarray変数に格納される("val1")
  var araay = map.get("key1").toString()
  // itemは0からはじまる
  for (item in valueList.indices){
      // val1がvalueListにある場合はListに追加
      if (araay.contains(valueList[item])){
          list.add(valueList[item])
      }
  }

Mapのvalueを配列にしてネストさせる(1:n)

  // 1:n
  val mapArray = mapOf("keyX" to arrayOf("valA","valB", "valC").map { num -> num },
                       "keyY" to arrayOf("valD","valE").map { num -> num })
  val valueArrayList = ArrayList(mapArray.values)

1:nのMapを条件分岐でListに格納する

  // 1:n
  val mapArray = mapOf("keyX" to arrayOf("valA","valB", "valC").map { num -> num },
                       "keyY" to arrayOf("valD","valE").map { num -> num })
  val valueArrayList = ArrayList(mapArray.values)

  var araay = mapArray.get("keyX").toString()
  for (item in valueArrayList.indices) {
      // ネストしている配列の要素数だけループする
      for (i in valueArrayList[item].indices) {
          if (araay.contains(valueArrayList[item].get(i))) {
             list.add(valueArrayList[item].get(i))
          }
      }
  }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java メモ

列挙で比較する時など

Arrays.asList({A,B,C}).contains

を使う

Optional.isPresent()

はなるべく使わず

Optional.map ~ orElse orElseThrow

する

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

初心者が1から始めるJava開発-Vol.3- クラス、メソッド編

今回はJavaの最も重要な概念であるクラス、メソッドについて説明していきます。

私もまだまだプログラマーとしては発展途上で、こういう概念の話は言語化がむずい。
分かりにくい概念の話が多いですが、実装を経て「あーそういう事か」と分かればOKです。

もちろん、Hands On形式で実際にコードも書いていきますよ!


Javaの初心者向けおすすめ本

スッキリわかるJava入門 第3版 (スッキリシリーズ)


クラス

オブジェクト指向の言語にはおおむねあると思っていますが、
Javaでは、ファイル1つのことを指す場合が多いです。

Javaでは
XXXX.javaというファイルを作った ⇔ XXXXというクラスを作った
という同値関係が、おおむね成立すると考えてもらってよいです。

※Pythonなどの場合は1ファイルの中に複数クラスを定義できたりします。

逆にFortranやCobolなどの言語はオブジェクト指向ではないので、
1ファイルにすべての処理を順に記載するといったこともあります。

クラスという概念は、1つの処理を複数のクラスに分割して、各クラスに処理を委譲するという事であり、
プログラムを簡単にかつ効率的に開発するうえで重要な考え方です。

オブジェクト指向

オブジェクト指向とは、簡単に言うと、「処理とデータを同時に部品として扱う」ようなことと考えてください。

料理で例える

カレーを作るという処理を考えたときに

  • カレーを作れと支持する人
  • 野菜を洗う人
  • 野菜を切る人
  • 肉を切る人
  • 煮込む人
  • 盛り付ける人

のようなステップ(人)によって実現します。
家で作るときは一人でやると思いますが、
プログラムの世界では、分業させた方がきれいで読みやすく保守しやすくなる方が多いので分けました。
この時「処理とデータを同時に部品として扱う」に従えば

  • 野菜を洗う人

では

クラス:「野菜を洗う人」
データ:「野菜」
処理:「洗う」

となります。

  • 「野菜」はJavaではフィールドというもので表現します。(必ずフィールドが必要なわけではありません)
  • 「洗う」はJavaではメソッドというもので表現します。

これによって、
クラス内に、フィールドとメソッドを定義することで「野菜を洗う人」というオブジェクトを定義するためのクラスが実装できるわけです。

※たとえが悪いと思うので後出の実装部分で実際のJavaで詳しく見ていきましょう。
 ちなみに、フィールドは例では使っていませんが。。

メソッド

Javaのメソッドは前出の通り、処理を表現するものです。

メソッドを使うメリットとして

  • 処理を使いまわすことが可能で、コードが見やすく、書きやすい
  • 修正箇所が限定的になる

例を見た方が良いので後出の実装部分で実際のJavaで詳しく見ていきましょう。

プログラム実装

それでは、今回は、渡した数字に対して、四則演算をして、集計をするようなプログラムを書いていきます。

仕様

1、2つの数字、a,bに対して、四則演算をしていく(今回は足し算(+2)と掛け算(×2)だけにします)
2、四則演算した2つの数字を最後に足し算して合計値を表示する。

それでは書いていきます。
まずはCalculationMainクラスを作成しましょう。
パッケージはどこでも構いません。

クラスの作り方を忘れた場合はVol.2を参照してください

package calculation;

public class CalculationMain {

}

今回はsrc/main/javaの下にcalculationパッケージを作成し、そこにクラスを作成しました。

次に2つの数字a,bを定義していきます。

package calculation;

public class CalculationMain {

    public static void main(String[] args) {
        int a = 1;
        int b = 2;
    }
}

お決まりのpublic static void メイン あーぐすを実装します。これもメソッドです。
その中にa,bの2つのint(数値)型を定義します。今回は1,2でいきます。

続いて、四則演算をしていきますが、ここで役割を分けるために四則演算専用クラスを作っていきます。
私と同じようにしている場合はcalculationパッケージの下にserviceパッケージを作成し、その下にCalculationクラスを作成します

  • calculationを選択し右クリックから New > other 選択します

パッケージ作成.png

  • packageと入力し Java > packageを選択しNextを押下します。

package.JPG

  • Nameにcalculationはすでに入っていると思うのでcalculation.serviceとします。

service.JPG

  • Finishで作成完了です。

すると、calculationのしたにserviceというフォルダらしきものができたことがわかると思います。

つりー.JPG

このように見えていない場合は、package explorerのところにある3点アイコンみたいなやつを選択して以下の画像のようにHierarchicalを選択すればOKです。ただここに関しては個人の好き好みがあると思うのでどちらでもOKです。

冷えらる気かる.png

ではクラスを作りましょう。serviceパッケージの下に以下のようなクラスができたかと思います。

package calculation.service;

public class Calculation {

}

足し算のメソッド

ここに四則演算をするためのメソッドを作っていきます。

まずは足し算用のメソッド

package calculation.service;

public class Calculation {

    public int add(int num) {

    }
}

新出の知識を抑えてください。

メソッドは

public [返り値の型] メソッド名前([引数の型 引数名,..])

で記述します。

[返り値の型]:voidは返り値なし、それ以外は指定に従って値を返します。
[引数の型 引数名,..]:型と引数の名前を指定します。複数指定が可能です。

ここで、今add(int num)の部分に赤線が出ていると思います。
マウスを持っていくと、なぜ赤線が出ているか理解できます。

そのとおり、返り値にvoid以外を指定するとreturnつまり値を返す記述が必要になります。
選択肢の中にあるAdd return statumentを選択してください。

すると、勝手にreturn num;が書かれたと思います。

package calculation.service;

public class Calculation {

    public int add(int num) {
        return num;

    }
}

続いて足し算(+2)なのでもうadd2という名前にしてしまいましょう。

そして、引数のnumに2を加算して返すように書くと

package calculation.service;

public class Calculation {

    public int add2(int num) {
        return num + 2;

    }
}

これで2を足すメソッドができました。

掛け算のメソッド

次に掛け算のメソッドを書いていきます。

名前はmultiply2としましょう。

package calculation.service;

public class Calculation {

    public int add2(int num) {
        return num + 2;
    }

    public int multiply2(int num) {
        return num;
    }
}

知ってる人はいますかね?掛け算は*を使うので以下で完成です。

package calculation.service;

public class Calculation {

    public int add2(int num) {
        return num + 2;
    }

    public int multiply2(int num) {
        return num * 2;
    }
}

最後に、仕様2の集計するメソッドを作ります。名前はaggregateとかで。

package calculation.service;

public class Calculation {

    public int add2(int num) {
        return num + 2;
    }

    public int multiple2(int num) {
        return num * 2;
    }

    public int aggregate(int a, int b) {
        return a + b;
    }
}

もう余裕ですよね?

2つの値を足しすので、引数には2つの値を指定します。

それでは最後にオブジェクト指向らしさを出す作業、CalculationMainのa,bに対し、このメソッドを追加って最後まで実装していきます。

CalculationMainを開いてください。

まずCalculationクラスの中のメソッドを使う場合、外部クラスで直接呼べないので、呼ぶために以下のように書きます。

package calculation;

import calculation.service.Calculation;

public class CalculationMain {

    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        Calculation calc = new Calculation();
    }
}

new を使って書いています。現場では「Newする」と言ったりもしますが、
Calculationクラスの形を持ったオブジェクトをインスタンス化するというのがたぶん正しい表現です。
要するに初期化して使えるようにするみたいなイメージです。

前出の料理の例でいえば

  • 辛口のカレーを作る
  • 甘口のカレーを作る

それぞれでNewが必要になる可能性があります。
(同じ鍋を使ったら甘口なのに中辛になっちゃうから一回鍋を洗うか、新しい鍋を使う的なイメージ)

フィールドがなくメソッドだけのクラスをNewする時には改めてNewしないで使うことができる場合も多いです。

ここら編のインスタンス化の詳細は参考書籍に譲ります。(直近影響はないと思うので、いつかちゃんと勉強して整理するつもり)

そして、a,bそれぞれに仕様通りの演算をします。

package calculation;

import calculation.service.Calculation;

public class CalculationMain {

    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        Calculation calc = new Calculation();

        int aAdd2=calc.add2(a);
        int bAdd2=calc.add2(b);
    }
}

まず、a,bにそれぞれ2を足します。
Calculation型のcalcというオブジェクトのadd2メソッドを使う場合はこのように書きます。

同様に掛け算と、集計もやってみます。

package calculation;

import calculation.service.Calculation;

public class CalculationMain {

    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        Calculation calc = new Calculation();

        int aAdd2 = calc.add2(a);
        int bAdd2 = calc.add2(b);

        int aMultiply2 = calc.multiple2(aAdd2);
        int bMultiply2 = calc.multiple2(bAdd2);

        int result = calc.aggregate(aMultiply2, bMultiply2);
    }
}

こんな感じで書けると思います。

最後にこんっソールに結果を表示したいので
java
System.out.println(result);

を入れて完成です。

package calculation;

import calculation.service.Calculation;

public class CalculationMain {

    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        Calculation calc = new Calculation();

        int aAdd2 = calc.add2(a);
        int bAdd2 = calc.add2(b);

        int aMultiply2 = calc.multiple2(aAdd2);
        int bMultiply2 = calc.multiple2(bAdd2);

        int result = calc.aggregate(aMultiply2, bMultiply2);

        System.out.println(result);
    }
}

結果はどうなるか予想しておきましょう。

a = 1 , b = 2

(a + 2)* 2 = 6
(b + 2)* 2 = 8

result = (a + 2)* 2 + (b + 2)* 2 

より

 result = 14

では実行してみてください。実行方法は前回Vold.2と同様です。

けっか.JPG

確かに14になりましたよね?

a,bの値を変えて試してみてください。

最後に

今回学んでほしかったのは

  • CalculationMainクラスが、「処理を実行する役割」
  • Calculationクラスが、「計算を実行する役割」

であること

  • add2メソッドが計算の中でも2を加算する役割
  • multiply2メソッドが計算の中でも2を掛け算する役割
  • aggregateメソッドが計算の中でも2つの数字を足す役割

というように細かく処理の担当を分けたりすることで、
例えば、
「今、最初に2足してるけど、3にしといて」という仕様変更が入っても
メソッドに変えておけば1か所直せばいいのです。メソッドになってないと2か所直す必要があります。

今回のコードは簡単ですが、もっと大規模になってくると、仕様変更で膨大な修正をしなければならないみたいなことも起きてくるので、
- 適切にメソッドを切り出す(出来るだけ小さくする)
- クラスやメソッドの役割を明確にしておく

ということを学んでもらえていればいいかなと思います。

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

~今さらながらJavaで関数型プログラミングを学習してみた~(第2部)

「関数型プログラミングとJava8での取り組みと追加API説明」を中心テーマとした第1部はすでに投稿されています。
 第1部へのリンク

はじめに

第1部では関数型プログラミング自体の概説と、Java8で新たに取り入れられたAPI+そのサンプルを中心に紹介しました。
本第2部では「実際Javaシステム開発においてどの程度関数型プログラミングを取り込むべきなのか?」を中心テーマとして少し現実的なサンプルも交えて検討していきたいと思います。
あわせて関数型プログラミングの説明でよく用いられている「パイプライン、高階関数、カリー化」という用語についてもJavaベースでの説明を行っていきます。
なお、内容には主観にもとづく部分や、個人的指向が入ったプログラムスタイルになっている場合がありますので了承お願い致します。
関数プログラミング特有の語句については第1部の後尾部分に理解した範囲での簡単な説明を記述していますので参考にして下さい。

関数型プログラミングにおける代表的手法

中心テーマに入る前に、まず関数型プログラミング説明においてよく用いられている代表的手法についてJavaベースで説明しておきたいと思います。
代表的手法としてパイプライン、高階階数、カリー化(部分適用)を対象としました。

(1) パイプライン
Javaでの関数型プログラミングにおけるパイプライン処理とは一般的にはStreamAPIでのパイプライン処理を意味します。
このパイプライン処理はストリームパイプラインとも呼ばれています。
ストリームパイプラインは中間操作(0個以上)と終端操作を連結して構成した処理となっています。
以下のサンプルではListをstream化し、その後filter(条件に一致したデータを抽出)処理やsort処理を行って、終端処理として結果戻り値であるList作成を作成したり、各要素の出力を行っています。
StreamAPIではこれらの一連の処理を014、016、018のようにパイプライン形式で1行に記述することが可能になっています。
この方法はJavaにおける関数型プログラミングスタイルでは一般的によく使用されます。

PipelineTest.java
001 package functiontest.unit;
002 import java.util.Arrays;
003 import java.util.Comparator;
004 import java.util.List;
005 import java.util.stream.Collectors;
    /**
    * Streamパイプラインテストクラス
    */
006 public class PipelineTest {
007 public static void main(String[] args) {
008   List<Food> foods = Arrays.asList(new Food[] {new Food("sibuya", "ラーメン", 1700), new Food("sibuya", "チャーハン", 800),
009     new Food("sibuya", "餃子", 500),  new Food("sinjuku", "スパゲティ", 1000), new Food("sinjuku", "オムライス", 900),
010     new Food("sinjuku", "ステーキ", 2500)});
011   execute(foods);
012 }
    /**
    *パイプラインテスト実行
    */
013 public static void execute(List<Food> foods) {
      //フィルター処理→リスト化
014   List<Food> filteredList = foods.stream().filter(fobj -> fobj.getStore().startsWith("sib")).collect(Collectors.toList());
015   System.out.println(filteredList);
      //ソート処理→リスト化
016   List<Food> sorttedList = foods.stream().sorted(Comparator.comparing(Food::getPrice)).collect(Collectors.toList());
017   System.out.println(sorttedList);
      //フィルター処理+ソート処理→要素出力処理
018   foods.stream().filter(fobj -> fobj.getPrice() >= 1000).sorted(Comparator.comparing(Food::getName)).forEach(System.out::println);
019 }
020 }
Food.java
    /**
    *Food情報格納クラス
    */
001 package functiontest.unit;
002 public class Food {
003   String store;
004   String name;
005   int price;
006 Food( String store, String name, int stock){
007   this.store = store;
008   this.name=name;
009   this.price = stock;
010 }
011 public String getStore() {
012   return store;
013 }
014 public String getName() {
015   return name;
016 }
017 public int getPrice() {
018   return price;
019 }

020 }

(2) 高階関数
高階関数とは他の関数を引数あるいは結果として返すことができる関数(メソッド)のことです。
Java8ではmap, flatMap, filter, reduce, forEach, anyMatch, allMatch関数などが標準で用意されています。
引数で設定される関数の処理内容についてはラムダ式を用いて実装・記述することができます。
また、自作関数インターフェイスを作成すれば高階関数を自作することもできます。
高階関数の目的・メリットは関数の一部処理を引数(関数)で渡すことによって特定処理(引数(関数)以外の部分)を共通化して整理することができるという点です。
つまり、大枠の処理を関数として共通化しておき、具体的な一部処理を引数(関数)で渡すということができるようになります。
カリー化もこの高階関数を利用して実現しています。
以下に6つの高階関数使用サンプル(最後のサンプルは対象外)を提示して概説致します。
・pickupAndCountメソッド(015~027):指定した範囲条件にマッチした値の件数を算出しています。
 016~024が命令型スタイルでのプログラミングで、025~026が関数型スタイルでのプログラミングとなっています。
 026でfilter高階関数を使用してラムダ式で条件を指定しています。
・reduceSumメソッド(028~032):配列要素の合計値を算出します。
 030でreduce高階関数を使用して合計値を算出しています。
・calSquareメソッド(033~038):二乗値を算出します。
 035で二乗値算出式を定義して標準Functionインターフェイスに設定しています。
 037のmapメソッドではこの標準Functionインターフェイスを引数として渡しています。
・calFiveTimesUsingInterfaceメソッド(039~042):標準Functionインターフェイスを使用して5倍値を算出しています。
 040でgetFiveTimesメソッドを使用した5倍値算出式を定義して標準Functionインターフェイスに設定しています。
 041でこのFunctionインターフェイスのapplyメソッドで5倍値を算出しています。
・calFiveTimesUsingLambdaメソッド(043~052):ラムダ式を使用して5倍値を算出しています。
 044~046でexecuteメソッドに渡す引数としてラムダ式(5倍算出式)を定義しています。
 049~052でexecuteメソッド内容を定義し引数の標準Functionインターフェイスでのapplyメソッドを実行しています。
・calIntegerAndCompareメソッド(053~069):2倍と5倍の値を比較し大きい方の値を抽出しています。
 054&055で2つのメソッドを使用した2倍&5倍値算出式を定義して標準Functionインターフェイスに設定しています。
 056でcalAndCompareメソッドを実行して2倍&5倍値の比較結果を取得しています。
 059~063でcalAndCompareメソッドの処理内容を定義しています。
 064~069でgetDoubleメソッド及びgetFiveTimesメソッドの具体的処理内容を定義しています。
・concatThreeStringメソッド(070~077):自作関数インターフェイスを使用して3つの文字列を結合しています。
 071で3文字列結合する自作関数インターフェイス(メソッド名はconcatString)を定義しています。
 072でconcatStringメソッド3文字列結合を実行しています。

HigherOrderFunctionTest.java
001 package functiontest.unit;
002 import java.util.Arrays;
003 import java.util.List;
004 import java.util.function.Function;
    /**
    * 高階関数テストクラス
    */
005 public class HigherOrderFunctionTest {

006 public static void main(String[] args) {
007   pickupAndCount();
008   reduceSum();
009   calSquare();
010   calFiveTimesUsingInterface(2);
011   calFiveTimesUsingLambda(1);
012   calIntegerAndCompare(3);
013   concatThreeString();
014 }
    /**
    * 高階関数(filter)を使用したリスト内における数値範囲条件にマッチした件数算出
    */
015 private static void pickupAndCount() {
016   List<Integer> list = Arrays.asList(new Integer[] {38, 20, 40, 32, 36});
017   System.out.println("命令型スタイルでのプログラミング");
018   int count = 0;
019   for (int num : list) {
020     if (num ==  20 || (num >= 30 && num <= 39)) {
021       count++;
022     }
023   }
024   System.out.println("命令型count:" + count);
      //
025   System.out.println("高階関数を使用したプログラミングスタイル(StreamAPI)");
026   System.out.println("関数型スタイルcount:" + list.stream().filter(n -> n == 20 || (n >= 30 && n <= 39)).count());
027 }
    /**
    * 高階関数(reduce)を使用した配列要素合計値算出
    */
028 public static void reduceSum() {
029   List<Integer> nums = Arrays.asList(new Integer[] {0, 1, 2, 3, 4});
030   int sum = nums.stream().reduce(0, (t, x) -> t + x);
031   System.out.println("関数型スタイルsum:" + sum);
032 }
    /**
    * 高階関数(map)を使用した二乗値算出
    */
033 public static void calSquare(){
034   List<Integer> nums = Arrays.asList(new Integer[]{0, 1, 2, 3, 4});
      //Functionインターフェイスを定義
035   Function<Integer, Integer> square = (x) -> { return x * x; };
      //StreamAPIのmapを使用して二乗算出
036   System.out.println("関数型スタイルsquare:");
037   nums.stream().map(square).forEach(System.out::println);
038 }
    /**
    * Functionインターフェイスを使用した5倍値算出
    */
039 public static void calFiveTimesUsingInterface(Integer value) {
040   Function<Integer, Integer> fivetimes = (x) -> { return getFiveTimes(x);};
041   System.out.println("関数型スタイルfivetimes_interface:" + fivetimes.apply(value));
042 }
    /**
    * 高階関数(execute)を使用した5倍値算出
    */
043 public static void calFiveTimesUsingLambda(Integer value) {
044   Integer fivetimes = execute(value, t -> {
045     return getFiveTimes(value);
046   });
047   System.out.println("関数型スタイルfivetimes_lambda:" + fivetimes);
048 }
    /**
    * 引数の関数インターフェイス(ラムダ式形式で定義)を実行するメソッド
    */
049 public static <R> R execute(Integer value, Function<Integer, R> fnc) {
050   R rtnval = fnc.apply(value);
051   return rtnval;
052 }
    /**
    * 高階関数(calAndCompare)を使用した2倍と5倍の値を比較し大きい値を抽出
    */
053 public static void calIntegerAndCompare(Integer value) {
054   Function<Integer, Integer> times1 = (x) -> { return getDouble(x);};
055   Function<Integer, Integer> times2 = (x) -> { return getFiveTimes(x);};
056   int rtnval = calAndCompare(value, times1, times2);
057   System.out.println("関数型スタイル(値比較)rtnval:" + rtnval);
058 }
    /**
    * 引数のFunctionインターフェイスを実行し値を比較するメソッド
    */
059 public static Integer calAndCompare(Integer value, Function<Integer, Integer> fnc1, Function<Integer, Integer> fnc2) {
060   int rtnval1 = fnc1.apply(value);
061   int rtnval2 = fnc2.apply(value);
062   return Math.max(rtnval1, rtnval2);
063 }
    /**
    * 具体的な処理を実行するメソッド(値を2倍する)
    */
064 public static Integer getDouble(Integer value) {
065   return value * 2;
066 }
    /**
    * 具体的な処理を実行するメソッド(値を5倍する)
    */
067 public static Integer getFiveTimes(Integer value) {
068   return value * 5;
069 }
    /**
    * 自作インターフェースを使用した3つの文字列を結合
    */
070 public static void concatThreeString() {
      //IOriginalFuncの定義と実行
071   IOriginalInterface originalInterface = (str1, str2, str3) -> {return str1+str2+str3;};
072   System.out.println(originalInterface.concatString("AAA", "BBB", "CCC"));
073 }
    //自作の関数型インターフェース
074 @FunctionalInterface
075 public interface IOriginalInterface {
      // String三つを引数に取る関数を定義
076   public String concatString(String str1, String str2, String str3);
077 }
078 }

(3) カリー化
関数型プログラミングにおいて理解しにくい単語の1つが「カリー化」ですが、基本だけでも理解すればそれほど難しくはありません。
カリー化とは複数の引数を受け取って処理する関数を、最初の引数を受け取って処理し、「残りの引数を受け取って処理する関数」を返す関数に変換することをいいます。
簡単簡略に言えば、関数を引数ごとに分解・分割して、それらを連続(ネスト)して処理することを「カリー化して処理すること」となります。
具体的に記述すると、引数3個の関数f(t, u, v) 関数があるとすると、この関数にt引数だけ渡して処理し、結果として引数uとvを持つg(u, v) 関数を返すこととなります。
これによって単引数を持つ関数の連続(ネスト)によって複数引数の関数を表現・記述することが可能になります。
ちなみに、純粋関数型言語の実質的標準であるHaskellにおいては原則複数引数の関数というものは定義できず、引数は1つとなっているようです。
ただ形式的(糖衣構文)には複数引数の設定は可能になっているようでこの場合には自動的にカリー化されて単引数関数を用いて処理されるようです。
(筆者はHaskell未使用なので確定的な記述はできません。)
ではなぜこのようなカリー化を行うかということですが、「カリー化」の目的は「部分適用」や「関数合成」を行う(やりやすくする)ためとなっています。
(ただ部分適用にカリー化が必須という訳ではありません。)
つまり、関数型プログラミングで関数の引数を1つにすることを原則・基本とするのは、「部分適用」・「関数合成」等の関数型プログラミング特有(限定という意味ではありません。)の処理・方法のためとも言えます。
「関数合成」は文字通り関数を合成して新しい関数を作るものです。
「部分適用」とは複数引数の関数の一部を固定化して引数を減らした関数を作ることを言い、これにより複数引数関数における不必要な処理について省略することが可能となります。
どちらもJava8以降で対応することが可能で、「関数合成」はFunctionインターフェイスでの2メソッドcomposeとandThenを利用することとなります。
ただ、開発システムのどのような処理・機能に対して、どのようにこの特有の処理・方法を適用していくかについては、純粋関数型プログラミング範疇に入り本記事の範疇を超えるのでここでは割愛としますので、他参考書を参照して下さい。
なお、以下ではJavaにおいて比較的「関数合成」よりはどちらかというと利用可能性があると思われる「カリー化」+「部分適用」についてのサンプルと説明を記述します。
・mainメソッド(001~040):カリー化及び部分適用テストのメインメソッド。
 007~009:1つの引数数値が1桁の場合1を返し、それ以外は-1を返す(カリー化をしていない)。
 010~012:2つの引数が0以上でs<eの場合1を返し、それ以外は-1を返す(カリー化をしていない)。
 013~018:2つの引数が0以上でs<eの場合1を返し、それ以外は-1を返す(カリー化して部分適用)。
  013で010で定義したITwoParameter(BiFunction)インターフェイスをカリー化しています。
  046~050でカリー化を行うcurryingメソッドを定義しています。
  014で1つ目の引数を固定してIOneParameter(Function)インターフェイスを取得し、これを使用して015そして017で2つ目の引数を設定して比較結果を求めています。
 019~022:013~018と同じ処理内容ですが一時的なインターフェイスを取得せずに実行しています。
 023~030:3つの引数が0以上でs<e<vの場合1を返し、それ以外は-1を返す。
  023~025でカリー化を行い、026で2つの引数を固定してIOneParameter(Function)インターフェイスを取得しています。
  027そして029で3つ目の引数を設定して比較結果を求めています。
 031~034:023~030と同じ処理内容ですが一時的なインターフェイスを取得せずに実行しています。
 035~040:2引数ケースと同じ処理を命令型複数引数メソッドで処理した場合とメンバー変数を使用した場合(部分適用に類似する)を記述。
 041~056:引数1,2,3個の関数インターフェイスを定義。
 057~059:メンバー変数の設定メソッド。
 060~069:引数が1つ及び2つの命令型比較メソッド。

CurryingTest.java
001 package functiontest.unit;
002 import java.util.function.BiFunction;
003 import java.util.function.Function;
    /**
    * カリー化テストクラス
    */
004 public final class CurryingTest {
005 private static Integer sint;
006 public static void main(String[] args) {
      //☆☆引数が1つのケース
      //引数数値が1桁の場合1を返し、それ以外は-1を返す(カリー化をしていないケース)
007   IOneParameter<Integer, Boolean> p1exe = (s) -> {if (-1 < s && 10 > s) {return true;} else {return false;}};
008   Boolean result = p1exe.apply(3);
009   System.out.println("IOneParameter_result:" + result);
      //☆☆引数が2つのケース
      //引数が0以上でs<eの場合1を返し、それ以外は-1を返す(カリー化をしていないケース)
010   ITwoParameter<Integer, Integer, Boolean> p2exe = (s, e) -> {if (-1 < s && -1 < e && s < e) {return true;} else {return false;}};
011   Boolean cmpResult1 = p2exe.apply(1, 2);
012   System.out.println("TwoParameter_cmpResult1:" + cmpResult1);
      //カリー化を実施し部分適用をしたケース(2ケース)
013   IOneParameter<Integer, IOneParameter<Integer, Boolean>> firstP2exe = p2exe.currying();
014   IOneParameter<Integer, Boolean> secondP2exe = firstP2exe.compare(2);
015   Boolean cmpResult2a = secondP2exe.compare(1);
016   System.out.println("TwoParameter_cmpResult2a:" + cmpResult2a);
017   Boolean cmpResult2b = secondP2exe.compare(3);
018   System.out.println("ITwoParameter_cmpResult2b:" + cmpResult2b);
      //カリー化を実施しインターフェイスを取得せずに実行したケース
019   IOneParameter<Integer, IOneParameter<Integer, Boolean>> curryingP2exe = s -> e -> {
020     if (-1 < s && -1 < e && s < e) {return true;} else {return false;}};
021   Boolean cmpResult2c = curryingP2exe.compare(1).compare(2);
022   System.out.println("CurryingTwoParameter_cmpResult2c:" + cmpResult2c);
      //☆☆引数が3つのケース
      //引数が0以上でs<e<vの場合1を返し、それ以外は-1を返す(カリー化をしないケース)
023   IThreeParameter<Integer, Integer, Integer, Boolean> p3exe = (s, e, v) -> {
024     if (-1 < s && -1 < e && -1 < v && s < e && e < v) {return true;} else {return false;}};
      //カリー化を実施し部分適用をしたケース(2ケース)
025   IOneParameter<Integer, IOneParameter<Integer, IOneParameter<Integer, Boolean>>> firstP3exe = p3exe.currying();
026   IOneParameter<Integer, Boolean> secondP3exe = firstP3exe.compare(1).compare(2);
027   Boolean cmpResult3a = secondP3exe.compare(1);
028   System.out.println("ThreeParameter_cmpResult3a:" + cmpResult3a);
029   Boolean cmpResult3b = secondP3exe.compare(3);
030   System.out.println("IThreeParameter_cmpResult3b:" + cmpResult3b);
      //カリー化を実施しインターフェイスを取得せずに実行したケース
031   IOneParameter<Integer, IOneParameter<Integer, IOneParameter<Integer, Boolean>>> curryingP3exe = s -> e -> v -> {
032     if (-1 < s && -1 < e && -1 < v && s < e && e < v) {return true;} else {return false;}};
033   Boolean cmpResult3c = curryingP3exe.compare(1).compare(2).compare(3);
034   System.out.println("CurryingThreeParameter_cmpResult4:" + cmpResult3c);
      //☆☆引数が2つのケース
      //命令型(オブジェクト指向)タイプで実行したケース
035   Boolean cmpResult4a = compareByObject (2, 1);
036   setSint(2);
037   Boolean cmpResult4b = compareByObject (3);
038   System.out.println("ObjectTwoParameter_cmpResult4a:" + cmpResult4a);
039   System.out.println("ObjectTwoParameter_cmpResult4b:" + cmpResult4b);
040 }
    /**
    * 引数が1つのFunction型インターフェイス(compareメソッド定義)
    */
041 interface IOneParameter<T, R> extends Function<T, R> {
042   default R compare(T t) {
043     return apply(t);
044   }
045 }
    /**
    * 引数が2つのBiFunction型インターフェイス(curryingメソッドでカリー化定義)
    */
046 interface ITwoParameter<T, U, R> extends BiFunction<T, U, R> {
047   default IOneParameter<T, IOneParameter<U, R>> currying() {
048     return p1 -> p2 -> apply(p1, p2);
049   }
050 }
    /**
    * 引数が3つの関数型インターフェイス(curryingメソッドでカリー化定義)
    */
051 interface IThreeParameter<T, U, V, R> {
052   R apply(T t, U u, V v);
053   default IOneParameter<T, IOneParameter<U, IOneParameter<V, R>>> currying() {
054     return p1 -> p2 -> p3 -> apply(p1, p2, p3);
055   }
056 }
    /**
    * sintメンバー変数の設定
    */
057 public static void setSint(Integer sint) {
058   CurryingTest.sint = sint;
059 }
    /**
    * 引数が1つの命令型(オブジェクト指向)タイプでの比較
    */
060 public static Boolean compareByObject (Integer e) {
061   return compareByObject (CurryingTest.sint, e);
062 }
    /**
    * 引数が2つの命令型(オブジェクト指向)タイプでの比較
    */
063 private static Boolean compareByObject (Integer s, Integer e) {
064   if (-1 < s && -1 < e && e > s) {
065     return true;
066   } else {
067     return false;
068   }
069 }

070 }

オブジェクト指向(命令型)と関数型プログラミングの比較・相違

次に、Javaオブジェクト指向(命令型)と関数型プログラミングの基本的な相違を簡単に説明してみたいと思います。
オブジェクト指向は基本的にデータと操作(関数)を一体化(クラス化)してその独立性をできるだけ高めシステムの堅牢性を向上させるという方法です。
クラスが完全に独立できればクラス単位という視点でみれば副作用排除ができるということになります(正確に言えば違いますが)。
しかし、一般的にはそのようなケースはまれで、そもそもクラス(概念)間でメッセージをやり取りするというオブジェクト指向本来の指向とは異なるため、全面的な副作用排除は難しいと言えます。
関数型プログラミングは関数(操作)単位での副作用排除(参照透過性の保証)を目指し、関数(操作)間で共有する変数をなくし、関数(操作)内での代入文も無くし、目的・出力(このデータを用いてこの出力を得る)を示して、それを実現する手続きはシステム、ライブラリやユーティリティ等に任せていくという方法であり、データと操作の一体化という指向ではなく、逆にどちらかというとデータと関数(操作)は分離していく傾向にあります。
つまりオブジェクト指向と関数型プログラミングはベースとしては相反に近い関係にあるとも言えることになり、オブジェクト指向ベースによるシステム開発に関数型プログラミングを全面的に近い形で取り入れることはほぼ困難であり、あくまで部分的・表層的な取り入れということになります。

現実的サンプルを使用したオブジェクト指向プログラミング→関数型プログラミング例

ではどの程度関数型プログラミングを取り入れることができるか、あるいは取り入れるべきなのかという事が焦点となります。
このことを考察・推察するために世の中で一般的に提示されている、「関数型プログラミングにすればこんなにコーディング量は少なくなります」、というようなごく簡単なサンプルではなく、少しだけ現実的なサンプルをオブジェクト指向(命令型)と関数型プログラミングの両方で実装して比較することにしました。
なおJava関数型プログラミングでは純粋に副作用を排除することは困難なので、
(1)メソッド外の変数を使用・代入しない(ただしコンストラクタは除外)。
(2)for文を使用しない。
(3)メソッド内でのローカル変数の代入は可能とするが極力少なくする。
という条件で作成してできるだけ副作用を少なくしています。
この現実的なサンプルではパイプラインや高階関数が複数使用されています。
現実的なサンプルは単純な商品在庫管理でメインクラス、在庫管理クラス、商品グループ別合計数及び平均値算出用ユーティリティクラスの3クラスで構成されています。
機能としては商品登録、在庫数更新(入庫/出庫)、商品グループ別合計数及び平均値算出があります。
"Object"と付加したクラスがオブジェクト指向(命令型)で、"Function"と付加したクラスが関数型プログラミングとなります。
StockObjectSampleMain:オブジェクト指向(命令型) 商品在庫管理メインクラス
StockObjectSample:オブジェクト指向(命令型) 商品在庫管理クラス
UtilityObjectSample:オブジェクト指向(命令型) 商品在庫管理用ユーティリティ
StockFunctionSampleMain:関数型プログラミング 商品在庫管理メインクラス
StockFunctionSample:関数型プログラミング 商品在庫管理クラス
UtilityFunctionSample:関数型プログラミング 商品在庫管理用ユーティリティ
処理内容はAタイプ商品の登録→在庫数チェック→入庫、Bタイプ商品の登録→在庫数チェック→入庫、全商品在庫数出力、グループ別合計数及び平均値出力としています。
グループ別合計数及び平均値算出はユーティリティクラスで算出しています。
以下5機能(メソッド)については処理形式が類似しているので標準関数インターフェイスを各々定義してexecuteFunctionメソッド(高階関数)を使用して関数引渡しで処理しています。
標準関数インターフェイスはStockFunctionSample.javaの038~076、executeFunctionメソッドは091~104で確認できます。
標準関数インターフェイスはコンストラクタで設定しています。
・商品確認:指定商品が登録されているか確認:checkGoodsメソッド
・在庫追加:入庫:stockメソッド
・在庫減少:出庫:shipメソッド
・在庫確認:指定数の在庫があるかの確認:checkStockメソッド
・商品・在庫追加:指定商品を登録し在庫数を設定:addGoods+stockメソッド
UtilityFunctionSample.javaではグループ別合計数及び平均値出力をStreamAPI(filter、flatmap、reduce、forEachメソッド使用)を用いて算出しています。

StockObjectSampleMain.java
001 package functiontest.stock;
    /**
    * オブジェクト指向(命令型):商品在庫管理メインクラス
    *   在庫情報:商品名、在庫数、商品説明の3要素で構成
    * 商品名:"Goods"+商品グループ名(1文字)+連番で構成
    */
002 public class StockObjectSampleMain {
003   private static final String[] goodsNameA = {"GoodsA1", "GoodsA2", "GoodsA3"};
004   private static final String[] goodsNameB = {"GoodsB1", "GoodsB2", "GoodsB3"};
005   private static final int[] goodsStockA = {1, 2, 3};
006   private static final int[] goodsStockB = {1, 2, 3};
007   private static final String[] goodsDescriptionA = {"商品A1", "商品A2", "商品A3"};
008   private static final String[] goodsDescriptionB = {"商品B1", "商品B2", "商品B3"};
009 public static void main(String[] args) {
010   System.out.println("開始");
      //初期化
011   StockObjectSample ss = initialize();
      //在庫追加
012   ss.stock(goodsNameA[0], goodsStockA[0]);
013   ss.stock(goodsNameA[1], goodsStockA[1]);
014   ss.stock(goodsNameA[2], goodsStockA[2]);
      //商品確認
015   System.out.println("商品確認(GoodsA1):" + ss.checkGoods(goodsNameA[0]));
      //在庫確認
016   System.out.println("在庫確認(GoodsA2):" + ss.checkStock(goodsNameA[1], 2));
      //在庫数確認
017   System.out.println("在庫数確認(GoodsA3):" + ss.getStock(goodsNameA[2]));
      //商品・在庫追加
018   update(goodsNameB[0], goodsStockB[0], goodsDescriptionB[0]).addGoods();
019   update(goodsNameB[1], goodsStockB[1], goodsDescriptionB[1]).addGoods();
020   update(goodsNameB[2], goodsStockB[2], goodsDescriptionB[2]).addGoods();
021   update(goodsNameB[0], goodsStockB[0]).stock();
022   update(goodsNameB[1], goodsStockB[1]).stock();
023   update(goodsNameB[2], goodsStockB[2]).stock();
      //在庫リスト出力
024   ss.outputStock();
      //グループ別在庫数出力
025   ss.outputStockByGroup();
026   System.out.println("終了");
027 }
    /**
    * 初期化
    */
028 private static StockObjectSample initialize() {
029   StockObjectSample ss = new StockObjectSample();
030   ss.addGoods(goodsNameA[0], 0, goodsDescriptionA[0]);
031   ss.addGoods(goodsNameA[1], 0, goodsDescriptionA[1]);
032   ss.addGoods(goodsNameA[2], 0, goodsDescriptionA[2]);
033   return ss;
034 }
    /**
    * 商品在庫情報の更新
    */
035 private static StockObjectSample update(String name, int num, String des) {
036   StockObjectSample ss = new StockObjectSample(name, num, des);
037   return ss;
038 }
    /**
    * 商品在庫情報の更新
    */
039 private static StockObjectSample update(String name, int num) {
040   StockObjectSample ss = new StockObjectSample(name, num);
041   return ss;
042 }
043 }
StockObjectSample.java
001 package functiontest.stock;
002 import java.util.ArrayList;
003 import java.util.List;
004 import functiontest.utility.UtilityObjectSample;
    /**
    * オブジェクト指向(命令型):商品在庫管理クラス
    *   在庫情報:商品名、在庫数、商品説明の3要素で構成
    * 商品名:"Goods"+商品グループ名(1文字)+連番で構成
    */
005 public class StockObjectSample {
006   private static List<String> goodsNameList = new ArrayList<String>();
007   private static List<Integer> numberStockList = new ArrayList<Integer>();
008   private static List<String> descriptionList = new ArrayList<String>();
009   private String goodsName;
010   private Integer numberStock;
011   private String description;
    /**
    * コンストラクタ
    */
012 public StockObjectSample() {
013   numberStock = 0;
014 }
    /**
    * コンストラクタ
    */
015 public StockObjectSample(String name, int num, String des) {
016   goodsName = name;
017   numberStock = num;
018   description = des;
019 }
    /**
    * コンストラクタ
    */
020 public StockObjectSample(String name, int num) {
021   goodsName = name;
022   numberStock = num;
023 }
    /**
    * 商品登録
    */
024 public void addGoods() {
025   addGoods(goodsName, numberStock, description);
026 }
    /**
    * 商品登録
    */
027 public void addGoods(String name, int num, String des) {
028   if (-1 < checkAdditionalGoods(name)) {
029     goodsNameList.add(name);
030     numberStockList.add(num);
031     setDescription(des);
032   }
033 }
    /**
    * 入庫
    */
034 public int stock() {
035   return stock(goodsName, numberStock);
036 }
    /**
    * 入庫
    */
037 public int stock(String name, int num) {
038   int index = checkGoods(name);
039   int sval = -1;
040   if (-1 < index) {
041     sval = numberStockList.get(index).intValue() + num;
042     numberStockList.set(index, sval);
043   }
044   return sval;
045 }
    /**
    * 出庫
    */
046 public void ship() {
047   ship(goodsName, numberStock);
048 }
    /**
    * 出庫
    */
049 public int ship(String name, int num) {
050   int index = checkGoods(name);
051   int sval = -1;
052   if (-1 < index) {
053     sval = numberStockList.get(index).intValue() - num;
054     numberStockList.set(index, sval);
055   }
056   return sval;
057 }
    /**
    * 在庫数チェック
    */
058 public boolean checkStock() {
059   return checkStock(goodsName, numberStock);
060 }
    /**
    * 在庫数チェック
    */
061 public boolean checkStock(String name, int num) {
062   boolean result = true;
063   int index = checkGoods(name);
064   if (-1 < index) {
065     if (num > numberStockList.get(index).intValue()) {
066       result = false;
067     }
068   }
069   return result;
070 }
    /**
    * 在庫数取得
    */
071 public int getStock() {
072   return getStock(goodsName);
073 }
    /**
    * 在庫数取得
    */
074 public int getStock(String name) {
075   int result = 0;
076   int index = checkGoods(name);
077   if (-1 < index) {
078     result = numberStockList.get(index).intValue();
079   }
080   return result;
081 }
    /**
    * 在庫数クリア
    */
082 public void clearStock() {
083   clearStock(goodsName);
084 }
    /**
    * 在庫数クリア
    */
085 public void clearStock(String name) {
086   int index = checkGoods(name);
087   if (-1 < index) {
088     numberStockList.set(index, 0);
089   }
090 }
    /**
    * 在庫情報出力
    */
091 public void outputStock() {
092   StringBuilder sb = new StringBuilder();
093   for (int i = 0; i < goodsNameList.size(); i++) {
094     if (0 != i) {
095       sb.append("\n");
096     }
097     sb.append("商品名:");
098     sb.append(goodsNameList.get(i));
099     sb.append(" 在庫数:");
100     sb.append(numberStockList.get(i));
101     sb.append(" 説明:");
102     sb.append(descriptionList.get(i));
103   }
104   outputMessage(sb.toString());
105 }
    /**
    * グループ別在庫数出力
    */
106 public void outputStockByGroup() {
107   List<List<String>> groupStockList = UtilityObjectSample.getStockOfGroup(goodsNameList, 5, 6, numberStockList);
108   outputMessage("商品グループ名, 合計数, 平均数");
109   for (int i = 0; i <groupStockList.size(); i++) {
110     outputMessage(groupStockList.get(i).get(0) + ", " + groupStockList.get(i).get(1) + ", " + groupStockList.get(i).get(2) + ", ");
111   }
112 }
    /**
    * 追加商品チェック
    */
113 public int checkAdditionalGoods(String name) {
114   if (0 > checkGoodsName(name))  {
115     return -1;
116   }
117   if (goodsNameList.contains(name)) {
118     outputMessage("既に商品名は登録されています。");
119     return -1;
120   }
121   return 0;
122 }
    /**
    * 商品チェック
    */
123 public int checkGoods(String name) {
124   if (0 > checkGoodsName(name))  {
125     return -1;
126   }
127   int index = goodsNameList.indexOf(name);
128   if (0 > index) {
129     outputMessage(name + "は登録されていません。");
130     return -1;
131   }
132   return index;
133 }
    /**
    * 商品名チェック
    */
134 public int checkGoodsName(String name) {
135   if (null == goodsNameList) {
136     outputMessage("商品名が全く登録されていません。");
137     return -1;
138   }
139   if (null == name || "".equals(name)) {
140     outputMessage("商品名が指定されていません。");
141     return -1;
142   }
143   return 0;
144 }
    /**
    * メッセージ出力
    */
145 private void outputMessage(String mes) {
146   System.out.println(mes);
147 }
    /**
    * 説明を設定
    */
148 public void setDescription(String des) {
149   if (null == des) {
150     des = "";
151   }
152   descriptionList.add(des);
153 }
154 }
UtilityObjectSample.java
001 package functiontest.utility;
002 import java.util.ArrayList;
003 import java.util.Collections;
004 import java.util.List;
    /**
    * オブジェクト指向(命令型) 商品在庫管理用ユーティリティ
    */
005 public class UtilityObjectSample {
    /**
    * グループ別合計数及び平均値算出
    */
006 public static List<List<String>> getStockOfGroup(List<String> goodsData, int sgroup, int egroup, List<Integer> stockList) {
007   List<String> list = new ArrayList<String>();
008   for (String name : goodsData) {
009     String grp = name.substring(sgroup,  egroup);
010     if (!list.contains(grp)) {
011       list.add(grp);
012     }
013   }
014   Collections.sort(list);
015   List<List<String>> groupStockList = new ArrayList<List<String>>();
016   for (String gname : list) {
017     List<String> itemList =  new ArrayList<String>();
018     int count = 0;
019     float totalCount = 0;
020     for (int i = 0; i < goodsData.size(); i++) {
021       if (-1 < goodsData.get(i).indexOf(gname)) {
022         ++count;
023         totalCount = totalCount + stockList.get(i);
024       }
025     }
026     itemList.add(gname);
027     itemList.add(String.valueOf(totalCount));
028     itemList.add(String.valueOf(totalCount / count));
029     groupStockList.add(itemList);
030   }
031   return groupStockList;
032 }
033 }
StockFunctionSampleMain.java
001 package functiontest.stock;
    /**
    * 関数型プログラミング:商品在庫管理メインクラス
    *   在庫情報:商品名、在庫数、商品説明の3要素で構成
    * 商品名:"Goods"+商品グループ名(1文字)+連番で構成
    */
002 public class StockFunctionSampleMain {
003   private static final String[] goodsNameA = {"GoodsA1", "GoodsA2", "GoodsA3"};
004   private static final String[] goodsNameB = {"GoodsB1", "GoodsB2", "GoodsB3"};
005   private static final int[] goodsStockA = {1, 2, 3};
006   private static final int[] goodsStockB = {1, 2, 3};
007   private static final String[] goodsDescriptionA = {"商品A1", "商品A2", "商品A3"};
008   private static final String[] goodsDescriptionB = {"商品B1", "商品B2", "商品B3"};
009 public static void main(String[] args) {
010   System.out.println("開始");
      //初期化
011   StockFunctionSample ss = initialize();
      //在庫追加
012   ss.executeFunction(goodsNameA[0], goodsStockA[0], ss.getICheckGoods(), ss.getIStock());
013   ss.executeFunction(goodsNameA[1], goodsStockA[1], ss.getICheckGoods(), ss.getIStock());
014   ss.executeFunction(goodsNameA[2], goodsStockA[2], ss.getICheckGoods(), ss.getIStock());
      //商品確認
015   System.out.println("商品確認(GoodsA1):" + ss.executeFunction(goodsNameA[0], -1, ss.getICheckGoods(), null));
      //在庫確認
016   System.out.println("在庫確認(GoodsA2):" + ss.executeFunction(goodsNameA[1], 2, ss.getICheckGoods(), ss.getICheckStock()));
      //在庫数確認
017   System.out.println("在庫数確認(GoodsA3):" + ss.getStock(goodsNameA[2], ss.getGoodsNameList()));
      //商品・在庫追加
018   StockFunctionSample ss1 = update(goodsNameB[0], goodsStockB[0], goodsDescriptionB[0]);
019   StockFunctionSample ss2 = update(goodsNameB[1], goodsStockB[1], goodsDescriptionB[1]);
020   StockFunctionSample ss3 = update(goodsNameB[2], goodsStockB[2], goodsDescriptionB[2]);
021   ss1.addGoods(null, 0,null);
022   ss2.addGoods(null, 0,null);
023   ss3.addGoods(null, 0,null);
024   update(goodsNameB[0], goodsStockB[0]).executeFunction(goodsNameB[0], goodsStockB[0], ss1.getICheckGoods(), ss1.getIStock());
025   update(goodsNameB[1], goodsStockB[1]).executeFunction(goodsNameB[1], goodsStockB[1], ss2.getICheckGoods(), ss2.getIStock());
026   update(goodsNameB[2], goodsStockB[2]).executeFunction(goodsNameB[2], goodsStockB[2], ss3.getICheckGoods(), ss3.getIStock());
      //在庫リスト出力
027   ss.outputStock(ss.getGoodsNameList(), ss.getNumberStockList(), ss.getDescriptionList());
      //グループ別在庫数出力
028   ss.outputStockByGroup(ss.getGoodsNameList(), ss.getNumberStockList());
029   System.out.println("終了");
030 }
    /**
    * 初期化
    */
031 private static StockFunctionSample initialize() {
032   StockFunctionSample ss = new StockFunctionSample();
033   ss.addGoods(goodsNameA[0], 0, goodsDescriptionA[0]);
034   ss.addGoods(goodsNameA[1], 0, goodsDescriptionA[1]);
035   ss.addGoods(goodsNameA[2], 0, goodsDescriptionA[2]);
      // Printer.printBasicObject("check01", ss.getGoodsNameList());
036   return ss;
037 }
    /**
    * 商品在庫情報の更新
    */
038 private static StockFunctionSample update(String name, int num, String des) {
039   StockFunctionSample ss = new StockFunctionSample(name, num, des);
040   return ss;
041 }
    /**
    * 商品在庫情報の更新
    */
042 private static StockFunctionSample update(String name, int num) {
043   StockFunctionSample ss = new StockFunctionSample(name, num);
044   return ss;
045 }
046 }
StockFunctionSample.java
001 package functiontest.stock;
002 import java.util.ArrayList;
003 import java.util.List;
004 import java.util.function.BiFunction;
005 import java.util.function.Consumer;
006 import java.util.function.Function;
007 import java.util.stream.Collectors;
008 import java.util.stream.IntStream;
009 import functiontest.utility.UtilityFunctionSample;
    /**
    * 関数型プログラミング:商品在庫管理クラス
    *   在庫情報:商品名、在庫数、商品説明の3要素で構成
    * 商品名:"Goods"+商品グループ名(1文字)+連番で構成
    */
010 public class StockFunctionSample {
011   private static List<String> goodsNameList = new ArrayList<String>();
012   private static List<Integer> numberStockList = new ArrayList<Integer>();
013   private static List<String> descriptionList = new ArrayList<String>();
014   private GoodsInterface IAdditionalGoods;
015   private Function<String, Integer> ICheckGoods;
016   private BiFunction<Integer, Integer, Integer> IStock;
017   private BiFunction<Integer, Integer, Integer> IShip;
018   private BiFunction<Integer, Integer, Integer> ICheckStock;
019   private Function<Integer, Integer> IGetNumberStock;
020   private Consumer<Integer> IClearStock;
    /**
    * コンストラクタ
    */
021 public StockFunctionSample() {
022   implementsInterface(null, 0, null, goodsNameList,  numberStockList, descriptionList);
023 }
    /**
    * コンストラクタ
    */
024 public StockFunctionSample(String name, int num, String des) {
025   implementsInterface(name, num, des, goodsNameList,  numberStockList, descriptionList);
026 }
    /**
    * コンストラクタ
    */
027 public StockFunctionSample(String name, int num) {
028   implementsInterface(name, num, null, goodsNameList,  numberStockList, descriptionList);
029 }
    /**
    * 関数インターフェイスの実装
    */
030 private void implementsInterface(String goodsName, Integer numberStock, String description, List<String> goodsNameList,  List<Integer> numberStockList, List<String> descriptionList) {
031   implementsGoodsInterface(goodsName, numberStock, description, goodsNameList,  numberStockList, descriptionList);
032   implementsStockInterface(goodsNameList,  numberStockList, descriptionList);
033 }
    //商品登録用自作関数型インターフェース
034 @FunctionalInterface
035 private interface GoodsInterface {
036   public void addGoods(String name, int num, String des);
037 }
    //商品登録用インターフェースの実装
038 private void implementsGoodsInterface(String goodsName, Integer numberStock, String description, List<String> goodsNameList,  List<Integer> numberStockList, List<String> descriptionList) {
039   IAdditionalGoods = (x, y, z) -> {
040     if (null != x) {
041       if (-1 < checkAdditionalGoods(x, goodsNameList)) {
042         addGoodsData(x, y, z, goodsNameList,  numberStockList, descriptionList);
043       }
044     } else {
045       if (-1 < checkAdditionalGoods(goodsName, goodsNameList)) {
046         addGoodsData(goodsName, numberStock, description, goodsNameList,  numberStockList, descriptionList);
047       }
048     }
049   };
050 }
    //在庫数関連インターフェースの実装
051 private void implementsStockInterface(List<String> goodsNameList,  List<Integer> numberStockList, List<String> descriptionList) {
052   ICheckGoods = (x) -> { return checkGoods(x, goodsNameList);};
053   IStock = (x, y) -> {
054     int sval = numberStockList.get(x).intValue() + y;
055     numberStockList.set(x, sval);
056     return sval;
057   };
058   IShip = (x, y) -> {
059     int sval = numberStockList.get(x).intValue() - y;
060     numberStockList.set(x, sval);
061     return sval;
062   };
063   ICheckStock = (x, y) -> {
064     int result = 0;
065     if (y > numberStockList.get(x).intValue()) {
066       result = -1;
067     }
068     return result;
069   };
070   IGetNumberStock = (x) -> {
071     return numberStockList.get(x).intValue();
072   };
073   IClearStock = (x) -> {
074     numberStockList.set(x, 0);
075   };
076 }
    /**
    * 商品登録
    */
077 private void addGoodsData(String name, int num, String des, List<String> goodsNameList,  List<Integer> numberStockList, List<String> descriptionList) {
078   goodsNameList.add(name);
079   numberStockList.add(num);
080   setDescription(des, descriptionList);
081 }
    /**
    * 商品説明設定
    */
082 private void setDescription(String des, List<String> descriptionList) {
083   if (null == des) {
084     des = "";
085   }
086   descriptionList.add(des);
087 }
    /**
    * 商品登録
    */
088 public void addGoods(String name, int num, String des) {
089   IAdditionalGoods.addGoods(name, num, des);
090 }
    /**
    * 指定関数を実行するメソッド
    */
091 public int executeFunction(String name, Integer value, Function<String, Integer> ckfnc, BiFunction<Integer, Integer, Integer> dofnc) {
092   int result = -1;
093   if (null != ckfnc) {
094     if (null != name) {
095       result = ckfnc.apply(name);
096     }
097   }
098   if (-1 < result && null != dofnc) {
099     if (-1 < value) {
100       result = dofnc.apply(result, value);
101     }
102   }
103   return result;
104 }
    /**
    * 在庫数取得
    */
105 public int getStock(String name, List<String> goodsNameList) {
106   int result = 0;
107   int index = checkGoods(name, goodsNameList);
108   if (-1 < index) {
109     result = IGetNumberStock.apply(index);
110   }
111   return result;
112 }
    /**
    * 在庫数クリア
    */
113 public void clearStock(String name, List<String> goodsNameList) {
114   int index = checkGoods(name, goodsNameList);
115   if (-1 < index) {
116     IClearStock.accept(index);
117   }
118 }
    /**
    * 在庫情報出力
    */
119 public void outputStock(List<String> goodsNameList,  List<Integer> numberStockList, List<String> descriptionList) {
120   List<String> list = IntStream.range(0,  goodsNameList.size())
121   .mapToObj(i -> {
122     StringBuilder sbc = new StringBuilder();
123     sbc.append("商品名:");
124     sbc.append(goodsNameList.get(i));
125     sbc.append(" 在庫数:");
126     sbc.append(numberStockList.get(i));
127     sbc.append(" 説明:");
128     sbc.append(descriptionList.get(i));
129     return sbc.toString();
130     })
131     .collect(Collectors.toList());
132     outputMessage(list);
133   }
      /**
      * グループ別在庫数出力
      */
134 public void outputStockByGroup(List<String> goodsNameList,  List<Integer> numberStockList) {
135   List<List<String>> groupStockList = UtilityFunctionSample.getStockOfGroup(goodsNameList, 5, 6, numberStockList, 0, 0);
136   outputMessage("商品グループ名, 合計数, 平均数");
137   List<String> list = IntStream.range(0,  groupStockList.get(0).size())
138   .mapToObj(i -> {
139     StringBuilder sbc = new StringBuilder();
140     sbc.append(groupStockList.get(0).get(i));
141     sbc.append( ", ");
142     sbc.append(groupStockList.get(1).get(i));
143     sbc.append( ", ");
144     sbc.append(groupStockList.get(2).get(i));
145     sbc.append( ", ");
146     return sbc.toString();
147     })
148     .collect(Collectors.toList());
149     outputMessage(list);
150   }
      /**
      * 追加商品チェック
      */
151 public int checkAdditionalGoods(String name, List<String> goodsNameList) {
152   if (0 > checkGoodsName(name, goodsNameList))  {
153     return -1;
154   }
155   if (null != name && goodsNameList.contains(name)) {
156     outputMessage("既に商品名は登録されています。 " + name);
157     return -1;
158   }
159   return 0;
160 }
    /**
    * 商品チェック
    */
161 public int checkGoods(String name, List<String> goodsNameList) {
162   if (0 > checkGoodsName(name, goodsNameList))  {
163     return -1;
164   }
165   int index = goodsNameList.indexOf(name);
166   if (0 > index) {
167     outputMessage(name + "は登録されていません。");
168     return -1;
169   }
170   return index;
171 }
    /**
    * 商品名チェック
    */
172 public int checkGoodsName(String name, List<String> goodsNameList) {
173   if (null == goodsNameList) {
174     outputMessage("商品名が全く登録されていません。 " + name);
175     return -1;
176   }
177   if (null == name || "".equals(name)) {
178     outputMessage("商品名が指定されていません。 " + name);
179     return -1;
180   }
181   return 0;
182 }
    /**
    * メッセージ出力
    */
183 public void outputMessage(String mes) {
184   System.out.println(mes);
185 }
    /**
    * メッセージ出力
    */
186 private void outputMessage(List<String> list) {
187   list.stream().forEach(s -> {outputMessage(s);});
188 }
189 public Function<String, Integer> getICheckGoods() {
190   return ICheckGoods;
191 }
192 public BiFunction<Integer, Integer, Integer> getIStock() {
193   return IStock;
194 }
195 public BiFunction<Integer, Integer, Integer> getIShip() {
196   return IShip;
197 }
198 public BiFunction<Integer, Integer, Integer> getICheckStock() {
199   return ICheckStock;
200 }
201 public List<String> getGoodsNameList() {
202   return goodsNameList;
203 }
204 public List<Integer> getNumberStockList() {
205   return numberStockList;
206 }
207 public List<String> getDescriptionList() {
208   return descriptionList;
209 }
210 }
UtilityFunctionSample.java
001 package functiontest.utility;
002 import java.util.ArrayList;
003 import java.util.Collections;
004 import java.util.List;
005 import java.util.stream.Collectors;
006 import java.util.stream.Stream;
    /**
    * 関数型プログラミング 商品在庫管理用ユーティリティ
    */
007 public class UtilityFunctionSample {
    /**
    * グループ別合計数及び平均値算出
    */
008 public static List<List<String>> getStockOfGroup(List<String> goodsData, int sgroup, int egroup, List<Integer> stockList, int snumeric, int enumeric) {
      //グループ区分け及びソート
009   List<String> groupList = new ArrayList<String>();
010   goodsData.stream().filter(vf -> {if(!groupList.contains(vf.substring(sgroup, egroup))) {return true;}return false;}).forEach(ve -> {
011     groupList.add(ve.substring(sgroup, egroup));
012   });
013   Collections.sort(groupList);
      //グループ別合計数及び平均値算出
014   Stream<String> groupStock = groupList.stream().flatMap(gname -> {
        //gnameグループ名と一致する在庫数リストを作成
015     List<Integer> countList =  new ArrayList<Integer>();
016     goodsData.stream().filter(vf -> {if (-1 < vf.indexOf(gname)) {return true;}return false;}).forEach(ve -> {
017       if (null == stockList) {
018         countList.add(new Integer(ve.substring(snumeric, enumeric)));
019       } else {
020         countList.add(stockList.get(goodsData.indexOf(ve)));
021       }
022     });
        //グループ別合計数
023     float totalCount = countList.stream().reduce((i, t) -> i + t).get();
024     Stream<String> gelement = Stream.of("0"+gname);
025     gelement = Stream.concat(gelement, Stream.of("1"+String.valueOf(totalCount)));
        //グループ別平均値
026     gelement = Stream.concat(gelement, Stream.of("2"+String.valueOf(totalCount / countList.size())));
027     return gelement;
028   });
      //groupStock先頭文字  0:グループ名/1:合計数/2:平均値
029   List<String> glist = groupStock.collect(Collectors.toList());
030   List<String> nameList = createGroupList(glist.stream(), "0", 0, 1);
031   List<String> totList = createGroupList(glist.stream(), "1", 0, 1);
032   List<String> aveList = createGroupList(glist.stream(), "2", 0, 1);
033   List<List<String>> groupStockList = new ArrayList<List<String>>();
034   groupStockList.add(nameList);
035   groupStockList.add(totList);
036   groupStockList.add(aveList);
037   return groupStockList;
038 }
    /**
    * Stream<<String>から指定文字範囲が指定キーに一致するデータ(2文字目以降)をリスト化して戻す
    */
039 public static List<String> createGroupList(Stream<String> strm, String key, int sindx, int eindx) {
040   List<String> list = new ArrayList<String>();
041   strm.filter(s -> {if (key.equals(s.substring(sindx, eindx))) {return true;}return false;}).forEach(s -> {
042     list.add(s.substring(1));
043   });
044   return list;
045 }
046 }

現実的サンプルでの考察・推測

まずは、後述のまとめを含め現実的サンプルでの考察・推測をするための前提を以下2項目とします。
・オブジェクト指向ベースのシステム開発で、純粋な関数型プログラミングの知識が少ないメンバーがほとんどである。
・StreamAPIの利用に関する知識は保有している。
Javaで関数型プログラミングを取り入れる目的としては、
(1)副作用を少なくし品質をあげる。
(2)コーディング量を少なくする。
が一般的に挙げられています。
(1)については確かに表層的・部分的には副作用を減少させることが可能ではありますが、上述で述べているとおり本質的な対応は困難ですし、無理矢理適用すればメソッド引数がかなり多くなりプログラミング上好ましくない状態となり、データと処理操作が乖離していきベースとなるオブジェクト指向から遠ざかっていくこととなります。
今回の現実的サンプルでもメソッドの引数はかなり増加していてプログラムの見通しが悪くなり無駄も発生しています。
(2)のコーディング量については、公開サイトでよく見られる数行のサンプルでは少なるように思えますが、一般的にはforループ内は少し複雑な処理となったり、インデックス使用となることが比較的多いので余計な対応が必要となったりして結局それ程の効果はないと推測され、今回の現実的サンプルでも少なくはなっていない。
また、streamAPIを多用すればstream化や逆stream化処理での処理時間が遅くなることも想定されます。

まとめ

現実的サンプルでの考察・推測をふまえて、最終的にJavaでは関数型プログラミングをどのように扱えばよいのかをまとめたいと思います。
あくまで個人的な意見・対応方針となりますが、
(a)関数型プログラミング主体でシステム開発したい場合はHaskell等の純粋関数型言語を使用しJavaでは行わない。
(b)システム開発設計はきちんとオブジェクト指向で行う。
(c)あえて副作用をなくすため(参照透過性保証)にオブジェクト指向に反する設計やプログラミングは行わない。
(d)無理して関数型プログラミングに関係した手法(自作高階関数、カリー化など)を取り込まない。
(e)Javaで標準として用意されている関数プログラミング用APIをフィットする範囲で利用する範囲にとどめてプログラミングを行う。
(f)コレクションfor文をなくすために毎回あえてstreamAPIを使用することはしない。
(g)for文内が単純でstreamAPIにフィットする処理ケース以外はfor文をそのまま使用した方が見通しも良く、処理速度劣化も無い(並列ストリーム使用時を除く)。
(h)静的ユーティリティ用メソッドはオブジェクトのメソッドよりも多少ではあるが副作用排除はしやすいのではと思われる。
以上のまとめをざっくり言えば、Javaを用いてオブジェクト指向で開発する範囲においては、表層的・部分的な副作用回避のために関数型プログラミングを多用する意義はそれほど大きくはなく、コーディング量の減少効果も全体的にみればそれほど期待はできないと思われる。
ただ、この関数型プログラミング効果は開発対象や開発メンバーに左右されるので、よく調査しベンチマークの上どの程度取り入れるべきかを決定することが妥当である事はいうまでもない。
という内容が今回の活動での結論・総括となりました。

最後に

以上です。最後までお読み頂き有難うございました。

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

Java Silver SE11 継承とオーバーライドの間違えやすい部分

Java Silver SE11の問題を解いていて継承とオーバーライドに関する問題を間違えるので備忘録として残す。

大事なのは何の型で宣言されて、何のインスタンスを生成しているか。
メソッドはどのようなアクセス修飾子が付与されているか。

public class Main {
    public static void main(String[] args) throws Exception {
        //case A
        Super superClass = new Sub();

        //case B
        // Super superClass = new Super();

        Sub subClass = new Sub();

        //code 1
        // subClass = superClass;

        //code 2
        // subClass = (Sub)superClass;

        //code 3
        // superClass = subClass;

        superClass.print();
        subClass.print();
    }
}

class Super{
    //case C privateで修飾
    //case D staticで修飾
    void print(){
        System.out.println("Super");
    }
}

class Sub extends Super{
    //case D staticで修飾
    void print(){
        System.out.println("Sub");
    }
}

それぞれの実行結果がどうなるか。

  • case Aの場合

    • code 1の時
      サブクラス型の変数subClassにスーパークラス型の変数superClassを代入しているためコンパイルエラー
    • code 2の時
      スーパークラス型の変数superClassはSubクラスのインスタンス生成しているため、ダウンキャストされて代入される。実行結果はオーバーライドされたメソッドが優先されるので「Sub Sub」
    • code 3の時
      スーパークラス型の変数superClassにサブクラス型の変数subClassを代入しているため自動的にアップキャストされ、実行結果も同様にオーバーライドされたメソッドが優先されて「Sub Sub」
  • case Bの場合

    • code 1の時
      case Aの場合と同様にコンパイルエラー
    • code 2の時
      明示的にキャストしているためコンパイルエラーは起こらない。しかし、スーパークラス型の変数superClassはSuperクラスのインスタンスを生成しており、サブクラスの差分を持たないため、ClassCastExceptionがスローされる(実行時エラーが起きる)
    • code 3の時
      case Aの場合と同様にアップキャストされ、実行結果は「Sub Sub」
  • case Cの場合

    • スーパークラスであるSuperクラスのprintメソッドがprivateで修飾されると、オーバーライドされているとみなされず、生成されているインスタンスに関わらず、変数の型のprintメソッドが実行される。
  • case Dの場合

    • 両方のprintメソッドがstaticで修飾された場合、変数の型のprintメソッドが実行される。
    • staticで修飾する場合は両方のメソッドを修飾しないとコンパイルエラーになる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DynamoDB Enhanced Client にて、なぜかインデックステーブルに対してアクセスしてしまう

エラー内容

エラー文は以下の通り。

java.lang.IllegalArgumentException: A sort key value was supplied for an index that does not support one. Index: $PRIMARY_INDEX

結論

属性に対するゲッター・セッターの定義がおかしい。(敗北N回目、、、)

対処

適切なゲッター・セッターを設定しましょう。
具体的には以下の2点をチェック。

  • メソッド名
  • 引数の型

発生状況

筆者の場合は以下のような形で発生しました。

@DynamoDbBean
@Setter
public class Foo {

  private String name;
  private String date;

  @DynamoDbAttribute("name")
  public String getName() {
    return name;
  }

  @DynamoDbAttribute("date")
  public String getDate() {
    return date;
  }

  public setDate(Date date) {
    this.date = new SimpleDateFormat("yyyy-MM-dd").format(date);
  }

}

原因はFoo#setDate(Date)ですね。
テーブルのメタデータにはゲッターとセッターを設定する必要があります。
@DynamoDbAttributeアノテーションで設定する場合は、メソッドの名称と引数の型を、フィールドと合わせる必要があります。
フィールドdateString型で定義されていますが、セッターではDate型を受け取ってしまっています。
Lombockの@Setterアノテーションで生成されるセッターとはシグネチャが異なるので共存可能であり、Foo#setDate(String)も定義されています。
しかし、テーブルのメタデータとしてはFoo#setDate(Date)が拾われてしまうようです。
そこからなぜか、インデックステーブルへアクセスしようとし、エラーが発生してしまうようですね。

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

Selenium マウスホバーしてからクリック

Seleniumを使用してDOM要素をマウスホバーしてからクリックする方法について明記します。

Java
import org.openqa.selenium.interactions.Actions;

public static void autoHoverClick(WebDriver driver, WebElement element){
    Actions act = new Actions(driver);
    act.moveToElement(element).perform(); // 指定したDOM要素をホバーする(hover)
    element.click(); // DOM要素をクリック
}

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

Selenium DOM要素をホバー&クリックする方法

Seleniumを使用してDOM要素をホバーしてからクリックする方法について明記します。

SeleniumはWebサイトやWebページの閲覧を自動化することができます。
今回は、抽出したDOM要素をホバーしてからクリックする方法について明記します。
要素をホバーすることでプログラム上、マウスカーソルがDOM要素に重なっている状態を表現します。

Java
import org.openqa.selenium.interactions.Actions;

public static void autoHoverClick(WebDriver driver, WebElement element){
    Actions act = new Actions(driver);
    act.moveToElement(element).perform(); // 指定したDOM要素をホバーする(hover)
    element.click(); // DOM要素をクリック
}

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

たったこれだけ!コードフォーマット未適用を防ぐ方法

image.png

はじめに

「コードフォーマットが適用されておりません。」
こんな指摘をした人、された人。いるのではないでしょうか?
今回はコードフォーマットをCIツールで実行し、開発者はフォーマッターの実行をしないような仕組みを紹介します。

この記事を書こうと思った動機

お客様指定のコードスタイルのある規約があるプロダクトにおいて以下の問題に直面し、結果的に工数が増大したため。

:star: ケース1. ローカルで「コードフォーマッター」が適用されずにPull Requestを出すケース
image.png

:star: ケース2. すり抜けコードをベースに修正し、コードフォーマッターを適用したら、改修箇所以外の差分が抽出された

image.png

:star: ケース3. 「クロスチェック」させる体制にして工数増
image.png

【コードフォーマッターが適用されないわけ】

  • 開発環境構築手順書・開発フローにコードフォーマッターの記載されていない。(曖昧なドキュメント)

image.png

  • 開発メンバー間でのフローの引き継ぎもない(開発メンバーが流動的)

image.png

折角コードフォーマッターの設定ファイルを用意しても、運用されていなければ意味がありません
ドキュメントをしっかり書いたとしても、プロジェクトメンバー全員の開発環境設定が同じであることを確認するにも限度があります。そのため、同じような悩みを持っているコードオーナの方向けにCIツール側でコードフォーマッターを適用する方法について紹介しようと思いました。

CIツールでコードフォーマッターを実行する

解決アイディア : 開発環境に依存するのでは無く、フォーマッタはレビュー実施前に自動的に適用される方式とする。

image.png

前提

CIツールとコードフォーマッターを連携するために以下の要件を満たす必要があります。

  1. バージョン管理ツールへのPushやPull Request(Merge Request)を検出する仕組み
  2. 以下に対応したコードフォーマッターが存在すること
    • コマンドラインから実行可能で且つ、スタンドアロンで動作可能なツールであること
    • CIツールに対応したツールであること

後述の方式は上記の要件を満たしたツールを選定しておりますが、もし存在しない( 特に [1.] ) は自作することもご検討ください。

コードフォーマット自動適用例

今回のサンプルは以下の仕様のもとで作成するもとのする。

項目 内容
プログラミング言語 Java
コードスタイル Google Java Style Guide に準拠
ツール google-java-format

CIツールで自動的にコードフォーマッターを実行する

CI用コード(GitHub Action)

:star: ポイント:on pull_reqestにて取得されたブランチはPull Requestしたブランチとは別ブランチのため、checkout時にPull Requestを行ったブランチを明示的して取得する必要があります。

name: Reformat Java source code

on:
  pull_request:

jobs:
  format:
    runs-on: ubuntu-latest
    steps:
      # Pull Requestされたブランチをチェックアウト(★refがポイント)
      - uses: actions/checkout@v2
        with:
          ref: ${{ github.head_ref }}
      # Java のセットアップ
      - name: Set up JDK 11
        uses: actions/setup-java@v1
        with:
          java-version: 11
      # Gitの設定
      - name: Git settings
        run: |
          # Git リポジトリの設定
          git config --local user.name "ユーザ名"
          git config --local user.email "E-Mailアドレス"
      # コードフォーマットの実行
      - name: Code format
        run: |
          wget -q -O google-java-format.jar \
            https://github.com/google/google-java-format/releases/download/google-java-format-1.9/google-java-format-1.9-all-deps.jar
          java -jar google-java-format.jar -replace $(git ls-files src/**/*.java)
      # 変更確認
      - name: Check for modified files
        id: git-check
        run: echo ::set-output name=modified::$(if git diff-index --quiet HEAD --; then echo "false"; else echo "true"; fi)
      # 変更されていた場合、コミット・プッシュを行う
      - name: Push
        if: steps.git-check.outputs.modified == 'true'
        run: |
          git commit -am "Automated :Reformat Java source code."
          git push

実行結果

PullRequst実施、変更時にコードフォーマッターが適用された新しいコミットが追加される

github_001.gif

変更差分
image.png

サンプルリポジトリ

上記は以下のリポジトリで実現されております。(ブランチ: feature/format-action)
https://github.com/yukiusa/github-action-auto-java-format-sample/tree/feature/format-action

さいごに

いかがだったでしょうか?
今回はCIツールを利用し「開発環境に依存せずルールを遵守させる仕組み」について紹介しました。
本取り組みを実施することで

  • 開発環境の作成の手間を減らす
  • チェックの工数を削減する
  • レビューア・レビューイの負荷を減らす

などの効果が期待できるかと思います。
(※コードスタイルがお粗末であれば意味をなさないため、コードスタイルのガイドライン作りは慎重に行う必要があります)

今後の業務の改善案の一つとしてお役立ていただければ幸いでございます。

最後まで読んでいただきましてありがとうございました。

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

たったこれだけ!チーム開発でコードフォーマット未適用を防ぐ方法

image.png

はじめに

「コードフォーマットが適用されておりません。」
こんな指摘をした人、された人。いるのではないでしょうか?
今回はコードフォーマットをCIツールで実行し、開発者はフォーマッターの実行をしないような仕組みを紹介します。

この記事を書こうと思った動機

お客様指定のコードスタイルのある規約があるプロダクトにおいて以下の問題に直面し、結果的に工数が増大したため。

:star: ケース1. ローカルで「コードフォーマッター」が適用されずにPull Requestを出すケース
image.png

:star: ケース2. すり抜けコードをベースに修正し、コードフォーマッターを適用したら、改修箇所以外の差分が抽出された

image.png

:star: ケース3. 「クロスチェック」させる体制にして工数増
image.png

【コードフォーマッターが適用されないわけ】

  • 開発環境構築手順書・開発フローにコードフォーマッターの記載されていない。(曖昧なドキュメント)

image.png

  • 開発メンバー間でのフローの引き継ぎもない(開発メンバーが流動的)

image.png

折角コードフォーマッターの設定ファイルを用意しても、運用されていなければ意味がありません
ドキュメントをしっかり書いたとしても、プロジェクトメンバー全員の開発環境設定が同じであることを確認するにも限度があります。そのため、同じような悩みを持っているコードオーナの方向けにCIツール側でコードフォーマッターを適用する方法について紹介しようと思いました。

CIツールでコードフォーマッターを実行する

解決アイディア : 開発環境に依存するのでは無く、フォーマッタはレビュー実施前に自動的に適用される方式とする。

image.png

前提

CIツールとコードフォーマッターを連携するために以下の要件を満たす必要があります。

  1. バージョン管理ツールへのPushやPull Request(Merge Request)を検出する仕組み
  2. 以下に対応したコードフォーマッターが存在すること
    • コマンドラインから実行可能で且つ、スタンドアロンで動作可能なツールであること
    • CIツールに対応したツールであること

後述の方式は上記の要件を満たしたツールを選定しておりますが、もし存在しない( 特に [1.] ) は自作することもご検討ください。

コードフォーマット自動適用例

今回のサンプルは以下の仕様のもとで作成するものとする。

項目 内容
プログラミング言語 Java
コードスタイル Google Java Style Guide に準拠
ツール google-java-format

CIツールで自動的にコードフォーマッターを実行する

CI用コード(GitHub Action)

:star: ポイント:on pull_reqestにて取得されたブランチはPull Requestしたブランチとは別ブランチのため、checkout時にPull Requestを行ったブランチを明示的して取得する必要があります。

name: Reformat Java source code

on:
  pull_request:

jobs:
  format:
    runs-on: ubuntu-latest
    steps:
      # Pull Requestされたブランチをチェックアウト(★refがポイント)
      - uses: actions/checkout@v2
        with:
          ref: ${{ github.head_ref }}
      # Java のセットアップ
      - name: Set up JDK 11
        uses: actions/setup-java@v1
        with:
          java-version: 11
      # Gitの設定
      - name: Git settings
        run: |
          # Git リポジトリの設定
          git config --local user.name "ユーザ名"
          git config --local user.email "E-Mailアドレス"
      # コードフォーマットの実行
      - name: Code format
        run: |
          wget -q -O google-java-format.jar \
            https://github.com/google/google-java-format/releases/download/google-java-format-1.9/google-java-format-1.9-all-deps.jar
          java -jar google-java-format.jar -replace $(git ls-files src/**/*.java)
      # 変更確認
      - name: Check for modified files
        id: git-check
        run: echo ::set-output name=modified::$(if git diff-index --quiet HEAD --; then echo "false"; else echo "true"; fi)
      # 変更されていた場合、コミット・プッシュを行う
      - name: Push
        if: steps.git-check.outputs.modified == 'true'
        run: |
          git commit -am "Automated :Reformat Java source code."
          git push

実行結果

PullRequst実施、変更時にコードフォーマッターが適用された新しいコミットが追加される

github_001.gif

変更差分
image.png

サンプルリポジトリ

上記は以下のリポジトリで実現されております。(ブランチ: feature/format-action)
https://github.com/yukiusa/github-action-auto-java-format-sample/tree/feature/format-action

さいごに

いかがだったでしょうか?
今回はCIツールを利用し「開発環境に依存せずルールを遵守させる仕組み」について紹介しました。
本取り組みを実施することで

  • 開発環境の作成の手間を減らす
  • チェックの工数を削減する
  • レビューア・レビューイの負荷を減らす

などの効果が期待できるかと思います。
(※コードスタイルがお粗末であれば意味をなさないため、コードスタイルのガイドライン作りは慎重に行う必要があります)

今後の業務の改善案の一つとしてお役立ていただければ幸いでございます。

最後まで読んでいただきましてありがとうございました。

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

たったこれだけ!チーム開発でコードフォーマット忘れを防ぐ方法

image.png

はじめに

「コードフォーマットが適用されておりません。」
こんな指摘をした人、された人。いるのではないでしょうか?
今回はコードフォーマットをCIツールで実行し、開発者はフォーマッターの実行をしないような仕組みを紹介します。

この記事を書こうと思った動機

お客様指定のコードスタイルのある規約があるプロダクトにおいて以下の問題に直面し、結果的に工数が増大したため。

:star: ケース1. ローカルで「コードフォーマッター」が適用されずにPull Requestを出すケース
image.png

:star: ケース2. すり抜けコードをベースに修正し、コードフォーマッターを適用したら、改修箇所以外の差分が抽出された

image.png

:star: ケース3. 「クロスチェック」させる体制にして工数増
image.png

【コードフォーマッターが適用されないわけ】

  • 開発環境構築手順書・開発フローにコードフォーマッターの記載されていない。(曖昧なドキュメント)

image.png

  • 開発メンバー間でのフローの引き継ぎもない(開発メンバーが流動的)

image.png

折角コードフォーマッターの設定ファイルを用意しても、運用されていなければ意味がありません
ドキュメントをしっかり書いたとしても、プロジェクトメンバー全員の開発環境設定が同じであることを確認するにも限度があります。そのため、同じような悩みを持っているコードオーナの方向けにCIツール側でコードフォーマッターを適用する方法について紹介しようと思いました。

CIツールでコードフォーマッターを実行する

解決アイディア : 開発環境に依存するのでは無く、フォーマッタはレビュー実施前に自動的に適用される方式とする。

image.png

前提

CIツールとコードフォーマッターを連携するために以下の要件を満たす必要があります。

  1. バージョン管理ツールへのPushやPull Request(Merge Request)を検出する仕組み
  2. 以下に対応したコードフォーマッターが存在すること
    • コマンドラインから実行可能で且つ、スタンドアロンで動作可能なツールであること
    • CIツールに対応したツールであること

後述の方式は上記の要件を満たしたツールを選定しておりますが、もし存在しない( 特に [1.] ) は自作することもご検討ください。

コードフォーマット自動適用例

今回のサンプルは以下の仕様のもとで作成するものとする。

項目 内容
プログラミング言語 Java
コードスタイル Google Java Style Guide に準拠
ツール google-java-format

CIツールで自動的にコードフォーマッターを実行する

CI用コード(GitHub Action)

コードの前提
以下のScriptはGitHub MarketplaceでVerifiedとなっているプラグインのみ利用して実現しております。
(※手早く実装したい場合はMarketplaceにあるプラグインをどんどん活用しましょう)

name: Reformat Java source code

on:
  pull_request:

jobs:
  format:
    runs-on: ubuntu-latest
    steps:
      # Pull Requestされたブランチをチェックアウト
      - uses: actions/checkout@v2
        with:
          ref: ${{ github.head_ref }}
      # Java のセットアップ
      - name: Set up JDK 11
        uses: actions/setup-java@v1
        with:
          java-version: 11
      # Gitの設定
      - name: Git settings
        run: |
          # Git リポジトリの設定
          git config --local user.name "ユーザ名"
          git config --local user.email "E-Mailアドレス"
      # コードフォーマットの実行(※1)
      - name: Code format
        run: |
          wget -q -O google-java-format.jar \
            https://github.com/google/google-java-format/releases/download/google-java-format-1.9/google-java-format-1.9-all-deps.jar
      # 以下の例ではすべての変更に対してフォーマッタを加えております。
          # プルリクエスト時の変更を取る場合はgit logで変更差分のみ取得してフォーマッタをかけましょう。(※2)
          java -jar google-java-format.jar -replace $(git ls-files src/**/*.java)
      # 変更確認
      - name: Check for modified files
        id: git-check
        run: echo ::set-output name=modified::$(if git diff-index --quiet HEAD --; then echo "false"; else echo "true"; fi)
      # 変更されていた場合、コミット・プッシュを行う(※3)
      - name: Push
        if: steps.git-check.outputs.modified == 'true'
        run: |
          git commit -am "Automated :Reformat Java source code."
          git push

※1 … google-java-format
※2 … get-all-changed-files
※3 … Git Auto Commit

実行結果

PullRequst実施、変更時にコードフォーマッターが適用された新しいコミットが追加される

github_001.gif

変更差分
image.png

サンプルリポジトリ

上記は以下のリポジトリで実現されております。(ブランチ: feature/format-action)
https://github.com/yukiusa/github-action-auto-java-format-sample/tree/feature/format-action

さいごに

いかがだったでしょうか?
今回はCIツールを利用し「開発環境に依存せずルールを遵守させる仕組み」について紹介しました。
本取り組みを実施することで

  • 開発環境の作成の手間を減らす
  • チェックの工数を削減する
  • レビューア・レビューイの負荷を減らす

などの効果が期待できるかと思います。
今後の業務の改善案の一つとしてお役立ていただければ幸いでございます。

最後まで読んでいただきましてありがとうございました。

改訂履歴

@naminodarie 様からの指摘一部修正。ご指摘ありがとうございました。

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