最終更新日:2021/01/30 原本2017-

ラムダ式

関数型インターフェース

インターフェースの中で,抽象メソッドをただ一つだけ持つものを関数型インターフェースと呼びます.
下記のLambdaTest.javaをコピーし,動作を確認してみましょう.

LambdaTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package jp.ac.utsunomiya_u.is;
 
public class LambdaTest {
 
    @FunctionalInterface
    private interface Adder {
 
        public int add(int x, int y);
    }
 
    public static void main(String[] args) {
        Adder adder = new Adder() {
            @Override
            public int add(int x, int y) {
                return x + y;
            }
        };
        System.out.println(adder.add(2, 3));
    }
}
5行目
関数型インタフェースを定義する際は@FunctionalInterfaceアノテーションをつけるようにしましよう.
6行目
この例では内部インタフェースとしてAdderインターフェースを定義しています.
8行目
Adderインタフェースはaddメソッドのみを抽象メソッドとして持ちます.
12-17行目
匿名クラスとしてAdderインタフェースを実装しています.
13-16行目
インタフェースは全てのメソッド(ここではaddメソッドのみ)をオーバーライドしなければなりません.
オーバーライドする際は@Overrideアノテーションをつけるようにしましよう.
LambdaTest.javaを下記のように修正して動作を確認してみましょう.

LambdaTest.javaの修正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package jp.ac.utsunomiya_u.is;
 
public class LambdaTest {
 
    @FunctionalInterface
    private interface Adder {
 
        public int add(int x, int y);
    }
 
    public static void main(String[] args) {
        Adder adder = (int x, int y) -> {
            return x + y;
        };
        System.out.println(adder.add(2, 3));
    }
}
12-14行目
ラムダ式を用いることで,6行のコードが3行で簡潔に書くことが出来ます.
ラムダ式の書式は以下のようです.

ラムダ式の書式

1
(実装するメソッドの引数)->{処理}
ラムダ式では以下のような省略ルールが使えます.
LambdaTest.javaを下記のように修正して動作を確認してみましょう.

LambdaTest.javaの修正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package jp.ac.utsunomiya_u.is;
 
public class LambdaTest {
 
    @FunctionalInterface
    private interface Adder {
 
        public int add(int x, int y);
    }
 
    public static void main(String[] args) {
        Adder adder = (x, y) -> x + y;
        System.out.println(adder.add(2, 3));
    }
 
}
12行目
ラムダ式を用いることで,6行のコードが1行で簡潔に書くことが出来ます.
LambdaTest.javaを下記のように修正して動作を確認してみましょう.

LambdaTest.javaの修正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package jp.ac.utsunomiya_u.is;
 
public class LambdaTest {
 
    @FunctionalInterface
    private interface Adder {
 
        public int add(int x, int y);
         
        public int mul(int x,int y);
    }
 
    public static void main(String[] args) {
        Adder adder = (x, y) -> x + y;
        System.out.println(adder.add(2, 3));
    }
 
}
10行目
抽象メソッドとしてmulを追加すると,2つ以上の抽象メソッドとなるため関数型インターフェースの定義から外れてしまいます. このように関数型インターフェースに間違って抽象メソッドを追加してしまった時に,コンパイルエラーとなるように@FunctionalInterfaceアノテーションをつけるようにしましょう.
LambdaTest.javaを下記のように修正して動作を確認してみましょう.

LambdaTest.javaの修正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package jp.ac.utsunomiya_u.is;
 
public class LambdaTest {
 
    @FunctionalInterface
    private interface Adder {
 
        public int add(int x, int y);
 
        public default int mul(int x, int y) {
            return x * y;
        }
    }
 
    public static void main(String[] args) {
        Adder adder = (x, y) -> x + y;
        System.out.println(adder.add(2, 3));
    }
 
}
10行目
インタフェースでは,default修飾子やstatic修飾子を付けたメソッドが定義出来ます. これらのメソッドは抽象メソッドとして扱われないので,関数型インターフェースとして成立します.

ジェネリクス

JavaでもC++と同様にジェネリックプログラミングが可能です. ジェネリックプログラミングは,データ型に関わらず同じコードが利用できる仕組みです. C++ではtemplateとして知られています.

コレクション

これまで複数の要素をまとめて扱う場合には配列を利用してきました. しかし,配列は要素数を後で変更できないなどの使い勝手が悪い面もあります. C++と同様にJavaでもコレクションが利用できます. コレクションは配列と同じように複数の要素の集まりを扱う場合に適しています. 代表的なコレクションは以下のようなものがあります.
クラス説明主な実装されたインターフェース
ArrayList<E>List<E>インタフェースのサイズ変更可能な配列の実装List<E>
LinkedList<E>List<E>およびDeque<E>インタフェースの二重リンク・リスト実装List<E>, Deque<E>
HashSet<E>ハッシュ表に連動したSet<E>インタフェースを実装Set<E>
HashMap<K,V>Map<K,V>インタフェースのハッシュ表に基づく実装Map<K,V>
ここで,<E>のEはジェネリックと呼ばれ,型をパラメタとしたコレクションが利用できます.
下記のCollectionTest.javaをコピーし,動作を確認してみましょう.

CollectionTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package jp.ac.utsunomiya_u.is;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
 
public class CollectionTest {
 
