20190302のJavaに関する記事は19件です。

いまさらJavaのStream APIにおける「stream」「collect」の意味を考察する

はじめに

既に大分前になりますが、JavaにStream APIという関数型プログラミング的手法が導入されました。
関数型プログラミングは既に一般的に広まっており、Javaはかなり後発の部類に入りますが、やはり導入された理由は、うまく使いこなせれば可読性の高いコードを効率的に書けるようになるメリットがあるからと考えています。
(参考:「平凡なプログラマにとっての関数型プログラミング」)

しかし先日、とある講演にて、「Javaのコレクション操作(Stream API)は、Haskellなど純関数型言語と比べると無理やり感があって使いたくない」という話を聞きました。
具体的に言えば、関数型言語ならlist.map(...)で済むものを、Javaの場合は一々
list.stream().map(/* */).collect(Collectors.toList())
みたいにstream()を挟んでやる必要があり、冗長な書き方になってしまうところかなと思いました。

それでも無いよりは良いので私自身Stream APIは比較的好んで使いますが、関数型言語を好む人からすれば受け付けない面はあるのかなと思いました。

では、なぜJavaがlist.map(...)のような簡単な書き方を採用せず、一々stream()を呼び出してStreamという別の型に変換して、collectにて再度変換するようにしたか、についてです。Javaは、言語設計を入念に行う文化があり、その結果に賛否はあれど何かしらの理由があるはずです。その理由は大きく次の2つであると考えています。

  1. 遅延評価
  2. オブジェクト指向による制約

以下、詳しく考えを述べていきます。

遅延評価とは

一般にコレクション操作を行う際、その過程でいちいちコレクションの作り直しをしていては無駄ですし、パフォーマンスへの懸念が生まれます。
これを防ぐには、コードの見た目上はコレクションを徐々に変化させているように見えても、実際には最後に最後にまとめて生成するようにします。このように、値が必要になるまで計算しないという計算方法を遅延評価と呼びます。
例えば、

List<String> list = Arrays.asList("foo", "bar", "hoge", "foo", "fuga");
list.stream()
  .filter(s -> s.startsWith("f"))
  .map(s -> s.toUpperCase())
  .collect(Collectors.toSet()); // ["FOO", "FUGA"]

という感じに、

  • 文字列のリストのうち、"f"から始まるものを抽出する
  • 文字列を大文字に変換する
  • 重複を取り除く(Setに変換)

というコレクション操作を行う際、filterが呼び出された時点で新たな要素数3のコレクションが生まれることはありません。
実際にコレクションが生成されるのは、最後にcollect(Collectors.toSet())が呼び出されたタイミングになります。

※ちなみに、厳密にはこれを遅延評価とは呼ばないみたいですが、他に適切な呼び方がないため遅延評価と呼ぶことにします。(参考:「遅延評価ってなんなのさ」)

遅延評価の対義語は正格評価です。値が不要であってもその時点で計算してしまう方法です。通常はこちらの方が一般的です。

遅延評価のデメリット

メリットがある一方で、注意して使わないと思わぬ落とし穴があります。
以下は(あまり好ましくない書き方ですが)実際に遅延評価により見た目と実際の実行結果の認識齟齬が生まれそうな例です。

// 入出力データ定義
List<String> input = Arrays.asList("foo", "bar", "hoge", "foo", "fuga");
List<String> copy1 = new ArrayList<>();
List<String> copy2 = new ArrayList<>();

// コレクション操作開始. filterを実行
Stream<String> stream = input.stream()
    .filter(s -> {
        copy1.add(s);
        return s.startsWith("f");
    });
System.out.println(copy1.size()); // この時点では、filter操作は実際に評価されないため、copy1は空のままで0が出力される
System.out.println(copy2.size()); // 当然copy2も空のままなので0が出力される

// 続いてコレクション操作のmapを実行
stream = stream
    .map(s -> {
        copy2.add(s);
        return s.toUpperCase();
    });
System.out.println(copy1.size());  // この時点でもまだfilter操作は評価されないため、0が出力される
System.out.println(copy2.size()); // 同様にmap操作も評価されないため、0が出力される

stream.collect(Collectors.toList());
System.out.println(copy1.size()); // stream.collectによりようやくfilterが評価されるため、5が出力される
System.out.println(copy2.size()); // 同様にmap操作も評価されるため、3が出力される

上のコードは、一見するとfilter, mapを呼び出した際にcopy1, copy2のサイズが増えるように見えますが、
実際の動きとしてはcopy1, copy2のサイズが増えるのはstream.collectを呼び出したタイミングとなります。
このように、見た目と実際の評価タイミングにずれがあると、何か問題が起きた際にデバッグしづらく原因の特定が難しくなってしまう危険性があります。

どのようにバランスを取るか

遅延評価は使い方を誤ると複雑なバグを埋め込む危険性があります。かといって、遅延評価を全く使わない場合、無駄にコレクションが生成されてパフォーマンス低下の危険性があります。

Javaの場合、バックエンドで大量データを扱う可能性が普通に考えられる以上、後者のリスクは避けたいため、遅延評価を導入せざるを得ません。しかも、意図せず正格評価が使われないよう、自然(?)と遅延評価になっているようなものが好ましいでしょう。

しかし、だからといってJava標準機能の広範囲に渡り遅延評価を適用できるようにするのはリスキーです。
そのため、遅延評価は特定の型に限定させ、他の型では遅延評価が使われないような作りが妥当な落としどころと考えたのでしょう。

「Stream」の登場

ストリーム とは:

ストリーム(stream)とはデータを「流れるもの」として捉え、流れ込んでくるデータを入力、流れ出ていくデータを出力として扱う抽象データ型である。

先に述べた、唯一、遅延評価を行える型を「Stream」と名付けました。
そして、コレクション操作APIの名前もそのまま「Stream API」、要はコレクション操作がやりたかったら、とりあえず名前の通りstream()を使えということでしょう。

そうすることにより、遅延評価を強制させ正格評価によるパフォーマンス低下のリスクを回避しようとしたと考えています。

オブジェクト指向による制約

streamを介する別の理由として、オブジェクト指向による制約があります。
(厳密にはオブジェクト指向というよりは、型の扱いによる制約に該当しますが、関数型とオブジェクト指向とで対比されることが多いため、ここでは「オブジェクト指向」という言葉を使います。)
仮に、List型に対してdefaultのmapメソッドを定義したとします。

interface List<E> {
    default <R> List<R> map(Function<? super E, ? extends R> mapper) {
        List<R> result = new ArrayList<>();
        for (E elem : this) {
            result.add(mapper.apply(elem));
        }
        return result;
    }
}

このようにしておけば、とりあえずList型の変換をlist.map(...)のように行うことができます。
filter等のメソッドや、他のコレクション型も同じような実装すれば、streamを介さずとも簡潔なという書き方でコレクションの変換を行うことができます。

しかし、この方法は重大な欠点を抱えています。
それは標準ライブラリ以外のコレクション型です。

例えば、ある開発者がListインターフェースを実装したMyListを作成しており、固有のメソッドdoSomethingを追加していたとします。
ここで、MyList型を上記方法でmap変換すると、変換後は別のList型になってしまい、doSomethingが呼び出せなくなってしまいます。

MyList<> mylist = new MyList<>();
// 中略
mylist.doSomething(); //OK
myList.map(x -> new AnotherType(x)).doSomething(); //コンパイルエラー

このあたり、オブジェクト指向言語に関数型プログラミングを取り入れる際の難点となるところでしょう。
とはいえ、実際にこういったケースはあまり見かけないため気にしなくても良い気もしますが、やはりそこはJavaという言語の性格上、許容できないところなのでしょう。

なお、Scalaに関して言えば、この難点を暗黙的な型解決とやらで見事突破しています。
下記補足Aで紹介する書籍に記述されていますので、興味がある方は是非見てみてください。

「Collector」の登場

上記理由により、コレクションの変換操作を始めると、もともとあったコレクションと別のものを再生成せざるを得なくなり、またコレクションの指定はライブラリ側ではなく呼び出し側の責務となります。
それを担うのが「Collector」であり、Stream.collectにより呼び出し側でどのコレクション型に変換するか指定します。
下のソースコードはCollectors.toListの実装内容です。

public static <T>
Collector<T, ?, List<T>> toList() {
    return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                               (left, right) -> { left.addAll(right); return left; },
                               CH_ID);
}

