Java 8 で導入されたStream APIが使われないと思われるのは、どう考えても例外処理の範例が見つからないからに違いない

JJUG CCC 2018 Fall」言って参りました~。

www.java-users.jp

f:id:ts0818:20181215201924p:plain

いろんな、講演があって面白かったです。難しい話も多々ありましたが...

 

qiita.com

⇧  上記サイト様が、講演のスライドをまとめてくださっていました。

 

というわけで、今回も、Java のお話です。 

 

Stream APIラムダ式を使用時の例外処理の行方は...

JJUG CCC 2018 Fall」の講演の中の1つで、『今こそStream API入門(櫻庭 祐一さん) #ccc_g4』 の質疑応答の中で、『ラムダ式での例外処理のベストプラクティスはありますか?』という質問に、『私は、Either を使っています』と。

『Either 』とは?

Either

  • Either<Left, Right> という2つの入れ物を持つ
  • ただしいずれかひとつしか値を入れることができない
  • 慣例として左に例外を、右に正常時戻り値を入れる(正しいのrightにかけている)
  • 読みは íːðɚ アクセントが前

*Java8にEitherは含まれていないので、JAVASLANGライブラリを利用

Java8 Streamから学ぶOptionalモナドとEitherモナド。 · GitHub

⇧『Either 』とは、Java 8 標準では無いらしい... 外部APIやんけ~

blog.y-yuki.net

⇧  しかも、『Javaslang』ではなく、『Vavr』って名前に改名されているらしい...何だかな~...。で、『Vavr(旧:Javaslang)』に含まれる、1機能であるらしいと。

 

『Vavr(旧:Javaslang)』を利用してみる

https://www.vavr.io/ にアクセス。

www.vavr.io

「DOWNLOADS」ってクリックすると、

f:id:ts0818:20181215205608p:plain

ページ内リンクで、Downloads の場所にスクロールするので、「Source Code」を選択。

f:id:ts0818:20181215205755p:plain

https://github.com/vavr-io/vavr に遷移するので、

github.com

「README.md」の、「Using Vavr」の『User Guide』のリンクをクリック。

f:id:ts0818:20181215210846j:plain

Vavr User Guide に遷移するので、スクロールすると、

f:id:ts0818:20181215211120p:plain

f:id:ts0818:20181215211153p:plain

f:id:ts0818:20181215211223p:plain

それぞれの導入方法が説明されています。

 

今回は、「2.1.Gradle」での導入を試してみたいと思います。

まずは、https://search.maven.org/search?q=g:io.vavr%20a:vavr にアクセスし、「Download」で、「jar」を選択。

f:id:ts0818:20181215212948p:plain

ダウンロードされます。

f:id:ts0818:20181215213725p:plain

 

続きまして、Eclipseを起動し、

f:id:ts0818:20181215212140p:plain

「ファイル(F)」>「新規(N)」>「Gradle プロジェクト」で。

f:id:ts0818:20181215212303p:plain

適当な「プロジェクト名」を入力し、「次へ(N)>」。

f:id:ts0818:20181215212444p:plain

一応、、「次へ(N)>」。

f:id:ts0818:20181215212602p:plain

「完了(F)」で。

f:id:ts0818:20181215212729p:plain

「Gradle プロジェクト」が作成されたら、プロジェクトを選択した状態で右クリックし、「新規(W)」>「フォルダー」で。

f:id:ts0818:20181215213859p:plain

外部ライブラリ 配置用のファルダを作成しておきます。

f:id:ts0818:20181215214053p:plain

外部ライブラリ 配置用のファルダに、さきほど、ダウンロードしたjarファイルを配置。

f:id:ts0818:20181215214934j:plain

「ファイルをコピー(C)」にチェックし、「OK」。

f:id:ts0818:20181215215033p:plain

そしたらば、「build.gradle」に依存関係を追記します。

f:id:ts0818:20181215215206p:plain

ファイルは、こんな感じ。 

/*
 * 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()
}
    

ってな感じで。

qiita.com

⇧  上記サイト様が詳しいです。

 

依存関係を追加したらば、プロジェクトを選択した状態で右クリックし、「Gradle」>「Gradle プロジェクトのリフレッシュ」で。

f:id:ts0818:20181215220445p:plain

見事に、解決できてない...

f:id:ts0818:20181215221315p:plain

Eclipse が介入した場合、

d.hatena.ne.jp

⇧  上記サイト様によりますと、「.classpath」に追記が必要らしい...知らんがな。

エクスプローラー」から、ワークスペースのプロジェクトの中にある「.classpath」ってファイルを適当なエディターで編集していきます。

f:id:ts0818:20181215221647p:plain

っていうか、既に記述されとりますやん...

f:id:ts0818:20181215222011p:plain

 

すみませんでした...build.gradleに追加したライブラリ名のタイポでした...

以下の部分を、

    // 今回追加
    compile files('lib/varv-0.9.2.jar')

/*
    // 今回追加
    compile files('lib/vavr-0.9.2.jar')

に修正して「Gradle」>「Gradle プロジェクトのリフレッシュ」で、エラー消えました。

んでは、Javaのクラスファイルを作成していきます。

f:id:ts0818:20181215223848p:plain

お馴染み、mainメソッドを含むクラスファイルを作成。

f:id:ts0818:20181215224040p:plain

作成したら、もう1つ、Entity用(DBのテーブルからの情報を保持する目的で使われるオブジェクト)のクラスを作成。

f:id:ts0818:20181216134834p:plain
「名前(M):」を適当に入力し、「完了(F)」で。 

「パッケージ・エクスプローラー」の構成は、こんな感じに。

f:id:ts0818:20181216135126p:plain

んでは、ファイルを編集で。
 

/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;
	}

}

んで、実行ししてみると。

f:id:ts0818:20181216230102p:plain

leftのほうの結果が表示されました。

f:id:ts0818:20181216230133p:plain

leftのほうをコメントアウトしてみます。 

		//logic(eisyunkenEntity).left().forEach(s -> {
		//    // 処理失敗
		//	System.out.println("failure");
		//	System.out.println(s);
		//});

で実行。

f:id:ts0818:20181216230615p:plain

例外が起きず、処理が終了してしまっているという...

使い方がいまいちよく分かってはいないけど、どちらか片方を返すってことを、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;
	}
}

んで、実行。

f:id:ts0818:20181216235047p:plain
とりあえず、Entityにnullが含まれていた場合、「""」に置き換えていくことはできたっぽいですかね、相変わらず、リフレクション使ってしまっていますが...

ソースで出てくる、「T」とか「E」とかは、ジェネリクスっていうらしいです。

www.slideshare.net

 

というか、Either使ってる場合も、Stream API を使ってるってことにして良いのだろうか?Javaチャンピオンが、Stream APIラムダ式における例外処理のベストプラクティスで回答してたのが、Eitherだから良いとしますか。

デザインパターンとか勉強しないとですね...その前に、クラス図とかも勉強せねばですかね...やらなければいけないことが多すぎて辛い...そして、何を勉強していけば良いのかが分からない...

 

今回はこのへんで。