「JJUG CCC 2018 Fall」言って参りました~。
いろんな、講演があって面白かったです。難しい話も多々ありましたが...
⇧ 上記サイト様が、講演のスライドをまとめてくださっていました。
というわけで、今回も、Java のお話です。
Stream API のラムダ式を使用時の例外処理の行方は...
「JJUG CCC 2018 Fall」の講演の中の1つで、『今こそStream API入門(櫻庭 祐一さん) #ccc_g4』 の質疑応答の中で、『ラムダ式での例外処理のベストプラクティスはありますか?』という質問に、『私は、Either を使っています』と。
『Either 』とは?
Either
- Either<Left, Right> という2つの入れ物を持つ
- ただしいずれかひとつしか値を入れることができない
- 慣例として左に例外を、右に正常時戻り値を入れる(正しいのrightにかけている)
- 読みは íːðɚ アクセントが前
*Java8にEitherは含まれていないので、JAVASLANGライブラリを利用
⇧『Either 』とは、Java 8 標準では無いらしい... 外部APIやんけ~
⇧ しかも、『Javaslang』ではなく、『Vavr』って名前に改名されているらしい...何だかな~...。で、『Vavr(旧:Javaslang)』に含まれる、1機能であるらしいと。
『Vavr(旧:Javaslang)』を利用してみる
https://www.vavr.io/ にアクセス。
「DOWNLOADS」ってクリックすると、
ページ内リンクで、Downloads の場所にスクロールするので、「Source Code」を選択。
https://github.com/vavr-io/vavr に遷移するので、
「README.md」の、「Using Vavr」の『User Guide』のリンクをクリック。
Vavr User Guide に遷移するので、スクロールすると、
それぞれの導入方法が説明されています。
今回は、「2.1.Gradle」での導入を試してみたいと思います。
まずは、https://search.maven.org/search?q=g:io.vavr%20a:vavr にアクセスし、「Download」で、「jar」を選択。
ダウンロードされます。
続きまして、Eclipseを起動し、
「ファイル(F)」>「新規(N)」>「Gradle プロジェクト」で。
適当な「プロジェクト名」を入力し、「次へ(N)>」。
一応、、「次へ(N)>」。
「完了(F)」で。
「Gradle プロジェクト」が作成されたら、プロジェクトを選択した状態で右クリックし、「新規(W)」>「フォルダー」で。
外部ライブラリ 配置用のファルダを作成しておきます。
外部ライブラリ 配置用のファルダに、さきほど、ダウンロードしたjarファイルを配置。
「ファイルをコピー(C)」にチェックし、「OK」。
そしたらば、「build.gradle」に依存関係を追記します。
ファイルは、こんな感じ。
/* * This file was generated by the Gradle 'init' task. * * This generated file contains a sample Java Library project to get you started. * For more details take a look at the Java Libraries chapter in the Gradle * user guide available at https://docs.gradle.org/4.10.2/userguide/java_library_plugin.html */ plugins { // Apply the java-library plugin to add support for Java Library id 'java-library' } dependencies { // This dependency is exported to consumers, that is to say found on their compile classpath. api 'org.apache.commons:commons-math3:3.6.1' // This dependency is used internally, and not exposed to consumers on their own compile classpath. implementation 'com.google.guava:guava:23.0' // Use JUnit test framework testImplementation 'junit:junit:4.12' // 今回追加 compile files('lib/varv-0.9.2.jar') } // In this section you declare where to find the dependencies of your project repositories { // Use jcenter for resolving your dependencies. // You can declare any Maven/Ivy/file repository here. jcenter() }
ってな感じで。
⇧ 上記サイト様が詳しいです。
依存関係を追加したらば、プロジェクトを選択した状態で右クリックし、「Gradle」>「Gradle プロジェクトのリフレッシュ」で。
見事に、解決できてない...
Eclipse が介入した場合、
⇧ 上記サイト様によりますと、「.classpath」に追記が必要らしい...知らんがな。
「エクスプローラー」から、ワークスペースのプロジェクトの中にある「.classpath」ってファイルを適当なエディターで編集していきます。
っていうか、既に記述されとりますやん...
すみませんでした...build.gradleに追加したライブラリ名のタイポでした...
以下の部分を、
誤
// 今回追加 compile files('lib/varv-0.9.2.jar')
正
/* // 今回追加 compile files('lib/vavr-0.9.2.jar')
に修正して「Gradle」>「Gradle プロジェクトのリフレッシュ」で、エラー消えました。
んでは、Javaのクラスファイルを作成していきます。
お馴染み、mainメソッドを含むクラスファイルを作成。
作成したら、もう1つ、Entity用(DBのテーブルからの情報を保持する目的で使われるオブジェクト)のクラスを作成。
「名前(M):」を適当に入力し、「完了(F)」で。
「パッケージ・エクスプローラー」の構成は、こんな感じに。
んでは、ファイルを編集で。
/TestEither/src/main/java/entity/EisyunkenEntity.java
package entity; public class EisyunkenEntity { private String syonento; private String jinkyo; private String hyosi; public String getSyonento() { return syonento; } public void setSyonento(String syonento) { this.syonento = syonento; } public String getJinkyo() { return jinkyo; } public void setJinkyo(String jinkyo) { this.jinkyo = jinkyo; } public String getHyosi() { return hyosi; } public void setHyosi(String hyosi) { this.hyosi = hyosi; } }
/TestEither/src/main/java/main/TestEither.java
package main; import java.lang.reflect.Field; import entity.EisyunkenEntity; import io.vavr.control.Either; public class TestEither { public static void main(String[] args) { // TODO 自動生成されたメソッド・スタブ EisyunkenEntity eisyunkenEntity = new EisyunkenEntity(); eisyunkenEntity.setSyonento("小念頭"); eisyunkenEntity.setJinkyo(null); eisyunkenEntity.setHyosi("標指"); logic(eisyunkenEntity).right().forEach(s -> { // 処理成功 System.out.println("Success"); System.out.println(s); }); logic(eisyunkenEntity).left().forEach(s -> { // 処理失敗 System.out.println("failure"); System.out.println(s); }); } // leftは、例外処理を返し、rightは正常な処理を返す static <T> Either<Throwable, T> logic(T entity) { for (Field field : entity.getClass().getDeclaredFields()) { boolean accessible = field.isAccessible(); try { field.setAccessible(true); Class<?> type = field.getType(); // String name = field.getName(); Object value = field.get(entity); if(getObject(type, value) == null) { return Either.left(new RuntimeException("null")); } } catch (IllegalArgumentException e) { // TODO 自動生成された catch ブロック e.printStackTrace(); } catch (IllegalAccessException e) { // TODO 自動生成された catch ブロック e.printStackTrace(); } finally { field.setAccessible(accessible); } } return Either.right(entity); } // null チェック private static <T> Object getObject(Class<T> type, Object field) { if (field != null) { if(type.isArray()) { getObject(type, field); } return field; } return null; } }
んで、実行ししてみると。
leftのほうの結果が表示されました。
leftのほうをコメントアウトしてみます。
//logic(eisyunkenEntity).left().forEach(s -> { // // 処理失敗 // System.out.println("failure"); // System.out.println(s); //});
で実行。
例外が起きず、処理が終了してしまっているという...
使い方がいまいちよく分かってはいないけど、どちらか片方を返すってことを、Either はやってくれるらいしい。
それを、踏まえて、最終的に、ソースをこんな感じに修正。というか、前のソースはミスってました、申し訳ない。
/TestEither/src/main/java/main/TestEither.java
package main; import java.lang.reflect.Field; import entity.EisyunkenEntity; import io.vavr.control.Either; public class TestEither { public static void main(String[] args) { // TODO 自動生成されたメソッド・スタブ EisyunkenEntity eisyunkenEntity = new EisyunkenEntity(); eisyunkenEntity.setSyonento("小念頭"); eisyunkenEntity.setJinkyo(null); eisyunkenEntity.setHyosi("標指"); TestEither either = new TestEither(); either.logic(eisyunkenEntity).right().forEach(s -> { // 処理成功 System.out.println("Success"); System.out.println(s); System.out.println(s.getJinkyo().length()); }); either.logic(eisyunkenEntity).left().forEach(s -> { System.out.println("failure"); System.out.println(s); }); } public <E> Either<Throwable, E> logic(E entity) { for (Field field : entity.getClass().getDeclaredFields()) { boolean accessible = field.isAccessible(); try { field.setAccessible(true); Class<?> type = field.getType(); // String name = field.getName(); Object value = field.get(entity); if(getObject(type, value) == null) { field.set(entity, ""); } } catch (IllegalArgumentException e) { // TODO 自動生成された catch ブロック e.printStackTrace(); return Either.left(new RuntimeException("不正な引数、または不適切な引数")); } catch (IllegalAccessException e) { // TODO 自動生成された catch ブロック e.printStackTrace(); return Either.left(new RuntimeException("アプリケーションが、配列以外のインスタンス作成、フィールドの設定または取得、メソッドの呼出しを試みた")); } finally { field.setAccessible(accessible); } } return Either.right(entity); } // null チェック private static <T> Object getObject(Class<T> type, Object field) { if(type.isArray()) { getObject(type, field); } if (field == null) { return null; } return field; } }
んで、実行。
とりあえず、Entityにnullが含まれていた場合、「""」に置き換えていくことはできたっぽいですかね、相変わらず、リフレクション使ってしまっていますが...
ソースで出てくる、「T」とか「E」とかは、ジェネリクスっていうらしいです。
というか、Either使ってる場合も、Stream API を使ってるってことにして良いのだろうか?Javaチャンピオンが、Stream APIのラムダ式における例外処理のベストプラクティスで回答してたのが、Eitherだから良いとしますか。
デザインパターンとか勉強しないとですね...その前に、クラス図とかも勉強せねばですかね...やらなければいけないことが多すぎて辛い...そして、何を勉強していけば良いのかが分からない...
今回はこのへんで。