0
0

ラムダ式の練習でstreamAPIをちょっと再現する

Last updated at Posted at 2024-09-07

ラムダ式・streamAPIとの出会い

最近までは,大量のデータを成形するときは迷わずfor文,while文(もしくはIterator)で処理していました.
実際のコードではないですが,代わりに犬の名前のリストから10歳以上の犬の種類のリストを取得する処理を書いておきます.

List<String> dogTypes = new ArrayList<>();
//dogNames:犬の名前が入った List<String>
for(String dogName : dogNames){
    Dog dog = //DBからdogエンティティの取得;
    if(dog.getAge() >= 10){
        dogTypes.add(dog.getDogType());
    }
}

SQLで対処できそうなのは置いといて,
こんなコードを書いて満足していましたが,友人からもっと格好良い処理があると言われ彼のコードを見ると...

List<Dog> dogs = //DBから全てのdogエンティティの取得;
//dogNames:犬の名前が入った List<String>
List<String> dogTypes = dogs.stream()
                            .filter(dog -> dogNames.contains(dog.getName()) && dog.getAge() >= 10)
                            .toList();

forやifのネストが無く非常にスタイリッシュなコードになっていました.


この出会いからラムダ式とstreamAPIの勉強を並行して進めてきました.
先程のコードで言うとラムダ式とstreamAPIは以下の内容を表しています.

  • ラムダ式 : .filter()の引数の記述方法
  • streamAPI : .stream()から.toList()で行われている,リストを成形するための処理を提供

streamの使い方はわかりましたが内部で使用されているラムダ式そのものに関しての理解が浅かったため,streamを再現して感覚を掴もうと思い記事にまとめました.

streamAPIの基本機能を作る

大まかな流れとしては ①関数型インターフェースの実装 ②streamの処理を持つクラスの実装 ③使ってみる となります.

①関数型インターフェースの実装

Javaで用意されたインターフェースを参考に,新たに4つのインターフェースを作成しました.

Consumerインターフェースっぽいもの
//値を渡して処理し,処理結果はいらない時に使用
public interface ConsumerTest<T> {
    //引数〇,戻り値×
	public void accept(T t);
}
Predicateインターフェースっぽいもの
//TrueFalseの判定が欲しい時に使用
public interface PredicateTest<T> {
    //引数〇,戻り値boolean
	public boolean test(T t);
}
Functionインターフェースっぽいもの
//値を渡して処理し,処理結果も欲しい時に使用
public interface FunctionTest<T, R> {
    //引数〇,戻り値〇
	public R apply(T t);
}
Comparatorインターフェースっぽいもの
//2つの値を比較したい時に使用
public interface ComparatorTest<T> {
    //引数〇,戻り値boolean
	public boolean compare(T t1, T t2);
}

②streamの処理を持つクラスの実装

今回はstreamの中でfilter, sorted, map, toList,ついでにforEachも作成しました.
streamメソッドはほぼ見た目用になってます
実装方法として,自作のstream用クラスを元々のListクラスに継承させるのもワンチャンあると思いましたが,一旦今回はListとstreamの処理の両方を持つクラスを作成しました.
個人的にmapの部分で初めて引数だけでなく戻り値にジェネリクスを使ったので新鮮な気持ちでした.ジェネリクスありがたいですね.

Listとstreamの処理を持つクラス
import java.util.ArrayList;

public class Array<T> {

	public ArrayList<T> list = new ArrayList<>();
	
	//forEach
	public void forEach(ConsumerTest<T> c) {
		for(T listVal : list) {
			c.accept(listVal);
		}
	}
	
	//stream
	public Array<T> stream(){
		Array<T> newArray = new Array<>();
		for(T listVal : list) {
			newArray.list.add(listVal);
		}
		return newArray;
	}
	
	//toList
	public ArrayList<T> toList(){
		return this.list;
	}
	
	//filter
	public Array<T> filter(PredicateTest<T> p){
		Array<T> newArray = new Array<>();
		for(T listVal : list) {
			if(p.evaluate(listVal)) {
				newArray.list.add(listVal);
			}
		}
		return newArray;
	}
	
	//sorted
	public Array<T> sorted(ComparatorTest<T> c){
		for(int i=0; i < list.size() - 1; i++ ) {
			for(int j=0; j < list.size() - 1 - i; j++ ) {
				if(c.compare(list.get(j), list.get(j+1))) {
					T tmp = list.get(j);
					list.set(j, list.get(j+1));
					list.set(j + 1, tmp);
				};
			}
		}
		return this;
	}
	
	//map
	public <R> Array<R> map(FunctionTest<T, R> e){
		Array<R> newArray = new Array<>();
		for(T listVal : list) {
			newArray.list.add(e.apply(listVal));
		}
		return newArray;
	}

}

③使ってみる

forEachとstreamnの処理の2つを試してみました.


public static void main(String[] args) {
    Array<String> strList = new Array<>();
    
    strList.list.add("asffvdss");
    strList.list.add("bbsfaavavb");
    strList.list.add("cfc");
    strList.list.add("ddasd");
    strList.list.add("vavvdaadadavdb");
    strList.list.add("b");
    strList.list.add("dcsfasfasdd");
    
    //forEach
    System.out.println("-----------forEach-----------");
    strList.forEach((str)->{
        System.out.println(str);
    });

    //stream
    System.out.println("-----------stream-----------");
    System.out.println(
        strList.stream()
                .map(str -> str.length())
                .filter(num -> num > 3)
                .sorted((first, second) -> first > second)
                .map(num -> {
                    String res = "";
                    int i = 0;
                    while(i<num) {
                        res += i;						
                        i++;
                    }
                    return res;
                })
                .toList()
    );
}

以下の結果が出力されました.上手く動いてそうですね.

-----------forEach-----------
asffvdss
bbsfaavavb
cfc
ddasd
vavvdaadadavdb
b
dcsfasfasdd
-----------stream-----------
[01234, 01234567, 0123456789, 012345678910, 012345678910111213]

実際に書いたことで,(関数型)インターフェースの実装クラスをラムダ式に置き換えているだけで,本質的にはただ作成したオブジェクトを引数に渡しているという感覚が掴めてきました.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0