ラベル java8 の投稿を表示しています。 すべての投稿を表示
ラベル java8 の投稿を表示しています。 すべての投稿を表示

2014年1月31日金曜日

Java 8を使ってみる(使用編・Stream編1)

Java 8には、Streamというクラスが追加されています。
面倒くさい説明を少々しますので飛ばす人はこちらへ→スキップ


StreamはC#でいうIEnumbleに準ずるもので、中間処理を実行しても実際の絞り込みは
実行されず、終端処理を呼び出された際に実行されるというものです。

下の例のような記述ができます。

int sumOfRedWeights  = blocks.stream().filter(b -> b.getColor() == RED)
                                           .mapToInt(b -> b.getWeight())
                                           .sum();

ここでは、blocksをgetColor()==REDで絞り込み(filter)、
getWeight()を抽出して(mapToInt)合計しています(sum)。
中間処理のfilter、mapToIntでは絞込は行われず、
終端処理のsumが呼ばれる際に遅延的に実行されます。


言い換えると、繰り返し処理を関数的に記述できるようになるというところでしょうか。
また、終端処理まで状態を保持しないため並列化にも適しています。
stream()のかわりにparallelStream()を使うことによって、処理を並列的に実行します。
サーバではもともとマルチスレッドなので他のコアも処理してたりして、
あまり恩恵ないかもしれないですが、可読性と並列化が捗りますね\(^o^)/。



ということで、
StreamとSQLのクエリと比較してみましょう。


単純なSELECTでTestMember.idのみを抽出する

//SQL
SELECT TestMember.id

//Listに変換
List list = members.stream().map(TestMember::getId)
        .collect(Collectors.toList());


//Setに変換
Set set = members.stream().map(TestMember::getId)
        .collect(Collectors.toCollection(TreeSet::new));


TestMember::getIdは関数渡しでメソッドを渡しています。
map中でgetIdを呼び出した値を利用しています。

 

Member.weightの合計を求める

//SQL
SELECT sum(TestMember.weight)



total = members.stream()
        .mapToInt(x -> x.getWeight())
        .sum();

合計を求める方法には、Collectorを使う方法もあります

int total = members.stream()
        .collect(Collectors.summingInt(x -> x.getWeight()));


平均を求める

//SQL
SELECT avg(TestMember.weight)



double average = members.stream()
        .mapToInt(x -> x.getWeight())
        .average()   
        .orElse(0d);

orElse()というメソッドが出てきました。
average()は、OptionalDoubleというNull許容なオブジェクトを返します。
OptionalDoubleのNullの場合のデフォルト値を指定してdoubleを返すのがorElse()です。
OptionalDoubleは、C#でいうところのdouble?ですね。


切り出し

何番目の要素を指定して取り出すということもできます。


//SQL
SELECT TestMember.weight LIMIT 1

members.stream()
.mapToInt(x -> x.getWeight())
.findFirst()
.orElse(0);


//SQL
SELECT * OFFSET 1 LIMIT 2
members.stream()
.skip(1)
.limit(2).toArray(); 



グループ・パーティション

Stream には group byや partitioned byに当たるメソッドもあります

//グループ分け
Map         = members.stream()
                    .collect(Collectors.groupingBy(x -> x.getPart()));


同じpartのTestMemberをpart毎のマップにする。


本当は、groupingByには関数渡しで、TestMember::getPartとできるはずが、
エラーになっちゃいますね。


//パーティション
Map        members.stream()
                .collect(Collectors.partitioningBy(s -> s.getId() >= 5));




その他

条件に一致したものが一件でもある

boolean match = members.stream().anyMatch(b -> b.getId() > 5);

countを使うより軽いはず?


weightでソートする
ソートをstream、ラムダ式で実装してみた

members.stream().sorted((r,l) -> {
       return Integer.compare(r.weight, l.weight);
}).collect(Collectors.toList());   

ソートするだけならもっと効率のいい方法があるかな
parallelStreamが使えるから早いのか?むむ?



今回使ったプロジェクト->StreamSample.zip

次回もStream編がつづく

2014年1月23日木曜日

Java 8を使ってみる (Timeクラス編)

Java 8を使ってみよう 3回目は、Timeクラスです。


前回はインターフェースを扱いました。流れ的に次はラムダ式かStreamかと思ったのですが、
まとめるのが難しいので先に箸休めでTimeクラスを紹介します。


さて、Dateクラス使うの面倒臭いと思いませんか。
日付処理をしようとするとCalendarクラスを使わないといけないし、
差分とかとっていろいろしたいけど…、
という要望に答えた、CalenerクラスとDateクラス(+α)を統合した
のがJava 8からのTimeクラスです。



DateTime


時間を表すクラスには、いくつかあります。
代表的なのは、
LocalDateTime : タイムゾーンを含まない、日時
ZonedDateTime : タイムゾーンを含む、日時
になります。
ほかにもいろいろ追加されてますが、ここではLocalDateTimeを扱います。


日付処理


今日の3時のDateクラスを作成したいとなった時に今までは、
Calendarクラスを使っていました。

Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, 3);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);

Date date = cal.getTime();


LocalDateTimeでは、クラスに日付処理が組み込まれているので、
Calendarのようなクラスを呼ぶ必要はありません。

 LocalDateTime datetime = LocalDateTime.now()
                .withHour(3).withMinute(0).withSecond(0).withNano(0);