自前で作成したコレクション型MyListも、これと同様の方法でCollectorインスタンスを生成するメソッドを用意しておけば、MyListからMyListへの変換を行うことが可能でしょう。
これにより、Stream API導入以前に作成されたコレクション型も、Stream API上で特別大きな変更を加えず使うことができます。

ちなみに、Stream型にtoListメソッドを作成しない理由

コレクションの変換はCollector型に集約することで見通しが良くなるとはいえ、List型などは日常で頻繁に出てきます。
せめて、stream.collect(Collectors.toList())ではなくstream.toList()ぐらいに簡潔に書けても良いんじゃないかという気もしてきます。
これを行わない理由はおそらく、型の依存関係にあると考えています。
要は、Collection型からStream型への参照は必須ですが、Stream型からCollection型を参照すると相互参照することになり型設計として好ましくない、という理由かなと考えています。

おまじないを唱えて安全に使おう

上記のように、様々なバランス、整合性を考えた結果がlist.stream().map(/* */).collect(Collectors.toList())という冗長とも取れるコレクション操作の形に落ち着いたのだと考察しています。

ある意味で、とてもJavaらしい結論ではないかなと考えています。

Javaを使う際に危ないからStream API使っちゃいけないよという謎プロジェクトが世の中には存在するみたいですが、このように安全性を考慮した作りになっているので、普通に使う分には取り立てて不安がる必要はありません。定型通りにstream, collectを唱えれば、よっぽどのことがない限り問題は起きないでしょう。

おわりに

純関数型言語に拘りがある人以外は、冗長な記述もまぁ許容範囲に収まると思います。
関数型プログラミングをうまく使いこなせれば、可読性の高いコードを効率的に書けるようになります。まだ使っていない人はぜひ試してみてください。
(参考:Java Stream APIをいまさら入門

補足A. Java以外の言語

あまり詳しくありませんが、他の言語がどのような機能を提供しているか参考程度に記載します。

C#

C#のLINQは、Javaと同様に遅延評価です。
Javaと異なるのは、stream()を呼び出して開始する必要がなく、コレクション化する際も例えばToListを呼び出すだけで済むことが多いので、Javaより大分簡潔に書けて便利です。
(参考:「[雑記] LINQ と遅延評価」)
(参考:「C#erなら当然知ってるよね!?LINQ遅延評価のメリット・デメリット」)

Scala

Scalaは関数型言語というのもあり、遅延評価・正格評価の使い分けが可能です。
例えば、list.map(...)で正格評価による別コレクションへの変換ができます。
また、list.view.map(...).filter(...).forceのようにview, forceという形で遅延評価による別コレクションへの変換も可能です。
(参考:「Scala でジェネレータを作ったり、遅延評価してみる」)

なお、はるか昔は正格評価と遅延評価の区別が付き辛く混乱を招いていた時期もあったらしいですが、ある時境界を明確にして

  • View
  • Stream

という2つの型のみ遅延評価の対象として整理したらしいです。
なお、Scalaに関しては「Scalaスケーラブルプログラミング」という本に恐ろしく詳しいことが色々と書かれているので、興味がある方はぜひ読んでみてください。

JavaScript

JavaScript標準には遅延評価はありません。Array.prototypeに、map, filterなど標準的なコレクション操作APIがありますが、どれも正格評価です。
これはおそらく、ほぼクライアントサイドで使用されるJavaScriptで大量データ処理を扱うことはないという前提で、遅延評価は標準搭載不要と考えたのでしょう。

Haskell

Haskellは純関数型言語というのもあり、上に挙げたものとまた毛色が違うようです。
通常の言語は基本が正格評価なのに対して、Haskellは基本が遅延評価です。
そのため、本記事で気にしているような正格評価と遅延評価のバランスと、そもそも無縁のようです。
(参考:「正格評価と遅延評価(詳細編)」)

上記以外(PHP, Ruby, Python, etc...)

そのうち調べます。

補足B. 他のライブラリを使うという選択肢

Java標準以外に、Eclipse collectionsというコレクション操作ライブラリがあります。
これを使うと、Stream APIでは冗長になるものがすっきりと記述できます。
(参考:「Eclipse Collectionsを触ってみた」)
(参考:「Eclipse Collectionsチートシート」)

また、ImmutableListという、不変なListインターフェースが用意されており、より関数型手法の色合いが濃いライブラリです。
Stream APIよりもより関数型的なコレクション操作を扱いたいなら導入も1つの選択肢かと思います。

ただし、全面的にStream APIをEclipse Collectionsに置き換えようとしたら相当な工夫が必要でしょう。
導入する際は、実際に導入を敢行した現場の話「Eclipse Collectionsを現場に浸透させるためのフレームワーク対応」が参考になるかと思います。

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

java Optionalの使い方について

Optionalの宣言方法

Optionalを使用するには型をラップして使う

Optional<String> opt1;

自作クラスでも同様

testClass.java
public class testClass
{
    private String no;
    private BigDecimal val;
}
OPtional<testClass> opt2;

使い方

optional型に値をいれるにはoptional.of か optional.ofNullableを使用する。

Optional<String> opt1 = Optional.of("test");

ただ、optional.ofは引数がnullの場合Exceptionが発生するので注意。

Optional<String> opt1 = Optional.of(null);

よって、optional.ofNullableを使用する。

Optional<String> opt1 = Optional.ofNullable("test");
Optional<String> opt2 = Optional.ofNullable(null);

testClass test = new testClass();
Optional<String> opt3 = Optional.ofNullable(test);

値の取り出し方

値を取り出すには、以下を使用する。
get : nullの場合、Exception発生
orElse : null出ない場合変数値、nullの場合orElseの引数を返却
orElseGet : null出ない場合変数値、nullの場合,suppelierの結果を返却

String val1 = opt1.get();
String val2 = opt1.orElse("")

自作クラスから値を取り出すときは、以下を使用してフィールドを取得できる。
map

String val1 = opt3.map(testClass::getNo).orElse("1");
String val2 = opt3.map(v -> v.getNo()).orElse("2");
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spock Test FrameworkでSystem.outをMockにする

はじめに

今年の新人向け研修向けにJavaコードを全部手直し+テストを書いて変更してもテストで保証するようにという対応をしていました。
その際にパラメータも使うしSpockでいいやとして作った時に際にはまったのでメモです。
(Junit5で楽になりましたが、それでも面倒なので自分はspock使うことが多いです)

環境

Java8
Groovy 2.5
spock-core 1.2-groovy-2.5
Gradle4.10

今回の現象

spockでJavaのSystem.outをMock化して表示したものを確認するというテストを作成していました。
その際にさらさらーっと書いて動かしたら起動時にエラーとなってしまうという現象に見舞われました。

問題となるコード

現象を再現するためにHello Worldで確認します。
本体のコードとテストコードは以下の通りです。

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}
class HelloWorldTest extends Specification {
    def mainTest() {
        setup:
        PrintStream printStream = Mock()
        System.out = printStream

        when:
        HelloWorld.main(null)

        then:
        1 * printStream.println("Hello World")
    }
}

解決策

cglib-nodepとobjenesisをDependencyとして追加してあげればOKです(エラーにもそう書いてます)。
なので、追加して動かせば終わりです。
最終的に動作した.build.gradleは以下の通りです。

plugins {
    id 'java'
    id 'groovy'
}

group 'com.tasogarei'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    testCompile "org.codehaus.groovy:groovy:2.5.6"
    testCompile "org.spockframework:spock-core:1.2-groovy-2.5"
    testCompile "cglib:cglib-nodep:3.2.10"
    testCompile "org.objenesis:objenesis:3.0.1"
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spock Test FrameworkdでSystem.outをMockにする

はじめに

今年の新人向け研修向けにJavaコードを全部手直し+テストを書いて変更してもテストで保証するようにという対応をしていました。
その際にパラメータも使うしSpockでいいやとして作った時に際にはまったのでメモです。
(Junit5で楽になりましたが、それでも面倒なので自分はspock使うことが多いです)

環境

Java8
Groovy 2.5
spock-core 1.2-groovy-2.5
Gradle4.10

今回の現象

spockでJavaのSystem.outをMock化して表示したものを確認するというテストを作成していました。
その際にさらさらーっと書いて動かしたら起動時にエラーとなってしまうという現象に見舞われました。

問題となるコード

現象を再現するためにHello Worldで確認します。
本体のコードとテストコードは以下の通りです。

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}
class HelloWorldTest extends Specification {
    def mainTest() {
        setup:
        PrintStream printStream = Mock()
        System.out = printStream

        when:
        HelloWorld.main(null)

        then:
        1 * printStream.println("Hello World")
    }
}

