こんにちは!フリーランスのオータケです。
Java8が最初にリリースされてからすでに3年以上が経ちました。
しかし、初心者の方々から
「新機能ってどう使えばいいのか全然わからない」
「どんな場面で使えばいいのかわからない」
といったお困りの声をお聞きします。
そこで、今回は初心者向けにJava8の新機能をわかりやすく解説します!
この記事では、Java8の新機能について
・Javaとは
・Java8の新機能
・Java8の環境構築
という基本的な内容から
・ラムダ式
・Stream API
・Optional
・日付時刻API
・interface
・アノテーション
など応用的な内容についても解説していきます。
今回はJava8の新機能について、使い方をわかりやすく解説します!
なお、Javaの記事については、こちらにまとめています。
1 Javaとは
Javaの歴史
1990年代にSunMicrosystemsが開発・発表したオブジェクト指向という考え方を取り入れた言語です。
2010年にOracleが買収しOracle製品の一つとなります。
Javaの特長
同じオブジェクト指向言語であるC++とは違いガベージコレクション等の機能を搭載しメモリの管理をほぼ意識しなくてもよい言語となりました。
またJavaは家電やモバイル機器を始めとしたデバイスに搭載されています。
国内ではフィーチャーフォンやスマートフォンに搭載されておりアプリ開発を行う際にはJava言語を使って開発を行うことになります。
次に2014年3月18日にリリースされたJava8ではいくつかの新機能が追加されているのでその新機能について見ていきましょう。
Java8の新機能
現在の最新バージョン
2017年9月にリリースされたJava9が現在のJavaの最新バージョンです。(2017年11月現在)
主な新機能
今回は、Java8の中でも開発の生産性を上げるための6つの新機能についてわかりやすくご紹介していきます。
概要 | 機能が追加された理由 | 他言語で似ている機能 | |
---|---|---|---|
ラムダ式 | メソッド定義を式として扱える機能 | インターフェースを実装するための記述を簡潔に書ける | JavaScriptの無名関数 PHP(5.3~)のラムダ式 C++0xのラムダ式 |
Stream API | 配列やListなどの要素データの操作を簡潔かつ理解しやすい形で書くことができる機能 | 要素データの操作を簡潔に記述できるため、 プログラムの可読性が向上 データ操作の変更が容易 | PHPやRubyのフレームワークに搭載されているORMの機能に近い |
Optional | 値がnullであるかどうかを簡潔に書くことができる機能 | nullチェック時のミスを減らすことができて、プログラムの堅牢性が向上 | ー |
日付時刻API | Date、Calendarクラスの代替として、次のクラスなどを導入 Instant : 日時(エポック秒) LocalDateTime : タイムゾーンなし日時 ZonedDateTime : タイムゾーンあり日時 | Date、Calenderクラスを扱うのは面倒で機能も乏しいため、Java8から日時に関するAPIが追加 | ー |
interface | interfaceのdefaultメソッド、staticメソッドを導入 | 実装する必要のないメソッドを実装クラスで書く必要がなくなる interfaceにメソッドを追加した時に、既存の実装クラスに影響がない | ー |
アノテーション | 変数の型やジェネリクスの型パラメータにアノテーションがつけられる機能を追加 | 型のチェックなどをコンパイル時に行い、想定外のエラーを避けることができる | ー |
それぞれの内容については、後ほど詳しく解説します。
Java8の環境構築
まずはJava8の開発環境を構築しましょう。
これから使うパソコンにこれまでJavaをインストールしたことがなければ、ダウンロードから始めてください。
以前にインストールしたことがある、もしくはインストールしたかどうかわからない場合はアップデートについての説明から進めてください。
ダウンロード
Java8をダウンロードしてインストールする方法については、こちらで詳しく解説していますので参考にしてください。
アップデート
Java8にアップデートするためには現在パソコンにインストールされているJavaのバージョンがJava8かJava7以前か確認する必要があります。
ここでは、使うパソコンのOSはWindows10の場合を代表例として説明します。
Javaのバージョンを確認する方法は以下のとおりです。
Windows 10-プログラムの確認
1.「スタート」をクリックします。
2.「設定」を選択します
3.「システム」を選択します
4.「アプリと機能」を選択します
5.プログラムの一覧からJavaを探し出してバージョンを確認します
この画像ではJava8がインストールされています。
Java7以前がインストールされている場合は、Javaをアンインストールする必要があります。
Javaをアンインストールできたら、前章のダウンロードの方法を参考にしてJava8をインストールしてください。
Windows 10-プログラムのアンインストール
1.「スタート」をクリックします。
2.「設定」を選択します
3.「システム」を選択します
4.「アプリと機能」を選択します
5.アンインストールするプログラムを選択し、「アンインストール」ボタンをクリックします。
6.プロンプトに従って、アンインストールを完了します
公式参考サイト
https://java.com/ja/download/faq/remove_olderversions.xml
ラムダ式
ラムダ式とは
ラムダ式とは一言でいうと、「メソッド定義を式として扱える機能」のことです。
関数型インターフェースを実装するための記述を簡潔に書けるメリットがあります。
ちなみに、関数型インターフェースとは抽象メソッドを1つ持つインターフェースのことです。
またラムダ式という仕組みはJavaScript、PHP、C++でも採用が進んでいます。
Java8でラムダ式が実装された背景には時代の流れに沿うものといったことも考えられるのではないでしょうか?
ラムダ式の使い方
ラムダ式の基本的な構文は、アロー演算子(->)を使って下記のようになります。
インターフェース名 オブジェクト名 = (引数1, 引数2, ・・・) -> {return 処理内容};
なお、引数の型は記述する必要はありません。
引数の型はコンパイラーによって判断されるので、記述する必要がないのがラムダ式の記述の特徴の一つでもあります。
だからと言って、従来の記述様式のように型を記述してもコンパイルエラーになることもありません。
ラムダ式の引数がない場合、ある場合、戻り値がある場合の記述について表にまとめました。
構文 | 記述例 | |
---|---|---|
引数がない場合 | インターフェース名 オブジェクト名 = () -> 処理; | InterfaceTest it = () -> System.out.println("Hello World"); |
引数がある場合 | インターフェース名 オブジェクト名 = (変数1, 変数2, ・・・) -> 処理; | InterfaceTest it = a -> System.out.println(a.length()); |
戻り値がある場合 | インターフェース名 オブジェクト名 = () -> {return 処理;}; | InterfaceTest it = () -> {return 0;}; |
引数がない場合
Java7までは以下のように記述していました。
Java7:
// インターフェース interface InterfaceTest { // 抽象メソッド public void method(); } public class Main { public static void main(String[] args) { // インターフェースの実装 InterfaceTest it = new InterfaceTest() { public void method() { System.out.println("Hello World"); } }; it.method(); } }
ラムダ式を使うと、以下のように記述することができます。
Java8:
// インターフェース interface InterfaceTest { // 抽象メソッド public void method(); } public class Main { public static void main(String[] args) { InterfaceTest it = () -> System.out.println("Hello World"); it.method(); } }
実行結果:
Hello World
アロー演算子(->)の左側の括弧はメソッドの引数を渡す括弧に相当します。
それをアロー演算子(->)でつなげて右側に処理を書くことでラムダ式として成立します。
今回の場合はSystem.out.printlnメソッドを呼んでいます。
引数がある場合
ラムダ式には引数を渡すことが可能です。
また、引数が1つの場合は変数を囲む括弧(( ))を省略することができます。
さらにさらに、上記の例のように型名の記述も省略することが可能です。
なお、型名の記述の省略については引数が幾つある場合でも省略することが可能です。
Java8:
// インターフェース interface InterfaceTest{ // 抽象メソッド public void method(String s); } public class Main { public static void main(String[] args) { InterfaceTest it = a -> System.out.println(a.length()); it.method("Hello World"); } }
実行結果:
11
戻り値がある場合
ラムダ式では戻り値を返すようなことも可能です。
Java8:
// インターフェース interface InterfaceTest{ // 抽象メソッド public int method(); } public class Main { public static void main(String[] args) { InterfaceTest it = () -> {return 1;}; System.out.println(it.method()); } }
実行結果:
1
3つを組み合わせて使うこともできます。
ラムダ式について、こちらでも詳しく解説しているので参考にしてくださいね!
Stream API
Stream APIとは
Java7までの配列やListなどの要素データの操作はforやwhileを用いて処理していました。
これから解説を行うStream APIという機能を使うことで、要素データの操作を簡潔かつ理解しやすい形で書くことができるようになりました。
簡潔かつ理解しやすい形で書くことができるため、可読性が向上します。
また簡潔かつ理解しやすいため、データ操作の条件も変更しやすくなります。
Stream APIの使い方
よく使うと思われるStream APIについて表にまとめました。
Stream API | 説明 | 記述例 |
---|---|---|
map | 四則演算などの一時的な処理 | list.stream().map(x -> x * 2) |
filter | 条件を満たす場合だけの処理 | list.stream().filter(x -> x >= 2) |
sorted | 順番に並べる処理 | list.stream().sorted() |
distinct | 重複する値を排除する処理 | list.stream().distinct() |
limit | 扱うデータ件数を制限する処理 | list.stream().limit(3) |
これらはStream APIのほんの一部です。
mapでの要素の値を書き換え
要素の値を一時的に変化させて変化後の値を出力するプログラムを見てみましょう。
Java8:
import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3); list.stream().map(x -> x * 2).forEach(System.out::println); } }
実行結果:
2 4 6
filterでの選別
filterを使用して、要素の値が2以上の合計値を求めるサンプルです。
Java8:
import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); int total = list.stream().filter(x -> x >= 2).mapToInt(x -> x).sum(); System.out.println("Total : " + total); } }
実行結果:
Total : 14
これと同じ処理をJava7で行うと、以下のようになります。
Java7:
import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); int total = 0; for (Integer x : list) { if (x >= 2) { total += x; } } System.out.println("Total : " + total); } }
プログラムが簡潔でわかりやすくなっているのがわかります。
sortedでの並び替え
要素の値を降順にソートするサンプルです。
sortedの引数を空にした場合は昇順にソートされます。
Java8:
import java.util.Arrays; import java.util.Comparator; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> list = Arrays.asList(5, 1, 4, 3, 2); list.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println); } }
実行結果:
5 4 3 2 1
distinctでの重複排除
要素の値に重複する値があれば除くサンプルです。
Java8:
import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> list = Arrays.asList(5, 1, 4, 5, 5); list.stream().distinct().forEach(System.out::println); } }
実行結果:
5 1 4
distinctを呼び出すことで重複するデータを除いて出力することができています。
limitでのデータ制限
limitを使うことで扱うデータ件数を制限することができます。
Java8:
import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); list.stream().limit(3).forEach(System.out::println); } }
実行結果:
1 2 3
上記の例では5つの要素から3つに絞って表示をしています。
なお、Stream APIの使い方については、こちらでも詳しく解説しているので参考にしてくださいね!
Optional
Optionalとは
値のない状態を「null」と言いますが、開発中はnullが原因で意図していないエラーが起こることが多いです。
そのため、エラーを未然に防ぐためにnullチェックの処理を書く必要がありました。
一般的にはif文を使って変数内の値がnullかどうかを確認します。
しかし、このOptionalを使うと、値がnullであるかどうかを簡潔に書くことができます。
if文のブロックを書かなくてもnullチェックが記述できるため、ifをネストさせることも極力避けて書くことができます。
またはっきりと「値が入っていない場合」という処理を示すことがでるため、ifによるnullチェック時のミスを減らすことも可能になり、プログラムの堅牢性も向上すると思われます。
Optionalの使い方
OptionalクラスのifPresentとorElseの使い方を、Java7までと比較して見てみましょう。
ifPresentの使い方
Java7:
public class Main { public static void main(String[] args) { String str = null; if (str != null) { System.out.println(str); } } }
Java8:
import java.util.Optional; public class Main { public static void main(String[] args) { String str = null; Optional<String> value = Optional.ofNullable(str); value.ifPresent(System.out::println); } }
上記の例では文字列クラスであるstrにnullを代入し、チェックに用いるための変数valueを定義しています。
ifPresentを使うことでnullでない場合のみ括弧内の処理を行うようになります。
上記を実行すると何も表示されませんが、str = nullの部分をstr = “abc”と変えることで実行するとabcと表示されるようになります。
orElseの使い方
Java7:
public class Main { public static void main(String[] args) { String str = null; if (str == null) { str = "abc"; } System.out.println(str); } }
Java8:
import java.util.Optional; public class Main { public static void main(String[] args) { String str = null; Optional<String> value = Optional.ofNullable(str); str = value.orElse("abc"); System.out.println(str); } }
実行結果:
abc
この例ではorElseを使うことで、String型の変数strがnullでだった場合に「abc」を返します。
この方法を使うとNullPointerアクセスを防ぐことができ、処理を続けることが可能です。
orElseの引数にラムダ式を使う場合
前項のorElseを使った例の応用として、何か処理をさせてから値を返したいという場合もあるかと思います。
その場合はorElseGetメソッドを使い、次のように記述します。
Java8:
import java.util.Optional; import java.util.Random; public class Main { public static void main(String[] args) { String str = null; Optional<String> value = Optional.ofNullable(str); str = value.orElseGet( () -> { Random rnd = new Random(); int ran = rnd.nextInt(10); return "" + ran; } ); System.out.println(str); } }
実行結果:
1
上記の例ではorElseGetを用いてラムダ式内で乱数を生成させ文字列として返しています。
これを実行するたびに乱数が生成されるので毎回違う値が表示されるようになります。
日付時刻API
Java8時代の日付の取扱について
元々Java7までの日付、時間の扱いはとても面倒くさいものでした。
DateやCalenderクラスを扱うのは面倒で機能も乏しいため、Javaプログラマからは敬遠される元となっていました。
これに対して、Java8では日時クラスの機能が強化されいろいろな処理ができるようになりました。
新しく追加されたクラスは以下のとおりです。
- Instant : 日時(エポック秒)
- LocalDateTime : タイムゾーンなし日時
- ZonedDateTime : タイムゾーンあり日時
これらについて1つずつ解説していきます。
まずは、Java7までの日時を扱うコードとJava8での記述とを比較して見てみましょう。
Java7:
import java.text.SimpleDateFormat; import java.time.ZonedDateTime; import java.time.ZoneId; import java.util.Calendar; import java.util.Date; public class Main { public static void main(String[] args) { // Java7までの記述 Date now = new Date(System.currentTimeMillis()); Calendar calendar = Calendar.getInstance(); calendar.setTime(now); calendar.add(Calendar.DAY_OF_MONTH, 15); String datetime_str = new SimpleDateFormat("yy/MM/dd hh:mm:ss").format(calendar.getTime()); System.out.println(datetime_str); // Java8での記述 ZonedDateTime before = ZonedDateTime.now(ZoneId.of("Asia/Tokyo")); ZonedDateTime after = before.plusDays(15); System.out.println(after.toString()); } }
実行結果:
17/09/21 07:37:15 2017-09-21T16:37:15.591+09:00[Asia/Tokyo]
上記は現在の日時を取得し、15日後の日時を表示するプログラムです。
Java7までの記述はなんだかとても複雑です。
Dateクラスが日時の値を保持しており日時の操作はCalendarが行っています。
日時の表示にはSimpleDateFormatクラスを使っています。
Java8の記述では、ZonedDateTimeクラスを使って現在の日時を取得し、15日後の日時を表示しています。
ZonedDateTimeクラスの使い方については後ほど解説します。
Java8では簡潔に記述できています。
Java8から導入された時間クラスでは日時の足し算引き算も行うことができ、Java7までに使われていたDateクラスと比較すると不便さが改善されたのではないでしょうか?
Java8で新しく追加されたクラスの使い方
エポック秒で表現された日時クラス
Instantクラスのnowメソッドを使って、現在の時刻をエポック秒で取得する方法についてみていきましょう。
Java8:
import java.time.Instant; public class Main { public static void main(String[] args) { Instant now = Instant.now(); System.out.println(now.toEpochMilli()); } }
実行結果:
1480230480923
このように書くことで現在の日時を取得することが可能です。
上記のコードでは現在時間を取得しエポックミリ秒で表示をしています。
Java7で言うところのDateクラスのgetTimeメソッドを呼び出しているのと同じような形になります。
なお、このInstantクラスはこの後解説するZonedDateTimeクラスとも関係があり、ZonedDateTimeへ変換することが可能です。
その際の記述は以下のようになります。
ZonedDateTime now = Instant.now().atZone(ZoneId.systemDefault());
タイムゾーンなしの日時クラス
LocalDateTimeではタイムゾーンなしの日時を返す形になります。
タイムゾーンを含む日時を扱いたい場合はこの後解説するZonedDateTimeクラスを用いる必要があります。
Java8:
import java.time.LocalDateTime; public class Main { public static void main(String[] args) { LocalDateTime now = LocalDateTime.now(); System.out.println(now.toString()); } }
実行結果:
2016-11-27T07:27:17.899
タイムゾーンありの日時クラス
ZonedDateTimeではタイムゾーン情報が付加されます。
これによって各地域の日時を取得することが可能です。
またZoneIdクラスを使ってタイムゾーンを指定することで、日本時間で扱うことができます。
Java8:
import java.time.ZonedDateTime; import java.time.ZoneId; public class Main { public static void main(String[] args) { ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Asia/Tokyo")); System.out.println(now.toString()); } }
実行結果:
2016-11-27T16:35:40.121+09:00[Asia/Tokyo]
interface
interfaceとは
interfaceとは具体的な処理内容は書かず、定数とメソッドの定義だけをしたクラスのようなものです。
interfaceは次のように記述します。
interface If { // 抽象メソッド public void method(); }
interfaceを実装したクラスで、メソッドをオーバーライドすることによって使われます。
interfaceを実装するには、「implements」を使用して次のように記述します。
class ImplClass implements If { public void method() { // 具体的な処理 } }
interface(インタフェース)ついては、こちらで詳しく解説しているので参考にしてください!
defaultメソッドとstaticメソッドの使い方
Java7まで、interfaceが実装を持つことはできませんでしたが、Java8からはデフォルト実装をもつdefaultメソッドやstaticメソッドを持つことができるようになりました。
interfaceにdefaultメソッドがあれば、interfaceを実装するクラスでオーバーライドする必要のないメソッドは書く必要がありません。
interfaceを実装したクラスで、defaultメソッドをオーバーライドすることもできます。
defaultメソッドは、メソッドの宣言の最初に「default」を使用します。
defaultメソッドとstaticメソッドの使い方を次のサンプルで確認してみましょう。
Java8:
// インタフェース interface If { // defaultメソッド default void method1() { System.out.println("default method1."); } // defaultメソッド default void method2() { System.out.println("default method2."); } // staticメソッド static void method3() { System.out.println("static method3."); } } // 実装クラス class ImplClass implements If { public void method2() { System.out.println("override method2."); } } public class Main { public static void main(String[] args) { // defaultメソッドの呼び出し ImplClass c = new ImplClass(); c.method1(); c.method2(); // staticメソッドの呼び出し If.method3(); } }
実行結果:
default method1. override method2. static method3.
インタフェースIfのmethod1とmethod2はメソッド宣言に「default」が使用されているのでdefaultメソッド、method3はstaticメソッドになります。
実装クラスImplClassでは、method2をオーバーライドしています。
そのため、ImplClassのインスタンスを作成し、method1を呼び出すとdefaultメソッドで実装した処理、method2を呼び出すとオーバーライドした処理が実行されます。
method3はstaticメソッドなので、「If.method3();」と呼び出すことができます。
アノテーション
アノテーションとは
アノテーションとはクラスやメソッドについての様々な情報を記入できる機能です。
@(アットマーク)から始まる記述法で、通常のコメント文とは違い、コンパイル時にアノテーションの規則に従っているかチェックされるため、スペルミスやバグの発生を防止することができます。
アノテーションついては、こちらで詳しく解説しているので参考にしてください!
タイプアノテーションの使い方
Java7まではクラスやメソッドの宣言にしかアノテーションを使えませんでしたが、Java8からは変数の型やジェネリクスの型パラメータに対しても使うことができるようになりました。
この機能をタイプアノテーションといいます。
これにより、型のチェックなどをコンパイル時に行い、想定外のエラーを避けることができます。
追加されたElementType(アノテーションを付加できる箇所を指定する定数)は以下の2つです。
- TYPE_PARAMETER:型パラメータ
- TYPE_USE:すべての型使用箇所
TYPE_USEを使ったサンプルです。
TypeUseAnnotation.java:
import java.lang.annotation.ElementType; import java.lang.annotation.Target; @Target(ElementType.TYPE_USE) public @interface TypeUseAnnotation {}
TypeUseSample.java:
import java.util.ArrayList; import java.util.List; @TypeUseAnnotation public class TypeUseSample { // メソッドの引数の型に利用 public void method1(@TypeUseAnnotation String str) { // ローカル変数の型に利用 @TypeUseAnnotation int i = 0; // ジェネリクスの型パラメータに利用 List<@TypeUseAnnotation String> list = new ArrayList<>(); } // メソッドの戻り値の型に利用 public @TypeUseAnnotation int method2() { return 0; } }
メソッドの引数や戻り値、ローカル変数など、すべての型を使用している箇所で利用することができます。
まとめ
今回はJava8の新機能について解説してきましたが、いかがでしたか?
大きなアップデートであるため細かい部分まで解説しきれなかったのは残念ですが、これらの機能を使いより楽をして品質の高いコードをかけるといいですね!