2017年5月14日日曜日

Java 8 ラムダ式の初歩(続き2)

 Java8のラムダ式と「Javaオブジェクトの集合(Collections)とそのソート」には、重要な関係があります。非常に簡単な例は下記のものです。 
 
=====================================
// 学部名のリストを作り、その文字数で昇順に並べる。 
import java.util.*;public class ListSort_Lambda{
  public static void main(String... args){

List<String> list = Arrays.asList("E:工学部", "C:創造工学部",
"I:情報学部","A:応用バイオ科学部","N:看護学部" );

// sortの第⒉引数に(Lambda Expressions)を与える
Collections.sort(list, (s1, s2)-> s1.length() - s2.length());

System.out.println("学部名を文字数の昇順に列挙します:");
 list.forEach(s -> System.out.println(s)); //内部反復にラムダ式
  }
}
=====================================

 上記のリストについての、詳細な説明は以下にありますので、ご覧いただければ幸いです。
http://kait-kbook.sakura.ne.jp/it/+6360-4/^06-Sort-of-List-Map(A)@(2016.5.23%2013.00-).pdf

2017年5月13日土曜日

Java 8 ラムダ式の初歩(続き1)

 前回の続編として、ラムダ式(Lambda Expressions)の続きを検討してみます。
 下図は、自分が最も関心のあるキーワード(一つだけ)を含むタイトルの書籍を何冊持っているかを示しています。そのキーワード毎に、学生が持っている書籍数を合計するカウントプログラムを考えます。