解決策

cglib-nodepとobjenesisをDependencyとして追加してあげればOKです(エラーにもそう書いてます)。
なので、追加して動かせば終わりです。
最終的に動作した.build.gradleは以下の通りです。

plugins {
    id 'java'
    id 'groovy'
}

group 'com.tasogarei'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    testCompile "org.codehaus.groovy:groovy:2.5.6"
    testCompile "org.spockframework:spock-core:1.2-groovy-2.5"
    testCompile "cglib:cglib-nodep:3.2.10"
    testCompile "org.objenesis:objenesis:3.0.1"
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java 初心者備忘録2 JSPサーブレット

Udemy サーチマン佐藤氏のJava講座 サーブレット編

JSPサーブレットについてメモ

・動的プロジェクト→サーバー登録
webconten->index.jsp
webinf->web.xml
src(ソース)->info

index.jspより右クリック、サーバーで実行
文字化け対処
https://siragumohuinblog.wordpress.com/2015/07/24/eclipse-tomcat-servlet-mysql%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E6%97%A5%E6%9C%AC%E8%AA%9E%E3%81%AE%E6%96%87%E5%AD%97%E5%8C%96%E3%81%91%E3%81%AE%E8%A7%A3%E6%B1%BA/
http://pandazx.hatenablog.com/entry/2013/12/26/223314
http://craftone.hatenablog.com/entry/20080702/1215012494

結果:サーチマン佐藤氏の回答にあったutf-8を、デフォルトのMS932 にしてみましょう。
であっさり解決。。
eclipseの表記が一瞬で直りました。サーバーは都度再起動する。

MySQL 苦戦したところ
https://qiita.com/_natsu_no_yuki_/items/ae4c94187093e4ab3cdc

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

Java 初心者備忘録2

Udemy サーチマン佐藤氏のJava講座 サーブレット編

JSPサーブレットについてメモ

・動的プロジェクト→サーバー登録
webconten->index.jsp
webinf->web.xml
src(ソース)->info

index.jspより右クリック、サーバーで実行
文字化け対処
https://siragumohuinblog.wordpress.com/2015/07/24/eclipse-tomcat-servlet-mysql%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E6%97%A5%E6%9C%AC%E8%AA%9E%E3%81%AE%E6%96%87%E5%AD%97%E5%8C%96%E3%81%91%E3%81%AE%E8%A7%A3%E6%B1%BA/
http://pandazx.hatenablog.com/entry/2013/12/26/223314
http://craftone.hatenablog.com/entry/20080702/1215012494

結果:サーチマン佐藤氏の回答にあったutf-8を、デフォルトのMS932 にしてみましょう。
であっさり解決。。
eclipseの表記が一瞬で直りました。サーバーは都度再起動する。

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

Canvasから動画を生成するWebサービスを試作してみた。

はじめに

3年ぶりの投稿です。
最近、Codepenなどを使ってCanvasで遊んでいるのですが、Canvasの画像・動きを動画として残せないかと考えるようになりました。
調べてみると、CCapture.jsなど、Canvasから動画を生成するライブラリがあるようなので、Herokuを使って、簡単なCanvas動画生成サービスを作ってみました。

作ったもの

成果物   :CapCanvas
ソースコード:Github

使い方

htmlヘッダーに以下の行を追加します。なお、idはcanvasのid、fpsはフレームレート、timeは録画時間(ms)を指定します。
'C'キーを推すとCanvasのキャプチャが開始され、録画終了後、動画(webm)がダウンロードされます。
<script type="text/javascript" src="https://capcanvas.herokuapp.com/capcanvas/id=app&fps=60&time=10000"></script>

実装

■JavaScript
Jsは、CCaputureのサンプルコードを参考に、以下のように実装しました。なお、$URL$ID$FPS$TIMEはサーバー側で文字列に置換します。

CapCanvas.js
window.onload = function(){
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "$URL";
document.body.appendChild(script);
};
window.addEventListener('keydown', event => {
if(event.key=='c'||event.key=='C'){
let canvas=document.getElementById("$ID");
startCapture(canvas,$FPS,$TIME);
}
});
let startCapture=function(canvas,fps,time){
let cap=new CCapture({format:'webm',framerate: fps,verbose: true});
let render=function(){
requestAnimationFrame(render);
cap.capture( canvas );
};
requestAnimationFrame(render);
cap.start();
setTimeout(function(){
cap.stop();
cap.save();
},time);
};

■サーバー
サーバー側はリクエストパラメータを解析し、CapCanvas.jsを書き換えて送信します。
個人的にお気に入りのSpark Frameworkで作成しています。

Main.java
package net.termat.webapp.capcanvas;

import static spark.Spark.get;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import spark.ModelAndView;
import spark.Spark;
import spark.template.mustache.MustacheTemplateEngine;

public class Main {
    private static String code;

    public static void main(String[] args) {
        Spark.staticFileLocation("/public");
        code=getJs();
        Optional<String> optionalPort = Optional.ofNullable(System.getenv("PORT"));
        optionalPort.ifPresent(p -> {
            int port = Integer.parseInt(p);
            Spark.port(port);
        });

        get("/", (request, response) -> {
            Map<String, Object> model = new HashMap<>();
             return new ModelAndView(model, "index.mustache");
        }, new MustacheTemplateEngine());

        get("/capcanvas/:param", (request, response) -> {
            try{
                String url="https://capcanvas.herokuapp.com/js/CCapture.all.min.js";
                Map<String,String> map=paramMap(request.params("param"));
                String id=map.get("id");
                String fps=map.get("fps");
                String time=map.get("time");
                response.status(200);
                String ret=new String(code);
                ret=ret.replace("$URL", url);
                ret=ret.replace("$ID", id);
                ret=ret.replace("$FPS", fps);
                ret=ret.replace("$TIME", time);
                return ret;
            }catch(Exception e){
                e.printStackTrace();
                response.status(400);
                response.type("application/json");
                return "";
            }
        });

    }

    private static String getJs(){
        try{
            URL url=Main.class.getResource("CapCanvas.js");
            BufferedReader br=new BufferedReader(new InputStreamReader(url.openStream()));
            StringBuffer buf=new StringBuffer();
            String line=null;
            while((line=br.readLine())!=null){
                buf.append(line);
            }
            return buf.toString();
        }catch(Exception e){
            return "";
        }
    }

    private static Map<String,String> paramMap(String param) throws Exception{
        Map<String,String> ret=new HashMap<String,String>();
        String[] p=param.split("&");
        for(int i=0;i<p.length;i++){
            String[] k=p[i].split("=");
            if(k.length<2)continue;
            ret.put(k[0], k[1]);
        }
        return ret;
    }
}

デプロイ

herokuへは、heroku-maven-pluginを使用してデプロイしました。
Port番号の解決に気が付かず、実際に動作させるまで2~3時間かかってしまいました。

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

VScodeエクステンションRUN-CODEでのjavaプログラムの文字コードによるエラー回避

どうしたいか。

VScodeで簡単にコンパイル・実行するためにCode Runnerという拡張機能を使用しています。
 MAC上のVScodeで作成したjavaプログラムを、他のOS上のVScodeで動かした場合に文字コードによるエラーが出るので解消する設定を記載します。流行のCoderでもうまくいくようです。

設定内容

 個別に文字コードの設定をしてやる。(ここでは、UTF-8)

"code-runner.executorMap": {
"java": "cd $dir && javac -encoding UTF-8 $fileName && java -Dfile.encoding=UTF8 $fileNameWithoutExt"
}

関連情報

https://westhillworker.com/visualstudiocode-cpp/

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

VScode拡張機能RUN-CODE使用時のjavaでの文字コードエラー回避

どうしたいか。

VScodeで簡単にコンパイル・実行するためにCode Runnerという拡張機能を使用しています。
 MAC上のVScodeで作成したjavaプログラムを、他のOS上のVScodeで動かした場合に文字コードによるエラーが出るので解消する設定を記載します。流行のCoderでもうまくいくようです。

設定内容

 個別に文字コードの設定をしてやる。(ここでは、UTF-8)

"code-runner.executorMap": {
"java": "cd $dir && javac -encoding UTF-8 $fileName && java -Dfile.encoding=UTF8 $fileNameWithoutExt"
}

