0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Java コンビネーション

Posted at

概要

Java のコンビネーションパターンを試してみました

結果は、関数型(コンビネーションパターン)の方がコードは可読性が増すけど、メモリを多く消費するようです。速度はほとんど変わりがないようでした。

参考

やり方

手続き型

    public ValidationResult imperativeValidate(Parson parson) {
        var result = ImperativeValidator.isValidName(parson.getName());
        if (result != ValidationResult.SUCCESS) {
            return result;
        }
        result = ImperativeValidator.isValidAddr(parson.getAddr());
        if (result != ValidationResult.SUCCESS) {
            return result;
        }
        return ImperativeValidator.isValidAge(parson.getAge());
    }

関数型

    public ValidationResult functionalValidate(Parson p) {
        return CombinatorPatternValidator.isValidName()
                .and(isValidAddr())
                .and(isValidAge())
                .apply(p);
    }

結果

JVM ヒープ

関数型が 124 KB に対して、 手続き型が 66 KB でした。

[QUICK PERF] Measured heap allocation (test method thread): 124.14 Kilo bytes (127 120 bytes)
[QUICK PERF] Measured heap allocation (test method thread): 66.49 Kilo bytes (68 088 bytes)

ベンチマーク

何回かやりましたが、あんまり変わらない、関数型の方がちょっと遅いという感じの結果になっています
(ここはバリデーションの内容によるのかもしれないです)

Benchmark                                   Mode  Cnt       Score      Error  Units
CombinatorPatternBenchmarkTest.functional  thrpt    5  859447.226 ± 5632.616  ops/s
CombinatorPatternBenchmarkTest.imperative  thrpt    5  865311.952 ± 4481.358  ops/s

付録

コード全体

Data

@AllArgsConstructor
@Data
public class Parson {
    private String name;
    private int age;
    private String addr;
    private String addr2;
}

enum

public enum ValidationResult {
    SUCCESS,
    HOGE_CANNOT_BE_USED_IN_NAME,
    ADDR_IS_TOO_LONG,
    IS_A_CHILD,
}

手続き型バリデーター

public class ImperativeValidator {
    public static ValidationResult isValidName(String name) {
        return name.contains("hoge") ? HOGE_CANNOT_BE_USED_IN_NAME : SUCCESS;
    }

    public static ValidationResult isValidAddr(String addr) {
        return addr.length() > 100 ? ADDR_IS_TOO_LONG : SUCCESS;
    }

    public static ValidationResult isValidAge(int age) {
        return age <= 18 ? IS_A_CHILD : SUCCESS;
    }
}

関数型(コンビネーターパターン)バリデーター

interface CombinatorPatternValidator extends Function<Parson, ValidationResult> {

    static CombinatorPatternValidator isValidName() {
        return p -> p.getName().contains("hoge") ? HOGE_CANNOT_BE_USED_IN_NAME : SUCCESS;
    }

    static CombinatorPatternValidator isValidAddr() {
        return p -> p.getAddr().length() > 100 ? ADDR_IS_TOO_LONG : SUCCESS;
    }

    static CombinatorPatternValidator isValidAge() {
        return p -> p.getAge() <= 18 ? IS_A_CHILD : SUCCESS;
    }

    default CombinatorPatternValidator and(CombinatorPatternValidator other) {
        return p -> {
            var result = this.apply(p);
            return result.equals(SUCCESS) ? other.apply(p) : result;
        };
    }
}

バリデーションサービス

public class ValidationService {

    public ValidationResult imperativeValidate(Parson parson) {
        var result = ImperativeValidator.isValidName(parson.getName());
        if (result != ValidationResult.SUCCESS) {
            return result;
        }
        result = ImperativeValidator.isValidAddr(parson.getAddr());
        if (result != ValidationResult.SUCCESS) {
            return result;
        }
        return ImperativeValidator.isValidAge(parson.getAge());
    }

    public ValidationResult functionalValidate(Parson p) {
        return CombinatorPatternValidator.isValidName()
                .and(isValidAddr())
                .and(isValidAge())
                .apply(p);
    }
}

テストコード

ヒープ測定

Javaでヒープサイズ測定 JUnit5編 を参考

@QuickPerfTest
public class CombinatorPatternTest {

    ValidationService service = new ValidationService();

    @MeasureHeapAllocation
    @Test
    void imperative() {
        service.imperativeValidate(create());
    }

    @MeasureHeapAllocation
    @Test
    void functional() {
        service.functionalValidate(create());
    }

    Parson create() {
        return new Parson(RandomStringUtils.randomAlphabetic(10), new Random().nextInt(100), RandomStringUtils.randomAlphabetic(20), RandomStringUtils.randomAlphabetic(20));
    }
}

ベンチマーク

Javaでベンチマーク(性能測定) JUnit5編 を参考

@State(value = Scope.Benchmark)
public class CombinatorPatternBenchmarkTest {

    ValidationService service = new ValidationService();

    @Benchmark
    public void imperative() {
        service.imperativeValidate(create());
    }

    @Benchmark
    public void functional() {
        service.functionalValidate(create());
    }
    
    @Test
    void benchMark() throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(CombinatorPatternBenchmarkTest.class.getSimpleName())
                .forks(1) // 1回実行
                .warmupIterations(1) // 1回繰り返し
                .build();
        new Runner(opt).run();
    }

    Parson create() {
        return new Parson(RandomStringUtils.randomAlphabetic(10), new Random().nextInt(100), RandomStringUtils.randomAlphabetic(20), RandomStringUtils.randomAlphabetic(20));
    }
}
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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?