最も関心の高いキーワードを含む書籍を何冊持っているか?

 やりたいことは自明であり、とにかくプログラムを作ることはできます。しかし、ここでは、書籍のカウントの仕方を問題にします。よくあるプログラムでは、カウントするためのメソッド(関数)の内容は、「指定されたものをカウント(合計)する」のと「カウントすべきものは何か、それをどのような手順で決めるか」が混っていることがあります。

 ここでは、後者の「カウントされるものが何か(どの本か)」を、前者の「カウントすること」から分離させることを考えます。それによって、書籍の種類を追加したり、合計したい本に何らかの条件を付けたりしても、前者の「「指定されたものをカウント(合計)する」関数は変更を受けないはずです。これは古くからあるデザインパタンの一つ(関心の分離:seperation of concernsですが、ラムダ式を用いることによって、いっそう柔軟な実装ができます。

 以下のプログラムは、Venkat Subramaniam氏の書籍[1]を参考に作りました。このなかで、【方式1】は、氏の方式に沿ったものです。一方、【方式2】は、当方で独自に修正してみたものです。【方式1】では、カウント関数 countAを呼び出す際に、「何をカウントすべきか」を決める手続きをラムダ式で(t -> t.type == Type.Keras のように)渡している所に特徴があります。

 一方、【方式2】は、関数呼び出しの度に同じような形のラムダ式を与えたくないので、ラムダ式自体を分離してみました。しかし、それは、カウントする関数 countBの中に含まれているので、「関心の分離」からは好ましくないようです。結論として、【方式1】に従うのがよさそうです。ただし、【方式2】もラムダ式の活用練習にはなるでしょう。

========================================
package designing.fpij;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import designing.fpij.Student.Type;
public class StudentManager2 {
  
  // 【方式1】Venkat Subramaniamの書籍の方式(但し、以下のコードは山本自作)
  public static int countA(List<Student> sts, Predicate<Student> selector){
  return sts.stream().filter(selector).mapToInt(s -> s.number).sum();
  }
  // 【方式2】Venkat Subramaniamの方式の変更版(以下のコードは山本自作)
  public static int countB(List<Student> sts, Type type){
    Predicate<Student> selector = s -> type == null ? true: s.type == type;
    return sts.stream().filter(selector).mapToInt(s -> s.number).sum();
  }

  public static void main(final String[] args) {
    List<Student> students = Arrays.asList(
    new Student(Type.Java8, 3), new Student(Type.Python, 1),
    new Student(Type.NetLogo, 1), new Student(Type.Python, 2)
    );
    
    // 【方式1】を使った実行
    System.out.println("total:" + countA(students, t->true));
    System.out.println("Keras:" + countA(students, t->t.type==Type.Keras));
    System.out.println("Python:" + countA(students, t->t.type==Type.Python));    

    // 【方式2】を使った実行
    System.out.println("total:" + countB(students, null));
    System.out.println("Keras:" + countB(students, Type.Keras));
    System.out.println("Python:" + countB(students, Type.Python));
  }
}

class Student {
    public enum Type {Java8, Python, NetLogo, Keras }; 
    protected Type type;
    protected int number;
    public Student(Type type, int number) {
        this.type = type;
        this.number = number;
    }
}
========================================
実行結果:【方式1】,【方式2】とも同一
total:7
Keras:0
Python:3
========================================

[参考文献]
[1] Venkat Subramaniam : Functional Programming in Java, O'Reilly, 2014.
(O'Reilly Japanから、和訳本も出版されている)

2017年5月10日水曜日

Java 8 ラムダ式の初歩

 3年生の実験1のひとつのテーマとして、「実践Java」をやっています。ここでは、2年生授業「Java言語」の復習を行ったあと、2年生ではやっていなかった「ラムダ式とストリーム」(Lambda Expressions and Streams)に取り組みます。先週から、このラムダ式に取りかかりました。今後は、これらの概念と利用の普及がより進むものと考えられます。

 そこで、ここでは、ご存じない方のために、簡単な例で(どんな雰囲気かを)説明してみます。以下のような簡単な問題を考えます。ボタンが3つ(黄、青、赤)あります。いずれかのボタンを押すと、背景がボタンに応じた色に変わるというものです。

例:押したボタンに応じて背景色を変える


 これを、2年生Javaまでの知識でプログラムを書くと、以下のようになります。ボタンを押したというイベントの処理プログラムになっています。ボタンイベントを処理するための内部クラスInListenerを作っているところポイントのひとつです。また、この内部クラスのオブジェクトisを作って、それを各ボタンのイベントリスナ(それが発生したイベントを処理してくれるオブジェクト)として登録しています。

// プログラム1:通常のJava SE7のプログラム----------------------
class BackColor_Basic extends JPanel{
JButton yB, bB, rB;
BackColor_Basic(){
yB = new JButton("黄");
bB = new JButton("青");
rB = new JButton("赤");
add(yB);
add(bB);
add(rB);
InListener is = new InListener();
yB.addActionListener(is);
bB.addActionListener(is);
rB.addActionListener(is);
}
class InListener implements ActionListener{
public void actionPerformed(ActionEvent e){
Color c;
if(e.getSource() == yB){
c = Color.yellow;
}else if (e.getSource() == bB){
c = Color.blue;
}else if (e.getSource() == rB){
c = Color.red;
}else{
c = Color.black;
}
setBackground(c);
}
}
public static void main(String args[]) {
//固定的なので省略
}
}
// ここまで ------------------------------------------------


 上記プログラム1は、何ら問題なく、意図通りの動作をします。これと同じ内容をJava SE8のラムダ式を使って書くと、以下のようになります。(プログラム2はその一例ですが)プログラム1と比べて、短くなっているだけではありません。ラムダ式が2重に使われているところに注目して下さい。

setBackC = (b, c) -> { ... ( e -> ... ) };

 特に、「オブジェクトがくるべきところが、関数らしきものに置き換わっている」点が特徴的です。関数型っていうのはこのことを指すようです。Javaのラムダ式は、「突然、予期せぬものが出て来て、不意を突かれる」ような感覚もありますが、少し馴れると、その簡潔さと美しさにとらわれます!


// プログラム2:Java SE8のラムダ式を使ったプログラム-------------
class BackColor_Lambda2 extends JPanel{
BackColor_Lambda2(){
BiConsumer<JButton,Color> setBackC = (b, c) ->
{ add(b);
b.addActionListener(e -> setBackground(c));
};
setBackC.accept(new JButton("黄"), Color.yellow );
setBackC.accept(new JButton("青"), Color.blue );
setBackC.accept(new JButton("赤"), Color.red );
}
public static void main(String args[]) {
//固定的なので省略
}
}
// ここまで -------------------------------------------------


 今後、また、「ラムダ式とストリーム」について書いて行きたいと思います。時期は未定ですが。