関連情報

https://westhillworker.com/visualstudiocode-cpp/

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

オブジェクト指向でじゃんけん

はじめに

最近Javaを勉強し始めたので、オブジェクト指向で何かを作ってみたかった。

流れ

①名前を入力する。(相手の名前はランダムに決まる。)
②出す手を選ぶ。(相手の手はランダムに決まる。)
③結果を出力する。
④終わり。(今回は一回だけの勝負です。)

クラスたち

①Janken.java
メインの関数です。

Janken.java
public class Janken {
    public static void main(String[] args) {
        Player you   = new You();
        Player enemy = new Enemy();

        System.out.print("おなまえを入力して下さい。: > ");
        you.setName();
        enemy.setName();
        String yourName  = you.getName();
        String enemyName = enemy.getName();
        System.out.println("あなたの なまえは " + yourName);
        System.out.println("あいての なまえは " + enemyName);

        Hand yourHand  = you.nextHand();
        Hand enemyHand = enemy.nextHand();

        System.out.println(yourName   + "は " + yourHand  +  "  をだした。");
        System.out.println(enemyName  + "は " + enemyHand +  "  をだした。");

        if (yourHand.winTo(enemyHand)) {
            System.out.println(yourName + " のかち!");
        } else if (yourHand.loseTo(enemyHand)) {
            System.out.println(yourName + " のまけ…");
        } else {
            System.out.println("あいこです。");
        }
    }
}

②Player.java
プレイヤーの大雑把な情報を持つ抽象クラスです。
このクラスを継承して、あなたと相手のクラスを作ります。

Player.java
public abstract class Player {
    protected String name;

    public String getName(){
        return this.name;
    }

    public abstract void setName();

    public abstract Hand nextHand();
}

③you.java

You.java
import java.util.*;
public class You extends Player {
    @Override
    public void setName() {
        Scanner scanner = new Scanner(System.in);
        String name = scanner.nextLine();
        this.name = name;
    }

    @Override
    public Hand nextHand() {
        Scanner scanner = new Scanner(System.in);

        while (true) {
            System.out.print("何を出しますか? グー:0 チョキ:1 パー:2  > ");
            try {
                int hand_number = Integer.parseInt(scanner.nextLine());
                if (0 <= hand_number && hand_number <= 2) {
                    return Hand.fromInt(hand_number);
                } else {
                    System.err.println("範囲外の数字が入力されています。");
                }
            } catch (NumberFormatException e) {
                System.err.println("数字以外が入力されています");
            }
        }
    }
}

④Enemy.java

Enemy.java
public class Enemy extends Player {
    private final String[] names = {"安倍", "小泉", "麻生", "菅"};

    @Override
    public void setName() {
        String enemyName = names[(int) (Math.random() * names.length)];
        this.name = enemyName;
    }

    @Override
    public Hand nextHand() {
        return Hand.fromInt((int) (Math.random() * 3));
    }
}

⑤Hand.java
列挙型のクラスを使いました。

Hand.java
public enum Hand {
    Rock,
    Scissors,
    Paper;

    @Override
    public String toString() {
        switch (this) {
            case Rock     : return "グー";
            case Scissors : return "チョキ";
            case Paper    : return "パー";
        }
        throw new IllegalStateException();
    }

    public static Hand fromInt(int n) {
        switch (n) {
            case 0 : return Rock;
            case 1 : return Scissors;
            case 2 : return Paper;
        }
        throw new IllegalArgumentException(Integer.toString(n));
    }

    public boolean winTo(Hand hand) {
        switch (this) {
            case Rock     : return hand == Scissors;
            case Scissors : return hand == Paper;
            case Paper    : return hand == Rock;
        }
        throw new IllegalStateException();
    }

    public boolean loseTo(Hand hand) {
        return this != hand && !this.winTo(hand); 
    }

}

以上。
敵いっぱい作ってトーナメント戦やったり、作戦クラスを作ってみるのも面白そうですね。
改善点あればよろしくお願いいたします。

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

【Java】protectedで修飾されたメソッドがサブクラス内から呼べるとは限らない

概要

Javaのアクセス修飾子の一つに、protectedというものがあります。protectedについて「同一パッケージ内およびサブクラスからのみアクセス可能」というような説明を聞いたことがないでしょうか。このような理解だと若干の誤解が生じるかもしれないという話をします。

同一パッケージ内からのアクセスについては特に混乱する要素はないと思うので、サブクラスからのアクセスについてのみ言及します。

対象読者

Java初心者

環境

Java11で動作確認を行いましたが、内容としては古いバージョンでも特に変わらないと思います。

うまくいかない例

「サブクラスからアクセス可能」という理解だと、下記のサンプルコードは動作すると思えますが、実際にはコンパイルエラーになります。
(ネーミングセンスについてはご容赦願います…)

package test.sp;

public class Super {
    protected void method() {
        System.out.println("Super#method() called.");
    }
}
package test.sub;
import test.sp.Super;

public class Sub extends Super {
    public static void main(String[] args) {
        Sub sub = new Sub();
        sub.run();
    }

    private void run() {
        Super sp = new Super();
        sp.method();  // エラー: method()はSuperでprotectedアクセスされます
    }
}

protectedで修飾されたmethod()メソッドを持つSuperクラスと、そのSuperクラスを継承したSubクラスがあります。
SuperクラスとSubクラスはそれぞれ別のパッケージにありますが、SubクラスはSuperクラスを継承している(SubクラスはSuperクラスのサブクラスである)ため、Subクラスからmethod()メソッドを呼び出すことは可能であるように思えます。しかし、上記のコードだとコンパイルエラーとなってしまいます。

うまくいく例

package test.sp;

public class Super {
    protected void method() {
        System.out.println("Super#method() called.");
    }
}
package test.sub;
import test.sp.Super;

public class Sub extends Super {
    public static void main(String[] args) {
        Sub sub = new Sub();
        sub.run();
    }

    private void run() {
        // (1)
        Sub sub = new Sub();
        sub.method();

        // (2)
        this.method();

        // (3)
        method();

        // (4)
        super.method();
    }
}

今度はうまくいく例ですが、SuperクラスおよびSuperクラスとSubクラスとの関係は変わらず、Subクラスからの呼び出し方法だけ変わっています。(1)から(4)までありますが、どれもコンパイルが通り、実行時にも特にエラーは発生せずに動作しました。

// (1)
Sub sub = new Sub();
sub.method();

(1)はSubクラスのインスタンスを明示的に生成して、そのインスタンスのmethod()を呼び出しています。Superクラスのインスタンスではコンパイルエラーになりましたが、Subクラスのインスタンスであれば問題ないようです。

// (2)
this.method();

// (3)
method();

(2)はthisでの呼び出しです。これも動作します。(3)はthisの記述を省略しているだけで、(2)と実質同じです。thisは自身のインスタンスを指すわけなので、Subクラスのインスタンスのmethod()を呼んでいるという意味では、(1)〜(3)のいずれも同じです。

// (4)
super.method();

(1)〜(3)と少し異なるのが(4)です。(4)はsuperのmethod()を呼び出していますが、これも動作します。superは(自身と関連づけられている)スーパークラスのインスタンスを指します。Superクラスのインスタンスを明示的に生成して、そのインスタンスのmethod()を呼び出した際はコンパイルエラーとなったのに、superであれば問題ないのですね。
いずれもSuperクラスのインスタンスという点では変わらないので同じ挙動になると思っていたのですが、ちょっと意外な結果となりました。

まとめ

protectedで修飾されたメソッドについて、別パッケージで定義されたサブクラスからの可視性は下記のようになります。

  • サブクラス内で、newで明示的に生成されたサブクラス自身のインスタンスから呼び出せる
  • サブクラス内のthisから呼び出せる。thisの記述を省略して呼び出す場合も同様
  • サブクラス内のsuperから呼び出せる
  • サブクラス内で、newで明示的に生成されたスーパークラスのインスタンスからは呼び出せない
  • protectedの可視性について「同一パッケージ内およびサブクラスからのみアクセス可能」と理解していると、上記の呼び出せないパターンについて呼び出せると勘違いする可能性がある

蛇足

同一パッケージ内からのみ呼び出し可能にしたいという意図でprotectedで修飾されていると思われるメソッドを見かけることがありますが、この場合はprotectedではなくパッケージプライベート(修飾子なし)を使うべきです。
protectedって個人的にはあまり使いません。(使い方を理解してないのかもしれませんが…)

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