    public static void main(String[] args) {
        {
            System.out.println("##### 要素の追加 #####");
            // ArrayListクラスのインスタンスをString型で生成
            ArrayList<String> fruit = new ArrayList<String>(); // ArrayList<String> fruit = new ArrayList<>(); のようにダイヤモンド演算子を使用した方が良い
            // コレクションに順次,要素を追加
            fruit.add("Apple");
            fruit.add("Orange");
            fruit.add("Banana");
            fruit.add("Grape");
            // 2番めの要素を表示
            System.out.println("fruit[2] = " + fruit.get(2));
            // 拡張for文でコレクションの要素に順番にアクセスし,表示
            for (String s : fruit) { // fruit.forEach(s -> { のようにラムダ式を使用して簡潔に書くことも可能
                System.out.print(s + "\t");
            }
            System.out.println();
 
            System.out.println("##### 要素数と削除 #####");
            // sizeメソッドはコレクションの要素数を返す(配列のlengthのようなもの)
            System.out.println("fruit.size = " + fruit.size());
            // "Orange"を削除
            fruit.remove("Orange");
            // 要素数が1つ減っている
            System.out.println("fruit.size = " + fruit.size());
            // "Orangeが要素にない
            fruit.forEach(s -> {
                System.out.print(s + "\t");
            });
            System.out.println("");
            // fruit[1]を削除
            fruit.remove(1);
            // 要素数が1つ減っている
            System.out.println("fruit.size = " + fruit.size());
            // fruit[1]="Banana"なので"Banana"が削除されている
            fruit.forEach(s -> {
                System.out.print(s + "\t");
            });
            System.out.println();
            // 要素全てを削除
            fruit.clear();
            System.out.println("fruit.size = " + fruit.size());
            fruit.forEach(s -> {
                System.out.print(s + "\t");
            });
            System.out.println();
 
            System.out.println("##### 要素の検索と変更 #####");
            // 要素を再度追加
            fruit.add("Apple");
            fruit.add("Orange");
            fruit.add("Banana");
            fruit.add("Grape");
            // "Orange"に該当する要素番号を返す
            int i = fruit.indexOf("Orange");
            System.out.println("Orange's index is " + i);
            // fruit[i]を"Mango"に変更
            fruit.set(i, "Mango");
            fruit.forEach(s -> {
                System.out.print(s + "\t");
            });
            System.out.println();
        }
        {
            System.out.println("##### 要素の順番反転と整列 #####");
            // ArrayListクラスのインスタンスをInteger型で生成 (asListを用いて初期化可能)
            ArrayList<Integer> number = new ArrayList<>(Arrays.asList(3, 7, 4, 8, 2));
            number.forEach(i -> {
                System.out.print(i + "\t");
            });
            System.out.println();
            // Collectionsクラスのreverse()メソッドで順番反転
            Collections.reverse(number);
            number.forEach(i -> {
                System.out.print(i + "\t");
            });
            System.out.println();
            // Collectionsクラスのsort()メソッドで整列
            Collections.sort(number);
            number.forEach(i -> {
                System.out.print(i + "\t");
            });
            System.out.println();
        }
        {
            System.out.println("##### LinkedListの使い方 #####");
            // LinkedListクラスのインスタンスをString型で生成
            LinkedList<String> fruit = new LinkedList<>();
            // add()メソッドで要素追加
            fruit.add("Orange");
            fruit.add("Banana");
            fruit.add("Grape");
            // 要素の最初に"Apple"を追加
            fruit.addFirst("Apple");
            fruit.forEach(s -> {
                System.out.print(s + "\t");
            });
            System.out.println();
        }
        {
            System.out.println("##### HashSetの使い方 #####");
            // HashSetクラスのインスタンスをString型で生成し,初期化
            HashSet<String> fruit = new HashSet<>(Arrays.asList("Apple", "Orange", "Banana", "Grape"));
            fruit.forEach(s -> {
                System.out.print(s + "\t");
            });
            System.out.println();
            // HashSetに"Orange”を追加          
            fruit.add("Orange");
            //  HashSetは同じ要素は保持できない
            fruit.forEach(s -> {
                System.out.print(s + "\t");
            });
            System.out.println();
        }
        {
            System.out.println("##### HashMapの使い方 #####");
            // HashMapクラスのインスタンスをKeyをString型でValueをInteger型で生成
            HashMap<String, Integer> fruit = new HashMap<>();
            // KeyとValueのセットで追加
            fruit.put("Apple", 200);
            fruit.put("Orange", 100);
            fruit.put("Banana", 50);
            fruit.put("Grape", 150);
            // Keyが"Orange"のもののValueをget()メソッドで取り出す
            System.out.println("Orange is " + fruit.get("Orange") + " yen.");
            // 拡張for文でMapの全ての要素にアクセスし表示
            for (Map.Entry<String, Integer> m : fruit.entrySet()) { //  fruit.entrySet().forEach((m) -> { のようにラムダ式を使用して簡潔に書くことも可能
                System.out.print(m.getKey() + " is " + m.getValue() + " yen.\t");
            }
            System.out.println();
            // ”Grape”のValueを150から200に変更
            fruit.put("Grape", 200);
            fruit.entrySet().forEach(m -> {
                System.out.print(m.getKey() + " is " + m.getValue() + " yen.\t");
            });
            System.out.println();
            // Keyが”Banana"のものを削除
            fruit.remove("Banana");
            fruit.entrySet().forEach(m -> {
                System.out.print(m.getKey() + " is " + m.getValue() + " yen.\t");
            });
            System.out.println();
        }
    }
}