指定日時のDateも作成しやすくなりました。

LocalDateTime datetime1 = LocalDateTime.of(2014, 5, 21, 1, 12);


一行で書けます。Javaではちょっと前から、一行で書くというのに
狂信的な人たちがいますね。





日付差分


Dateクラスは比較がafter,beforeくらいだったのですが
Java 8からは、専用のクラスが追加されてかなり拡張されています。
むしろこちらの方が嬉しい

ふたつのLocalDateTimeクラスの差分をDurationクラスで表せます。

Duration duration = Duration.between(datetime, datetime1);

このdurationは、datetimeからdatetime1までのナノ秒を保持しています。
LocalDateTimeクラスに、plusすることでdurationの秒数を追加できます。

LocalDateTime datePlused = datetime.plus(duration);

他にもminusとかできます。


Durationがあると、C#のTimeSpanのようなことができます。
この記事の内容をTimeSpanからDurationに置き換えると次のようになる。

public static final TimeSpan TIMESPAN = TimeSpan.Days(7);



Duration duration1 = Duration.ofDays(1);


で現在と差分を足したDateを求めるには、

Date date = TIMESPAN.getDate(new Date());
 


now.plus(duration1);

とすればいい。


何に利用するかというと主に
Memcachedに渡すExpireの 定義とかです。


Memcached.get(key, new Memgetable<String>() {
    public String get() {
        return value;
    }
}, TIMESPAN.getDate(new Date()));



Memcached.get(key, () -> {
        return value;
 }, Date.from(now.plus(duration1).toInstant());

こんな風に記述ができます。


まだ、*DateTimeに対応してないのでDateに直さないといけないのが面倒


つづく




2014年1月20日月曜日

Java 8を使ってみる(使用編・インターフェース)

準備編のつづきです。
今回はJava 8 を実際に使ってみましょう。


重みづけ抽選のロジック(以下RandomUtil)を、Java 8だとどうなるのか見ていきます。
ここでは、主にインターフェースの変更点についてみていきます。


FunctionalInterface


Java 8はインターフェースにFunctionalInterfaceというものが追加されました。
インターフェースに対して@FunctionalInterface宣言をするまたは、
一つのメソッドのみ定義するとFunctionalInterfaceと認識されます

RandomUtilだと、

public interface Weight<T> {
    int getWeight(T temp) ;
}

はJava 8ではFunctionalInterfaceとなります。

@FunctionalInterface
public interface Weight<T> {
    int getWeight(T temp) ;
}

と宣言をつけたものと同じ意味です。


FunctionalInterfaceにはラムダ式を渡すことができます。


TestMember member = RandomUtil.weightedRandom(members,
                    new Weight<TestMember>() {
                        public int getWeight(TestMember m) {
                            return m.weight;
                        }
                    });

以前までは、上記のように関数を渡していたのですが、
ラムダ式を渡すようにすると、次のように簡略化できます。

TestMember member = RandomUtil.weightedRandom(members,
                    m -> {
                        return m.weight;
                    });



Default Method


また、インターフェースにDefaultMethodを追加できるようになりました。


public interface Weight<T> {
    default int getWeight(T temp) {
        return 1;
    }
}

上のように定義した場合、

TestMember member = RandomUtil.weightedRandom(members,
                    new Weight<TestMember>() { });

とすると、getWeight の定義をしなくてもDefaultMethodとして定義している
メソッドが呼ばれます。ちなみに、DefaultMethodを定義していると
FunctionalInterfaceにはできません。



つづく


使用したプロジェクト:RandomUtil1.zip








2014年1月19日日曜日

Java 8を使ってみる(準備編)

遅ればせながら、Java8を触ってみようと思います。
以前、昨年の5月かそれぐらいに、一度動かしてみようと思っていたのですが
対応するIDEもなく、あきらめて日数がかなりたってしまいました。


Java 8の特徴は、なんといってもラムダ式の導入と
C#でIEnumbleにあたるStreamを導入し、Linqのような実装ができるようになるということです。
C#もつかう身としてJavaでLinqポイ実装ができるのは感慨もひとしおです。



まずは、Java 8が動作する環境を作ります。
Java 8はまだ正式リリースではないので、あくまでも試験的なものです。


Java 8 JDKのインストール


JDK 8 Early Access Releasesをダウンロードします。

https://jdk8.java.net/download.html

から環境に適したものをダウンロード、インストールします。


Eclipseのインストール


Eclipseというか、Spring Tool SuiteにJava8 supportのプラグインが
すでに入ったものがあるのでそれを利用します。


http://dist.springsource.com/snapshot/STS/nightly-distributions.html

上記のURLから
Spring Tool Suite - based on Eclipse 4.3 - including Java8 support
とあるところから、環境に適したものをダウンロードして、適当な場所に解凍します。



Eclipseの設定


解凍したフォルダにあるSTS.exeを起動します。
起動したらまず、JDkのパスを通しましょう

Window[メニュー] > Preferences > Java > InstalledJREs

からインストールしたJDK 8 ("C:\Program Files\Java\jdk1.8.0")を追加します。



プロジェクトの追加


Java 8に対応したプロジェクトを追加してみましょう
プロジェクト追加する際に、
Use an execution environment JREからJavaSE-1.8を選びます。
これで、プロジェクトでJava 8が利用できます。


準備編はここまで、使用編につづきます