意外と知らない、Javaの予約語まとめ

概要

Javaの予約語は、頻繁に見かけるものからあまり見かけないものまで、色々あります。
私の独断と偏見で選んだ、あまり見かけない予約語について簡単に紹介します。

あまり長くならないように、一つ一つに対してそれほど詳細な話はしません。

なお、Javaの言語仕様上「予約語」は「キーワード(keyword)」と呼ばれるそうですが、
「予約語」のほうが一般的な気がするので、この記事では「予約語」という呼称を使用します

対象読者

Java初心者
Javaをある程度使いこなしている方にとっては既知の内容になるかもしれません(予防線)

環境

Java11

Javaの予約語一覧

まずは予約語の一覧を見てみます。公式の言語仕様書によると、Java11時点で予約語は下記の通り51個あります。
https://docs.oracle.com/javase/specs/jls/se11/html/jls-3.html#jls-3.9

abstract   continue   for          new         switch
assert     default    if           package     synchronized
boolean    do         goto         private     this
break      double     implements   protected   throw
byte       else       import       public      throws
case       enum       instanceof   return      transient
catch      extends    int          short       try
char       final      interface    static      void
class      finally    long         strictfp    volatile
const      float      native       super       while
_ (underscore)

この中からいくつかピックアップして紹介してみます。

予約語(keyword)

assert

Javaで表明(assertion)を使うための予約語です。
表明とはプログラムを実行する際に満たされているべき前提条件を記述するもので、誤解を恐れずに言えば変数の値が想定外の値になっていないかどうか等をチェックする仕組みのようなものです。

assertの後に条件式を書き、条件式の評価値がfalseだったらAssertionErrorが投げられます。
例えば、メソッドの引数の値が想定通りの値かどうかをチェックするなどの用途で使用できます。

// 割り算
private int divide(int x, int y) {
    assert y != 0;  // 割り算なのでyは0であってはならない

    return x / y;
}

また、assertは実行時に-enableassertions(または-ea)を指定しないと有効になりません。
上記オプションを渡すかどうかで、有効無効を切り替えられます。
開発中は有効にして、本番稼働時は無効にするといったことが可能です。

const

C言語などでは定数を宣言するための予約語ですが、Javaでは何の意味も持たない予約語です。
何の意味もないのに、なぜか予約語としては存在しています。
constは構文上は何の意味も持たないため、使用するとコンパイルエラーになります。

なお、Javaで定数を宣言するにはconstではなくfinalを使用します。

// 予約語なのでシンタックスハイライトはされる
const int num = 1;
//  エラー:
//  式の開始が不正です
//  const int num = 1;
//  ^

default

複数の用途がある珍しい予約語です。バージョンが上がるにつれて追加されていきました。

  • switch文で、合致する項目がなかった場合に実行される箇所を示すラベル
  • アノテーションの定義で、定数のデフォルト値を指定する修飾子(Java 5から)
  • インタフェースの定義で、デフォルトメソッドであることを示す修飾子(Java 8から)

switch文の例

switch(i) {
    case 1:
        System.out.println("一");
        break;
    case 2:
        System.out.println("二");
        break;
    default:
        System.out.println("その他");
}

アノテーションの例

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Range {

    int min() default Integer.MIN_VALUE;

    int max() default Integer.MAX_VALUE;
}

インタフェースのデフォルトメソッドの例

public interface Person {
    default String name() {
        return "unknown";
    }
}

goto

C言語などでは任意の箇所に制御を飛ばすのに使用する予約語ですが、const同様にJavaでは何の意味もないのに存在している予約語です。
gotoに相当する機能はJavaにはありません。gotoを乱用するとソースコードの可読性が著しく下がるため、比較的モダンな言語では同等の機能が存在しないことが多いです。

constと同様、使用するともちろんコンパイルエラーです。

// やはりシンタックスハイライトはされる
goto label;
//  エラー:
//  式の開始が不正です
//  goto label;
//  ^

native

メソッドの中身がC言語などのネイティブ言語で実装されていることを示す予約語です。
JNIなどを使って実行されます。

public native boolean createDirectory(File f);

strictfp

浮動小数点の計算結果が、実行環境に依存して変化しないようにする(どの実行環境でも同じ結果になるようにする)ための予約語です。
メソッドやクラスを修飾します。
逆に言えば、strictfpで修飾しない場合は浮動小数点の計算結果は実行環境によって変わる場合があるということです。

個人的には、実際に使われているのを一度も見たことがありません。

private strictfp double calc(double x, double y) {
    return x * y;
}

transient

修飾したフィールドがシリアライズの対象外になります。
シリアライズとは、オブジェクトの内容をファイルに保存したり、他のマシンに送信したりできるように変換を行うことです。
transientで修飾することで、シリアライズ後のデータから該当のフィールドが除外されます。

public class Person implements Serializable {
    private String firstName;
    private String lastName;
    private transient String fullName;
}

volatile

修飾したフィールドについて、シングルスレッド前提の最適化を抑止します。
見たことはあるが、どういう意味があるのかわからないという方もいらっしゃると思います。
私もよくわかりません。

volatileで修飾しない場合、コンパイラが最適化のためにソースと若干違う内容のバイトコードを出力することがありますが、volatileによってそのような最適化が抑止されます。
シングルスレッドの場合は特に影響ありませんが、マルチスレッドだとコンパイラによる最適化によって不整合が生じる場合があります。volatileはそのような不整合の発生を防ぐのに使われます。

volatile boolean shutdownRequested;

...

public void shutdown() { shutdownRequested = true; }

public void doWork() { 
    while (!shutdownRequested) { 
        // do stuff
    }
}

https://www.ibm.com/developerworks/jp/java/library/j-jtp06197.html より

上記例では、shutdown()が他のスレッドから呼ばれたらループを抜けるという動作を期待していますが、shutdownRequestedをvolatileで修飾しないと想定通りに動作しない可能性があります。

…そのはずだったのですが、実際試したらvolatileなしでも普通に動きました。誰か詳しい人教えてください!

_(underscore)

Scalaなどの言語では、_はプレースホルダ等の用途で使用しますが、Javaにはそのような機能はありません。const、gotoと同様に意味のない予約語(記号?)です。
_が予約語になったのはJava9と比較的最近で、将来の機能拡張を見据えてのことなのかもしれません。

int _ = 1;
//  エラー:
//  リリース9から'_'はキーワードなので識別子として使用することはできません
//  int _ = 1;
//      ^

番外編

かつて存在した予約語

widefp

strictfpの逆で、浮動小数点の計算結果が環境に依存するようになります。
現在は既定でそのような動作であるため、widefpは削除されました。

予約語っぽいけど予約語じゃないもの

リテラル

true および false

おなじみのbooleanリテラルです。実はこれらはJavaの言語仕様上は予約語ではないのです。
まあ、識別子としては使えないので実質予約語みたいなものですけどね。

null

おなじみのnullリテラルです。これもbooleanリテラルと同様に、識別子としては使えませんが、言語仕様上は予約語ではありません。

制限されたキーワード (restricted keyword)

open, module, requires, transitive, exports, opens, to, uses, provides, with

いずれもモジュール機能のためにJava9で追加されました。一つ一つ取り上げていると長くなるし私もよく把握できていないので説明は省略します。
いずれも特別な意味を持つ言葉ですが、変数名などとして使うことが可能です。おそらく、互換性のためでしょうね。

String module = "module";

予約された型名(reserved type name)

var

Java10から追加された型推論機能のために使われます。
これも上記の「制限されたキーワード」と同様、特別な意味を持ちますが変数名などとして使うことが可能です。これも互換性のために予約語としていないのかもしれません。

var var = 1;

まとめ

Javaの予約語から、予約語っぽいけど予約語じゃないものまで、いろいろ見てきました。
そんなのあったんだ、と思ってもらえたらうれしいです。

参考リンク

https://docs.oracle.com/javase/specs/jls/se11/html/jls-3.html#jls-3.9
https://ja.wikipedia.org/wiki/%E3%82%AD%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%89_(Java)
https://java.keicode.com/lang/assert.php
https://www.wdic.org/w/TECH/strictfp
https://www.ibm.com/developerworks/jp/java/library/j-jtp06197.html
https://www.wdic.org/w/TECH/%E4%BA%88%E7%B4%84%E8%AA%9E%20%28Java%29

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

