Java8の新機能を徹底解説!(ラムダ式、Streamなど)

こんにちは!フリーランスのオータケです。

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チェック時のミスを減らすことができて、プログラムの堅牢性が向上
日付時刻APIDate、Calendarクラスの代替として、次のクラスなどを導入
Instant : 日時(エポック秒)
LocalDateTime : タイムゾーンなし日時
ZonedDateTime : タイムゾーンあり日時
Date、Calenderクラスを扱うのは面倒で機能も乏しいため、Java8から日時に関するAPIが追加
interfaceinterfaceのdefaultメソッド、staticメソッドを導入実装する必要のないメソッドを実装クラスで書く必要がなくなる
interfaceにメソッドを追加した時に、既存の実装クラスに影響がない
アノテーション変数の型やジェネリクスの型パラメータにアノテーションがつけられる機能を追加型のチェックなどをコンパイル時に行い、想定外のエラーを避けることができる

それぞれの内容については、後ほど詳しく解説します。

Java8の環境構築

まずはJava8の開発環境を構築しましょう。

これから使うパソコンにこれまでJavaをインストールしたことがなければ、ダウンロードから始めてください。

以前にインストールしたことがある、もしくはインストールしたかどうかわからない場合はアップデートについての説明から進めてください。

ダウンロード

Java8をダウンロードしてインストールする方法については、こちらで詳しく解説していますので参考にしてください。

アップデート

Java8にアップデートするためには現在パソコンにインストールされているJavaのバージョンがJava8かJava7以前か確認する必要があります。

ここでは、使うパソコンのOSはWindows10の場合を代表例として説明します。

Javaのバージョンを確認する方法は以下のとおりです。

Windows 10-プログラムの確認
1.「スタート」をクリックします。
2.「設定」を選択します
3.「システム」を選択します
4.「アプリと機能」を選択します
5.プログラムの一覧からJavaを探し出してバージョンを確認します

Java8check

この画像では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などの要素データの操作はforwhileを用いて処理していました。

これから解説を行う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クラスのifPresentorElseの使い方を、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までの日付、時間の扱いはとても面倒くさいものでした。

DateCalenderクラスを扱うのは面倒で機能も乏しいため、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の新機能について解説してきましたが、いかがでしたか?

大きなアップデートであるため細かい部分まで解説しきれなかったのは残念ですが、これらの機能を使いより楽をして品質の高いコードをかけるといいですね!

この記事を書いた人

30歳、フリーランスプログラマ。中学の頃よりプログラミングに興味を持ちゲーム開発やWebサイト構築などを経験
新しいフレームワークやライブラリに興味があり革新的な機能が含まれていると泣いて喜ぶ。

目次