概要
「1.4」で止まってたJavaの知識をバージョンアップするため
Java7,8,9について学習しています。
今回は「8」で追加された機能について調べた事のみを記載します。
調べたこと
Optional
戻り値にnullが含まれる事があるかどうかを明示的に表すことが出来るようになりました。
また、取り出した時に値が無かった場合の処理も設定できます。
public Optional<String> getGyunyu(String name) {
Map<String, String> gyunyu = new HashMap<>();
gyunyu.put("牛乳", "コーヒー牛乳");
return Optional.ofNullable(gyunyu.get(name));
}
Optional<String> gyunyu = getGyunyu("紅茶");
System.out.println(gyunyu.orElse("牛乳じゃない")); // 「牛乳じゃない」が出力される
- ofNullable(T value) : Optionalを生成する。値がnullでない場合はその値を記述するOptionalを返し、それ以外の場合は空のOptionalを返す。
- orElse(T other) : 存在する場合は値を返し、それ以外の場合はotherを返す。
isPresent()
メソッドを使うことで値の存在チェックを行うことも出来ます。
Optional<String> gyunyu = getGyunyu("紅茶");
if (!gyunyu.isPresent()) {
System.out.println("牛乳じゃない");
}
これだと、if(戻り値 == null)
と大差ない気がしますが、nullだったら値を取り直すとか
別の処理を追加で行いたい時に使用出来そう。
interface
interfaceが実装を持てるようになりました。
defaultキーワードを記述する事で、実装を持つメソッドを記述する事ができます。
public interface Gyunyu {
public default void print() {
System.out.println("牛乳");
}
}
public class Coffee implements Gyunyu {
// printメソッドをオーバーライドしてないけどコンパイルエラーにならない
}
Gyunyu gyunyu = new Coffee();
gyunyu.print();
Gyunyu
インターフェースを継承したMilk
インターフェースを作成し、
Ichigo
クラスに実装してprint()
メソッドを呼び出すと、
public interface Milk extends Gyunyu {
public default void print() {
System.out.println("ミルク");
}
}
public class Ichigo implements Milk {
}
Gyunyu gyunyu = new Ichigo();
gyunyu.print(); //「ミルク」が出力される
メソッドを呼び出しているクラスから最も近い位置にあるメソッド(今回はMilk
インターフェースのprint()
)が実行されます。
Date and Time API
現在日時を取得
now()
メソッドを使うことで現在日時を取得出来ます。
//タイムゾーン情報なし
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime.getYear());
System.out.println(localDateTime.getMonthValue());
System.out.println(localDateTime.getDayOfMonth());
System.out.println(localDateTime.getHour());
System.out.println(localDateTime.getMinute());
System.out.println(localDateTime.getSecond());
System.out.println(localDateTime.getNano());
//タイムゾーン情報あり
ZonedDateTime zoneDatetime = ZonedDateTime.now();
System.out.println(zoneDatetime.getYear());
System.out.println(zoneDatetime.getMonthValue());
System.out.println(zoneDatetime.getDayOfMonth());
System.out.println(zoneDatetime.getHour());
System.out.println(zoneDatetime.getMinute());
System.out.println(zoneDatetime.getSecond());
System.out.println(zoneDatetime.getNano());
System.out.println(zoneDatetime.getZone().getId()); // 「Asia/Tokyo」が出力される
和暦で表示
JapaneseDate japaneseDate = JapaneseDate.now();
System.out.println(japaneseDate.getEra()); //「Heisei」が出力される
System.out.println(japaneseDate.get(ChronoField.YEAR_OF_ERA)); //「29」が表示される
加算・減算
LocalDateTime localDateTime = LocalDateTime.now();
LocalDateTime tomorrow = localDateTime.plusDays(1);
System.out.println(tomorrow.getDayOfMonth()); // 11月29日に実行した場合は「30」が出力される
LocalDateTime yesterday = localDateTime.minusDays(1);
System.out.println(yesterday.getDayOfMonth()); // 11月29日に実行した場合は「28」が出力される
- 年の加算を行いたい場合は、
plusYears(long years)
を用いる - 年の減算を行いたい場合は、
minusYears(long years)
を用いる - その他の加算・減算については、LocalDateTimeクラス
Base64エンコード・デコード
標準でサポートされるようになりました。
String coffee = "コーヒー牛乳";
Encoder encoder = Base64.getEncoder();
String encodeCoffee = encoder.encodeToString(coffee.getBytes());
System.out.println(encodeCoffee); // 「44Kz44O844OS44O854mb5Lmz」が出力される
Decoder decoder = Base64.getDecoder();
String decodeCoffee = new String(decoder.decode(encodeCoffee));
System.out.println(decodeCoffee); // 「コーヒー牛乳」が出力される
文字列の連結
String.join()
を使うことで、複数の文字列を任意の文字列で結合する事が簡単にできるようになりました。
String[] gyunyu = {"コーヒー牛乳", "れもん牛乳", "いちご牛乳"};
System.out.println(String.join("###", gyunyu)); //「コーヒー牛乳###れもん牛乳###いちご牛乳」で出力される
List<String> gyunyu = new ArrayList<>();
gyunyu.add("コーヒー牛乳");
gyunyu.add("れもん牛乳");
gyunyu.add("いちご牛乳");
System.out.println(String.join("###", gyunyu)); //「コーヒー牛乳###れもん牛乳###いちご牛乳」で出力される
java.util.StringJoiner
クラスでも同様の結合が出来ます。
StringJoiner gyunyu = new StringJoiner("###");
gyunyu.add("コーヒー牛乳");
gyunyu.add("れもん牛乳");
gyunyu.add("いちご牛乳");
System.out.println(gyunyu); //「コーヒー牛乳###れもん牛乳###いちご牛乳」で出力される
StringJoinerクラスは、プレフィックスとサフィックスを指定して結合することも出来ます。
StringJoiner gyunyu = new StringJoiner(" > ", "[好き]", "[嫌い]");
gyunyu.add("コーヒー牛乳");
gyunyu.add("れもん牛乳");
gyunyu.add("いちご牛乳");
System.out.println(gyunyu); //「[好き]コーヒー牛乳 > れもん牛乳 > いちご牛乳[嫌い]」で出力される
ラムダ式
下記のような文法で記述される。
(メソッドの引数) -> {処理}
ラムダ式を用いる事で、処理を簡潔に書けるようになったらしい・・・
下記のようなインターフェースと処理があった場合
public interface Gyunyu {
public void output(String v);
}
Gyunyu gyunyu = new Gyunyu() {
@Override
public void output(String v) {
System.out.println(v);
}
};
gyunyu.output("コーヒー牛乳"); //「コーヒー牛乳」と出力される
ラムダ式を用いると、下記のような記述にする事ができます。
Gyunyu gyunyu = (String v) -> { System.out.println(v); };
gyunyu.output("コーヒー牛乳"); //「コーヒー牛乳」と出力される
さらに、引数の型は推論されるので省略可能。引数が1つの場合は、括弧「()」も省略可能。
式が1つの場合、波括弧「{}」も省略可能なので下記のような記述にする事も出来ます。
Gyunyu gyunyu = v -> System.out.println(v);
gyunyu.output("コーヒー牛乳");
関数型インターフェース
抽象メソッドをひとつだけ宣言したインターフェースのこと。
ラムダ式のところに書いてあるGyunyuインターフェースを関数型インターフェースにすると、
下記のような記述になります。
@FunctionalInterface
public interface Gyunyu {
public void output(String s);
}
@FunctionalInterface
アノテーションを付けることで、関数型インターフェースである事を明示的に表すことができ、
関数型インターフェースの条件を満たしていない場合にコンパイルエラーを出すことが出来るようになります。
メソッド参照
メソッド参照は、引数を省略できるため、ラムダ式よりも記述量を減らす事ができる。
staticメソッドや、インスタンスメソッドを実行させる方法。
- 表記方法
クラス名(インスタンス名)::メソッド名
インスタンスメソッドを参照する場合
Gyunyu gyunyu = System.out::println;
gyunyu.output("コーヒー牛乳"); //「コーヒー牛乳」と出力される
上記をラムダ式で記述すると、下記のように記述する事ができます。
Gyunyu gyunyu = v -> System.out.println(v);
gyunyu.output("コーヒー牛乳");
参照したいメソッドが自クラスの場合は、this
を用いる
public void println(String v) {
System.out.println(v);
}
public void execute() {
List<String> gyunyu = Arrays.asList("コーヒー牛乳", "いちご牛乳", "れもん牛乳");
gyunyu.forEach(this::println);
}
public static void main(String[] args) {
(new Main()).execute();
}
staticメソッドを参照する場合
public static void output(String v) {
System.out.println(v);
}
public static void main(String[] args) {
List<String> gyunyu = Arrays.asList("コーヒー牛乳", "いちご牛乳", "れもん牛乳");
gyunyu.forEach(Main::output);
}
Stream API
値の集計、データを使った処理を行うことが出来るAPI。
基本的な処理の流れとしては、
1. ストリーム生成
2. 中間処理
3. 終端処理
のようになる。
ストリーム生成
- 配列の場合
String[] gyunyu1 = {"コーヒー牛乳", "コーヒーミルク", "いちご牛乳", "いちごミルク", "れもん牛乳", "レモンミルク"};
Stream<String> stream1 = Arrays.stream(gyunyu1);
String[] gyunyu2 = {"メロン牛乳", "りんご牛乳", "メロン牛乳", "ぶどう牛乳"};
Stream<String> stream2 = Stream.of(gyunyu2);
- Collectionの場合
List<String> gyunyu3 = Arrays.asList("フルーツ", "ばなな", "すいか");
Stream<String> stream3 = gyunyu3.stream();
- Mapの場合
Map<String, String> gyunyu4 = new HashMap<>();
gyunyu4.put("1", "もも牛乳");
gyunyu4.put("2", "びわ");
gyunyu4.put("3", "いちじく牛乳");
Stream<Entry<String, String>> stream4 = gyunyu4.entrySet().stream();
参考:ストリーム生成
中間操作
Steamの絞り込み、編集などを行い、新しいStreamを作成するための処理。
- 要素を絞り込む場合
-
filter
メソッドを用いる事で指定した要素のみのStreamを取得できます。
-
// 牛乳を含む文字列だけを抽出したい
String[] gyunyu1 = {"コーヒー牛乳", "コーヒーミルク", "いちご牛乳", "いちごミルク", "れもん牛乳", "レモンミルク"};
Stream<String> stream1 = Arrays.stream(gyunyu1);
stream1.filter(s -> s.contains("牛乳"));
- 重複した要素を除きたい場合
-
distinct
メソッドを用いる事で重複した要素を取り除いたStreamを取得できます。
-
// 重複している牛乳を除きたい
String[] gyunyu2 = {"メロン牛乳", "りんご牛乳", "メロン牛乳", "ぶどう牛乳"};
Stream<String> stream2 = Stream.of(gyunyu2);
stream2.distinct();
- 要素を変換したい場合
-
map
メソッドを用いる事で要素を更新したStreamを取得できます。
-
// 牛乳を付け加えたい
List<String> gyunyu3 = Arrays.asList("フルーツ", "ばなな", "すいか");
Stream<String> stream3 = gyunyu3.stream();
stream3.map(s -> s + "牛乳");
参考:中間操作
終端処理
終端処理を実行すると中間処理が評価される。
- 要素数を取得したい場合
-
count
メソッドを用いることで要素数を取得する事ができます。
-
// 牛乳を含む件数を出力したい
Map<String, String> gyunyu4 = new HashMap<>();
gyunyu4.put("1", "もも牛乳");
gyunyu4.put("2", "びわ");
gyunyu4.put("3", "いちじく牛乳");
Stream<Entry<String, String>> stream4 = gyunyu4.entrySet().stream();
System.out.println(stream4.filter(s -> s.getValue().contains("牛乳")).count()); // 「2」が出力される
- 要素を1つずつ取り出して操作したい場合
-
forEach
メソッドを用いることで副作用を発生させる事ができます。
-
String[] gyunyu1 = {"コーヒー牛乳", "コーヒーミルク", "いちご牛乳", "いちごミルク", "れもん牛乳", "レモンミルク"};
Stream<String> stream1 = Arrays.stream(gyunyu1);
stream1.filter(s -> s.contains("牛乳")).forEach(s -> System.out.println(s));
参考:終端処理
終わりに
業務で担当しているサービスが、Java5,6で動いているため、8を業務で使う機会がありませんが、
機会を作ってバージョンアップさせたいと思っています。
StreamAPIとラムダ式に未だ馴染めていないので、この2つの機能について、もっと学習が必要だと感じています。