SalesforceのデータローダーがZulu OpenJDK 11でリリース

概要

Salesforceのデータローダーは、これまでOracle Java 8でビルドされていましたが、Ver.45のリリースで「Zulu OpenJDK 11」でビルドされるようになりました。下記のGitHubで公開されているので、早速導入してみました。
https://github.com/forcedotcom/dataloader/releases/tag/v45.0.0

Zuluとは
https://qiita.com/nowokay/items/edb5c5df4dbfc4a99ffb
ZuluはAzul Systemsが提供しているOpenJDKビルドです。Azul Systemsは OpenJDKのサポートを行う企業で元OracleのSimon Ritter氏が属している会社です。
CustomerにMicrosoftもあるので、たぶんMicrosoft Azureもzuluを使っているんではなかろうか。

手順

Dataloaderをダウンロード

https://github.com/forcedotcom/dataloader/releases/download/v45.0.0/dataloader_mac.zip
image.png
現時点では、まだdmgは公開されておらず、jarを直接実行して起動する必要があります。
Zuluを入れないで、Java 8で実行すると下記のエラーになります。

Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.UnsupportedClassVersionError: com/salesforce/dataloader/process/DataLoaderRunner has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)

Zulu Open JDKをインストール

https://www.azul.com/downloads/zulu
image.png
※Java 8の無料アップデートは、あと45日とのこと・・・

image.png

PATH(JAVA_HOME)をアップデート

Java 8のパスが設定されているはずなので、Zuluのパスでアップデートします。

export JAVA_HOME=/Library/Java/JavaVirtualMachines/<zulu_package>/Contents/Home/

コマンドから起動

Macである場合は、下記のコマンドからDataloaderを起動できます。

java -XstartOnFirstThread -jar target/dataloader-xx.0-uber.jar

image.png

まとめ

JDKが変わっただけで大きな変化はないと思いますが、本番での作業等を行う場合には念の為に早めに新しいバージョンに切り替えて実施した方がいいです。

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

Java 融通がきかないStringクラスのsubstring

JDK 1.8で書いています。

Stringクラスのsubstring

Stringクラスには、文字列の一部を切り出すsubstringメソッドがあります。引数の数に応じて2種類の切り出し方をしてくれます。

引数が1つの場合

定義

    substring(int beginIndex)

beginIndex以降の文字を切り出してくれる。文字列のインデックスは0から数えることに注意。

使用例

    String str = ("ドラえもん");
    String cutStr = str.substring(2);
    System.out.println("切り出し後:" + cutStr);

結果

    切り出し後:えもん

指定しているインデックス2にある文字以降の文字列が切り出される。

引数が2つの場合

定義

    substring(int beginIndex, int endIndex)

beginIndex以降の文字をendIndex-1の文字まで含めて切り出してくれる。
使用例

    String str = ("ドラえもん");
    String cutStr = str.substring(2, 4);
    System.out.println("切り出し後:" + cutStr);

結果

    切り出し後:えも

指定しているbeginIndex = 2にある文字以降から、endIndex - 1 = 3にある文字までの文字列えもが切り出される。

ここが微妙

引数が2つの場合、文字列がendIndex - 1に満たない長さの場合、例外が出ます。

ユースケース

画面表示する文字列を出力する。画面には4文字までしか表示できない制限があるため、出力する文字列を4文字までに絞りたい。もし4文字に満たない文字列であればそのまま出したい。

コード

    String str1 = ("ドラえもん");
    String str2 = ("のび太");
    String str3 = ("スネ夫");

    List<String> sourceStrList = Arrays.asList(str1, str2, str3);
    List<String> outStrList = new ArrayList<>();

    for (String sourceStr : sourceStrList) {
        outStrList.add(sourceStr.substring(0, 4));
    }

    System.out.println("切り出し後:" + outStrList);

結果

Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 4

はい、例外発生。ここではのび太スネ夫はインデックスが2までしかないので、endIndex - 1 = 3に満たないため、例外になるのです。
解消するには事前に文字の長さを調べる必要があるので面倒くさいですね~。

StringUtilssubstringを使って解決

org.apache.commons.lang3.StringUtilsに用意されているsubstringを使えば、この辺の気をきかして解決してくれます。

    substring(String str, int start, int end)

strに入れた切り出し対象の文字列のインデックスがstartからend - 1の文字列が出力されます。

コード

    String str1 = ("ドラえもん");
    String str2 = ("のび太");
    String str3 = ("スネ夫");

    List<String> sourceStrList = Arrays.asList(str1, str2, str3);
    List<String> outStrList = new ArrayList<>();

    for (String sourceStr : sourceStrList) {
        outStrList.add(StringUtils.substring(sourceStr, 0, 4));
    }

    System.out.println("切り出し後:" + outStrList);

結果

    切り出し後[ドラえも, のび太, スネ夫]

end - 1 = 3に満たない文字列も、そのまま出力してくれます。

参考

java.lang String substring
org.apache.commons.lang3 Class StringUtils substring

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

doma2でデータベース連携(Spring boot)

※アウトプット用メモですので他記事と被っている箇所があるかもしれません。悪しからず。

Doma2とは

データベースアクセスに使います。特徴としては以下。
公式(https://doma.readthedocs.io/en/stable/)によると

Doma 2は、Java 8以降用のデータベースアクセスフレームワークです。Domaにはさまざまな長所があります。

・注釈処理を使用してコンパイル時にソースコードを検証して生成します。
・データベース列をユーザー定義のJavaオブジェクトにマップします。
・「2-way SQL」と呼ばれるSQLテンプレートを使用します。
・java.time.LocalDate、 java.util.Optionalおよびjava.util.stream.StreamなどJava 8で導入されたクラスをサポート
・他のライブラリに依存しない

ほとんど翻訳のままですので少し日本語がおかしいかもしれません。。。

大きな利点としては以下の2点であるかと
・JRE 以外のライブラリへの依存が一切ない(依存性には敏感ですww)
・SQLをファイルですべて管理できる(疎結合ってやつですかね?)
これは自分が使ってみた意見ですが、SQLファイルで管理することで過剰な共通化を防ぐ利点があると思いました。
共通化のしすぎは逆にわかりにくくなることがあります。

実装

公式にサンプルプロジェクトがありましたのでこちらがわかりやすいかと思います。
公式サンプル
主に必要な構成としては
・Dao (Daoはデータベースアクセスのためのインタフェース)
・Entity (テーブルを表現する(だけではないですが)クラス)
・SQLファイル (クエリー)
になります。

DaoでCRUD処理をするメソッドを定義します。

MstEmployeeDao.java
package com.example.dao;

import com.example.entity.MstEmployee;
import com.example.entity.UserEntity;
import org.seasar.doma.Dao;
import org.seasar.doma.Insert;
import org.seasar.doma.Select;
import org.seasar.doma.boot.ConfigAutowireable;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/** 従業員マスタのDaoインターフェース. */
@ConfigAutowireable
@Dao
public interface MstEmployeeDao {

  @Select
  List<MstEmployee> selectAll();

  @Select
  MstEmployee selectOne(String id);

  @Select
  UserEntity selectUser(String id);

  @Insert
  @Transactional
  int insert(MstEmployee mstEmployee);
}

このメソッドでやりたい問い合わせをSQLファイルにしてやろう。という使い方です。SQLファイルを配置する場所はMETA-INFの中に上記クラス内のpackage com.example.dao;と同じようにフォルダ階層を作成しかつファイル名をメソッド名と同じ名前にします(拡張子はsql)

サンプルプロジェクトのtreeを見るとわかりやすいと思います。

tree
├── pom.xml ... Maven設定ファイル
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           ├── App.java  ... アプリケーション実行ファイル
    │   │           ├── SecurityConfig.java  ... セキュリティ設定ファイル
    │   │           ├── dao  ... Doma2のDAOクラス格納パッケージ
    │   │           │   ├── MstEmployeeDao.java
    │   │           │   ├── MstNewsDao.java
    │   │           │   └── MstRoleDao.java
    │   │           ├── dto
    │   │           │   └── NewsDto.java
    │   │           ├── entity  ... Doma2のEntityクラス格納パッケージ
    │   │           │   ├── AuditEntity.java
    │   │           │   ├── MstEmployee.java
    │   │           │   ├── MstNews.java
    │   │           │   ├── MstRole.java
    │   │           │   ├── UserEntity.java
    │   │           │   └── listener
    │   │           │       └── AuditListener.java
    │   │           ├── security  ... Spring Securityの認証機能
    │   │           │   ├── LoginUserDetails.java
    │   │           │   ├── LoginUserDetailsService.java
    │   │           │   └── UserInfo.java
    │   │           ├── service  ... サービスクラス格納パッケージ
    │   │           │   ├── NewsService.java
    │   │           │   └── NewsServiceImpl.java
    │   │           └── web ... Spring MVC コントローラクラス格納パッケージ
    │   │               ├── LoginController.java
    │   │               ├── NewsController.java
    │   │               ├── TopController.java
    │   │               └── manager
    │   │                   ├── NewsForm.java
    │   │                   ├── NewsManagerEditController.java
    │   │                   ├── NewsManagerListController.java
    │   │                   └── NewsManagerRegisterController.java
    │   └── resources
    │       ├── META-INF
    │       │   └── com
    │       │       └── example
    │       │           └── dao ... Doma2のSQLファイル格納パッケージ
    │       │               ├── MstEmployeeDao
    │       │               │   ├── selectAll.sql
    │       │               │   ├── selectOne.sql
    │       │               │   └── selectUser.sql
    │       │               ├── MstNewsDao
    │       │               │   ├── selectAll.sql
    │       │               │   ├── selectNewsDtoByCond.sql
    │       │               │   └── selectOneNewsDto.sql
    │       │               └── MstRoleDao
    │       │                   └── selectAll.sql
    │       ├── ValidationMessages.properties ... バリデーションチェックのメッセージ設定ファイル
    │       ├── application.yml ... アプリケーション設定ファイル
    │       ├── data.sql ... 起動時に実行するDDL文
    │       ├── messages.properties ... バリデーションチェックメッセージのFormプロパティ置換設定ファイル
    │       ├── schema.sql ... 起動時に実行するDML文
    │       ├── static ... 実行時にはコンテキストルート直下になる静的ファイル格納フォルダ
    │       │   ├── css
    │       │   │   └── lib ... CSSライブラリ格納フォルダ
    │       │   │       ├── bootstrap-theme.min.css
    │       │   │       └── bootstrap.min.css
    │       │   ├── fonts ... フォント格納フォルダ(Bootstrap)
    │       │   │   ├── glyphicons-halflings-regular.eot
    │       │   │   ├── glyphicons-halflings-regular.svg
    │       │   │   ├── glyphicons-halflings-regular.ttf
    │       │   │   ├── glyphicons-halflings-regular.woff
    │       │   │   └── glyphicons-halflings-regular.woff2
    │       │   ├── js
    │       │   │   └── lib ... JavaScriptライブラリ格納フォルダ
    │       │   │       └── bootstrap.min.js
    │       │   └── movie
    │       │       ├── sample
    │       │       │   └── nc144421.mp4
    │       │       └── thumbnail
    │       │           ├── nc144421.jpg
    │       │           └── nc144555.jpg
    │       └── templates ... Thymeleafテンプレート格納フォルダ
    │           ├── learning
    │           │   └── learning.html
    │           ├── loginForm.html
    │           ├── manager
    │           │   ├── managerLayout.html
    │           │   └── news
    │           │       ├── edit
    │           │       │   ├── newsEditComplete.html
    │           │       │   ├── newsEditConfirm.html
    │           │       │   └── newsEditInput.html
    │           │       ├── list
    │           │       │   └── newsList.html
    │           │       └── register
    │           │           ├── newsRegisterComplete.html
    │           │           ├── newsRegisterConfirm.html
    │           │           └── newsRegisterInput.html
    │           ├── news
    │           │   └── news.html
    │           └── top
    │               └── top.html
    └── test ... テストコード格納フォルダ
        └── java
            └── com
                └── example
                    └── service
                        └── NewsServiceImplTest.java

パラメータの書き方

以下のようにsqlファイルを記述することでDaoクラスの変数とsqlファイルの変数をバインドすることができます。

selectOne
SELECT
    employee_id,
    employee_last_name,
    employee_first_name,
    role_id
FROM mst_employee
WHERE
  employee_id = /* id */'employee_id';

さらに、以下のように記述することで動的条件を表現できます。
if文とか、アノテーションが使えるんですね。

selectNewsDtoByCond.sql
select
  n.mst_news_id id,
  n.role_id role_id,
  r.role_name role_nm,
  n.subject subject,
  n.url url,
  n.version version
FROM
  mst_news n
INNER JOIN
  mst_role r
ON
  n.role_id = r.role_id
WHERE
/*%if @isNotEmpty(url) */
    n.url LIKE /* @prefix(url) */'http'
/*%end*/
/*%if @isNotEmpty(subject) */
AND
    n.subject LIKE /* @prefix(subject) */'今日'
/*%end*/
/*%if @isNotEmpty(roleId) */
AND
    n.role_id = /* roleId */'01'
/*%end*/
ORDER BY
  n.mst_news_id

Spring bootのデータベースアクセスではJPAを使っていましたが、こっちの方が標準みたいですね。

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

迷路を作って解くJava実装

本文

  • やったこと:Javaで迷路を作り、到達可能か求める(深さ優先探索 DFS)、または最短経路を求める(幅優先探索 BFS)
  • ソース => GitHub
  • 実行結果 => Ideone
  • その他の入力例 => README

実行時の注意

  • 6つの引数が必須、順番は固定
  • -debugで迷路・経路を標準出力に書き込める
    • ただし出力量制限はない
    • 小さい迷路から大きな迷路へ、どのくらい表示できるか確認しながら出力した方がいい
  • -debugの代わりに-no-denugとか適当に入れれば標準出力OFFになる
  • 大きさは2501*2501を超えた辺りから、生成の実行時間が長くてつらい
  • 4つ目の引数circuitsに大きな数字を入れると、生成の実行時間が長くてやっぱりつらい
    • 100000とか無理

雑感

  • 迷路を作る実装はレアかも
  • 1.最短距離と2.書き換えた経路の両方を返す方が好ましいが、面倒なのでやってない
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android の CSV ライブラリ

Java のライブラリなら、
なんでも Android で使用できると思っていたが。
そうではなかったので、まとめておく。

Open CSV

カンマで分解して、文字列の配列にして、並び順で取得する。

http://opencsv.sourceforge.net/

サンプルプログラムは、ここに
https://github.com/ohwada/Android_Samples/tree/master/Csv1

下記のようなヘッダー行があるファイル
は、Java オブジェクトにマッピングする機能もある。

"Name","Quantity"
"Apple", "10"
"Banana", "20"

しかし、Android では、下記のエラーになる。

NoClassDefFoundError: Failed resolution of: Ljava/beans/Introspector

Android に は必要なライブラリがないようです。

参考 : Didn't find class “java.beans.Introspector” when Using OpenCSV to parse csv files

univocity-parsers

CSV ファイルを Java オブジェクトにマッピングする機能がある。

https://www.univocity.com/pages/univocity_parsers_tutorial

使い方

CSV ファイルのカラムに合わせたJavaクラスを用意する。
アノテーションでカラムと変数の対応を指定する。

@Data
public class Hoge {

@Parsed(field = "Name")
public String name = "";

@Parsed(field = "Quantity")
public int quantity = 0;

CSV ファイルの読み込み

    // Assetフォルダーから読み込む場合
    Reader reader = new InputStreamReader( getAssets().open(ファイル名) );

        CsvParserSettings settings = new CsvParserSettings();
    BeanListProcessor<Shopping> rowProcessor = new BeanListProcessor<>(Hoge.class);
        settings.setProcessor(rowProcessor);

 CsvRoutines routines = new CsvRoutines(settings);
List<Hoge> list
 = routines.parseAll( Hoge.class,  reader );

サンプルプログラムは、ここに
https://github.com/ohwada/Android_Samples/tree/master/Csv2

Apache Commons CSV

カンマで分解して、文字列の配列にして、並び順で取得する。

https://commons.apache.org/proper/commons-csv/

サンプルプログラムは、ここに
https://github.com/ohwada/Android_Samples/tree/master/Csv3

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

Java Servlet 4.0のweb.xmlのスキーマ定義

Java Servlet 4.0のスキーマ定義が覚えられないのでメモ。
公式の定義がどこに載っているのかわからなかった。

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"                  
         version="4.0">
</web-app>

ついでにスキーマ定義の意味もわかっていなかったので調べてみた

xmlns="http://xmlns.jcp.org/xml/ns/javaee"

「名前空間の名前」を定義する。
これでweb.xmlで使っているタグ要素とかがどの名前空間に属しているかが定義される。

xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"

(一つ宣言を飛ばして)
ここで先に定義した名前空間http://xmlns.jcp.org/xml/ns/javaeeのスキーマ定義ファイル(.xsd)の参照先を指定している。
つまり、とかといった要素は「名前空間http://xmlns.jcp.org/xml/ns/javaeeに属していて、その定義内容はhttp://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsdの定義ファイルを参照してね。」って意味と理解しました。
ちなみにweb-app_4_0.xsdが、Java Servlet 4.0を指していると思われる。

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

で、一つ飛ばしたこの宣言は、xsi:schemaLocationとしてスキーマ定義ファイルの場所を指定する要素schemaLocationが属している名前空間を定義していて、それを別名xsiで宣言している。

つまりまとめると

web.xmlでは2つの名前空間を定義しており、それぞれの用途は以下の通りという解釈。

  1. 使っている要素(タグ)の名前空間は、http://xmlns.jcp.org/xml/ns/javaeeに属している。
  2. http://www.w3.org/2001/XMLSchema-instanceは、1の名前空間の定義ファイルの場所を示すschemaLocationを使うためだけに定義している。

参考

web.xmlのバージョン別DTD・XSDの宣言方法
XMLにおける名前空間

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

Dockerでのデータベース環境構築in Spring boot(IntellJ)

Spring boot + dockerでデータベース連携するぞ!

初記事。
ご指摘たくさんください。

Spring bootでポートフォリオを作ろうと思った時にdockerでのDB構築に手間取ったのでメモ。

今回のゴール

サンプルプロジェクトにてdockerを用いて構築したデータベース(Mysql)の値を画面へ出力する

・対象読者...

docker初心者
or
開発経験の浅い(1、2年)エンジニア

今回注力する観点

・dockerfileの作成の仕方
・docker上に作成したMySQL環境とspring bootプロジェクトの連携

※dockerの概念などはザックリ説明→とりあえずdockerとアプリケーションを連携するところまで

dockerとは

@kotaro-drさんの記事を載せさせていただきます。
【図解】Dockerの全体像を理解する -前編-
┗ドキュメントが綺麗で初学者でもかなりわかりやすい
【図解】Dockerの全体像を理解する -中編-
┗中編の最初にはデータベースの永続化にも関わってくるデータ管理の話が出てくるので今回は最低そこまでチラ見すればOKです。
ついでに後編も載せておきます。
【図解】Dockerの全体像を理解する -後編-

なぜdockerなのか

・Vurtualboxよりも軽量である←テーマと焦点がズレるため詳しくは割愛。
・開発環境をすぐに用意できる
┗「この端末では動くのに自分の端末では動かない。」ということがなくなる。
┗知り合いに質問したりteratailなどのQAサービスで質問したりするときに自分の今の環境をそのまま相手に渡すことができ、回答が返って来やすくなる。
※今回はローカルマシンにIntelliJとJavaが入っていることを前提に進めます。Javaをdockerに含めることもできますが今回の観点を
「dockerでのMysql環境の構築とアプリケーションとの連携」としているため。
IntelliJとJavaのインストールについては以下に載せます。
■IntelliJ
IntelliJ -for mac
IntelliJ -for Windows
■Java11
Java -for mac
Java -for Windows

早速やってみよう

■環境情報
言語:Java(jdk11)
FW:Spring boot
IDE:IntelliJ
※今回、IDEをIntelliJにしてみました。操作感はほぼeclipseと同じですが、UIがなんかいい感じ。今では世界シェアはeclipseを抜いているとかいないとか。。。
まずはプロジェクトを簡単に作ってみたいと思います。
その前にIntelliJにjdkのパスを登録します。以下リンク内で、【Javaプロジェクトの作り方】を検索すると出てきます。
JDKの登録

今回作成するプロジェクトのtreeです。(画像ですみません)

スクリーンショット 2019-02-24 15.49.49.jpg

今までリンクばかりでしたが、そろそろ自分の言葉を出していきたいと思います。

プロジェクトの作成

今回はデータベースの値を全て出す簡単なモノを作ります。
というか見ればわかるレベルなので3分クッキングパターンで、出来たものが以下になります。
docker(github)
※要所でコメントを入れていますのでもし解釈が間違っている等ありましたらご指摘頂けると助かります。

application.propertiesについて

spring.datasource.url=jdbc:mysql://localhost:3306/demo?characterEncoding=UTF-8&serverTimezone=JST 
#//demoが今回のデータベース名
spring.datasource.username=root //ユーザ名
spring.datasource.password=p@ssw0rd  //パスワード
spring.jpa.hibernate.ddl-auto=update  //アプリケーション起動時に、Entityに対応するテーブルがなければ作成

こちらはデータベース連携に必要な最低限の設定です。のちに作成するdocker-compose.ymlの内容と合わせる必要がありますので注意してください。

dockerfileの作成

今回はsrcと同じ階層にdockerというフォルダを作成しその中に関連ファイルを作っていこうと思います。
場所についての記述が見つけられなかったため特に指定はないと認識しています。

fockerfile
FROM mysql:8.0  #dockerhubより取得するイメージを指定

RUN /bin/cp -f /etc/localtime /etc/localtime.org
RUN /bin/cp -f /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

COPY ./my.cnf /etc/mysql/conf.d/

RUN mkdir -p /var/log/mysql
RUN chown mysql.mysql /var/log/mysql

COPY:コマンドの左側がローカル側、右側がdockerイメージ側のことを書いております。
RUN:対象のイメージにインストールされているコマンドを実行できる

docker-compose.yml
version: '3'
services:
    mysql:
        build: ./mysql
        environment:
            - MYSQL_DATABASE=demo
            - MYSQL_ROOT_USER=root
            - MYSQL_ROOT_PASSWORD=p@ssw0rd
            - TZ=Japan
        volumes:
            - ./initdb.d:/docker-entrypoint-initdb.d
            - ./dbdata:/var/lib/mysql
        ports:
            - "3306:3306"

↑コロン(:)で区切られている場合

・左側がホスト側、右側がコンテナ側のパスを表す
・初期データ投入→initdb.dディレクトリに最初に読み込むsqlファイルを入れる
永続化→dbdataに処理をするごとにデータ相当のファイルがどんどん入ってくる

docker-composeを起動させよう

プロジェクト上のdocker-compose.ymlを右クリックし「再生」をします。
スクリーンショット 2019-03-01 22.49.47.jpg
すると、さっき書いたdockerfiledocker-compose.ymlを解釈してせっせか環境構築が始まります。
以下のように'Compose: docker' has been deployed successfully.と出たら完成です。
スクリーンショット 2019-03-01 22.53.15.jpg

では早速、データベースが出来上がっているかコンテナに入って確認してみます。
ターミナル(Windowsの場合はコマンドプロンプト)でプロジェクトのディレクトリへ移動します。
その中で
docker psコマンドで現在のコンテナの稼働状況を確認します。
実行後が下図です
スクリーンショット 2019-03-02 0.18.36.jpg

mysqlのイメージができていることがわかります。
起動にはこちらのCONTAINER IDもしくはNAMESを使います。今回はCONTAINER IDを使って起動してみます。

docker exec -it 【CONTAINER ID】 bashでコンテナに入ります。
root@37c06170b19b:/#こんな感じのターミナル(コマンドプロンプト)になっていれば入れています。
この画面よりrootユーザでMysqlへ接続します。
mysql -u root
今回はパスワードをp@ssw0rdとしてます。
以下のようにmysqlへ接続
スクリーンショット 2019-03-02 0.40.51.jpg

use demoで今回のサンプルデータベースを指定。あとは適当にsqlをうって確認します。初期データとしてはuserテーブルが入っていますので。
select * from user;
スクリーンショット 2019-03-02 0.46.40.jpg
テーブルの中身を確認できました。

あとは自分のショボいサンプルプロジェクトをDockerApplicationから実行すると...
以下のようにデータベースとの連携に成功しました。
スクリーンショット 2019-03-02 0.52.29(2).jpg

このようにdockerを使えば環境ごと相手に渡すことができるのでわざわざ相手にデータベースのセットアップやデータの投入をさせずに済みます。
dockerの知識はまだまだ浅いですが、これからどんどん使われていく事になると思いますので、積極的に使っていきたいものです。

次回は、Spring bootで認証画面を試してみたいと思います。

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