MyEnigma

とある自律移動システムエンジニアのブログです。#Robotics #Programing #C++ #Python #MATLAB #Vim #Mathematics #Book #Movie #Traveling #Mac #iPhone

Google製のJava便利ライブラリGuava入門

目次

はじめに

PythonやJuliaを使っている人が、

Javaを使うと、

言語のデフォルト機能が足りないなと

思うことがあると思います。

 

そんな時に便利なのが、

Googleが開発公開しているJavaライブラリであるGuavaです。

github.com

qiita.com

 

Guavaは、Javaのデフォルト機能で実装すると、

コードの量が長くなってしまったり、

バグが入りやすくなってしまうコードを、

簡潔に、バグが入りにくく実装することを

目的に開発されています。

 

Guavaはかなり古くから開発されているため、

Java8などで導入された機能とかぶっている部分も多いですが、

それでもまだデフォルトの機能にはない沢山の機能が実装されています。

 

今回の記事では、

Guavaの代表的な機能の概要と

コードサンプルを紹介したいと思います。

 

すべてのコードサンプルはこちらでも公開しています。

github.com

com.google.common.base.Optional

OptionalはJava 8から導入されたOptionalと同じく、

nullになる可能性があるオブジェクトを取り扱うことができます。

guava.dev

Java8以前を使っている場合は、非常に便利だと思います。

com.google.common.base.Preconditions

Preconditionは、関数の引数の条件などをチェックできるユーティリティ群です。

このPreconditionを使うことで、エラーメッセージがわかりやすく表示されます。

guava.dev

import com.google.common.base.Preconditions;

public class PreconditionSample {
    public static void main(String[] args) {

        try {
            System.out.println(sqrt(-4.0));
        } catch(IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }

        try {
            System.out.println(sum(4, null));
        } catch(NullPointerException e) {
            System.out.println(e.getMessage());
        }

        try {
            System.out.println(sum(4, null));
        } catch(NullPointerException e) {
            System.out.println(e.getMessage());
        }

        try {
            System.out.println(getValue(6));
        } catch(IndexOutOfBoundsException e) {
            System.out.println(e.getMessage());
        }

        try {
            System.out.println(getValue(-1));
        } catch(IndexOutOfBoundsException e) {
            System.out.println(e.getMessage());
        }

    }

    public static double sqrt(double input) throws IllegalArgumentException {
        Preconditions.checkArgument(input > 0.0,
                "Illegal Argument passed: Negative value %s.", input);
        return Math.sqrt(input);
    }

    public static int sum(Integer a, Integer b) {
        a = Preconditions.checkNotNull(a, "Illegal Argument passed: First parameter is Null.");
        b = Preconditions.checkNotNull(b, "Illegal Argument passed: Second parameter is Null.");

        return a+b;
    }

    public static int getValue(int input) {
        int[] data = {1,2,3,4,5};
        Preconditions.checkElementIndex(input, data.length, "Illegal Argument passed: Invalid index.");
        return data[input];
    }
}

stdout:

Illegal Argument passed: Negative value -4.0.

Illegal Argument passed: Second parameter is Null.

Illegal Argument passed: Second parameter is Null.

Illegal Argument passed: Invalid index. (6) must be less than size (5)

Illegal Argument passed: Invalid index. (-1) must not be negative

com.google.common.collect.Ordering

Orderingは、リストをソートする時に、

最大、最小値を取得したり、

大きい方や、小さい方から、指定した分だけデータを取得したり、

nullが存在していても、ハンドリングできるようにする便利ルーチンです。

メソッドチェーンでつないで処理できるのも特徴です。

guava.dev

com.google.common.collect.Range

Rangeは、範囲を表すクラスで、

その範囲にある値が入っているかや、

複数の範囲がつながっているか、含んでいるか、

複数の範囲の間の範囲の計算などを

簡単に実現することができます。

java.time との組み合わせることで期間を表現できたりする。

guava.dev

 

import com.google.common.collect.Range;

public class RangeSample {
    public static void main(String[] args) {
        // Int range
        Range<Integer> range1 = Range.closed(1, 4);
        System.out.println(range1.contains(1));//true

        // Double range
        Range<Double> range2 = Range.closed(1.0, 4.0);
        System.out.println(range2.contains(3.0));//true
        System.out.println(range2.contains(-1.0));//false

        // gap range
        System.out.println(range2.gap(Range.closed(-1.0, 0.0))); // (0.0..1.0)


        //Time range sample
        LocalDate date1 = LocalDate.of(2013, 12, 31);
        LocalDate date2 = LocalDate.of(2014, 1, 10);
        Range<LocalDate> timeRange = Range.closedOpen(date1, date2);
        System.out.println(timeRange);

        System.out.println(timeRange.contains(LocalDate.of(2014, 1, 1))); // true
        System.out.println(timeRange.contains(LocalDate.of(2014, 1, 11)));// false

    }
}

com.google.common.collect.Multiset

Multisetは重複する要素を許したSetです。

重複する要素の数を数えたり、

重複しないSetをMultisetから取得したり、

ある要素を指定した数だけ削除したりできます。

guava.dev

import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;

public class MultiSet {
    public static void main(String[] args) {
        Multiset<String> multiset = HashMultiset.create();
        multiset.add("a");
        multiset.add("b");
        multiset.add("b");
        multiset.add("x");

        System.out.println(multiset); // [a, b x 2, x]
        System.out.println(multiset.count("b")); // 2
        System.out.println(multiset.size());// 4
        System.out.println(multiset.elementSet()); // [a, b, x]
        multiset.remove("b",2);
        System.out.println(multiset); // [a, x]

    }
}

com.google.common.collect.Multimap

Multimapは先程のMultiSetと似ていますが、

同じキーに複数の値を登録することができるHash mapです。

guava.dev

blog.y-yuki.net

特に、ArrayListMultimapを使うと、

各KeyのデータがArrayとして表されます。

 

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class MultiMap {
    public static void main(String[] args) {

        Multimap<String,String> multimap = ArrayListMultimap.create();

        multimap.put("lower", "a");
        multimap.put("lower", "b");
        multimap.put("lower", "c");

        multimap.put("upper", "A");
        multimap.put("upper", "B");
        System.out.println(multimap);// {lower=[a, b, c, d, e], upper=[A, B, C, D]}

        // すべて置き換え
        multimap.replaceValues("upper", Arrays.asList("C", "D", "E") );
        System.out.println(multimap);// {lower=[a, b, c], upper=[C, D, E]}

        // ある要素を削除
        multimap.remove("upper", "C");
        System.out.println(multimap);// {lower=[a, b, c], upper=[D, E]}

        // あるキーのリストを取得(参照を返すので注意)
        List<String> lowerList = (List<String>) multimap.get("lower");
        System.out.println(lowerList);// [a, b, c]
        lowerList.add("d");
        System.out.println(lowerList);// [a, b, c, d]
        System.out.println(multimap);// {lower=[a, b, c, d], upper=[D, E]}

        // キーを取得 (Multisetとして、重複した値が返ってくる)
        Multiset<String> keys = multimap.keys();
        System.out.println(keys); // [lower x 4, upper x 2]

        // 全要素に個別にアクセス
        for (Map.Entry<String, String> entry : multimap.entries()) {
            System.out.println(entry.getKey() + " -> " + entry.getValue());
//            lower -> a
//            lower -> b
//            lower -> c
//            lower -> d
//            upper -> D
//            upper -> E
        }

        //ある特定のValueが存在するか?
        System.out.println(multimap.containsValue("D"));// true

        //ある特定のKeyとValueが存在するか?
        System.out.println(multimap.containsEntry("upper", "A"));// false

    }
}

com.google.common.collect.Bimap

Bimapは、通常のmapではkeyを使って、

valueを検索することができますが、

Bimapは逆にvalueからkeyを検索できるようにしたmapです。

guava.dev

blog.y-yuki.net

 

import com.google.common.collect.HashBiMap;
import com.google.common.collect.BiMap;

public class BiMapSample {
    public static void main(String[] args) {

        BiMap<Integer, String> bimap = HashBiMap.create();

        bimap.put(1, "A");
        bimap.put(2, "B");
        bimap.put(3, "C");

        System.out.println(bimap);// {1=A, 2=B, 3=C}

        //通常検索
        System.out.println(bimap.get(2));// B

        //逆検索
        System.out.println(bimap.inverse().get("B"));// 2

    }
}

com.google.common.collect.Table

Tableは2つのキーを使った、Mapを表すクラスになります。

行と列で表される二次元の表のようなデータを表現できます。

guava.dev

qiita.com

 

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;

public class TableSample {

    public static void main(String[] args) {
        Table<Integer, Integer, Double> gridMap = HashBasedTable.create();

        // データの追加
        gridMap.put(1, 1,2.0);
        gridMap.put(1, 2,3.0);
        System.out.println(gridMap);// {1={1=2.0, 2=3.0}}

        // データの変更
        gridMap.put(1, 1,5.0);
        System.out.println(gridMap);// {1={1=5.0, 2=3.0}}

        // データ取得
        System.out.println(gridMap.get(1, 1)); // 5.0

        // 行と列の取得
        gridMap.put(2, 1,4.0);
        System.out.println(gridMap.row(2)); // {1=4.0}
        System.out.println(gridMap.column(1)); // {1=5.0, 2=4.0}

        for(Table.Cell<Integer, Integer, Double> cell : gridMap.cellSet()) {
            System.out.println(cell.getRowKey() + "," + cell.getColumnKey() + "," + cell.getValue());
//            1,1,5.0
//            1,2,3.0
//            2,1,4.0
        }
    }
}

com.google.common.cache

cacheは、JVM上にkey-valueで表現されるキャッシュを

簡単に実装することができるようになります。

guava.dev

qiita.com

下記のコードのように、

Databaseからデータを取得するのに、コストがかかる場合、

下記のように、一度データをDatabaseから取得した時に、

2回目からは、JVM上のキャッシュからデータを取得することができます。

 

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import javax.annotation.Nonnull;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class CacheSample {

    public static void main(String[] args) {
        LoadingCache<String, Integer> employeeCache =
                CacheBuilder.newBuilder()
                        .maximumSize(100)                             // maximum 100 records can be cached
                        .expireAfterAccess(30, TimeUnit.MINUTES)      // cache will expire after 30 minutes of access
                        .build(new CacheLoader<String, Integer>() {  // build the cacheloader

                            @Override
                            public Integer load(@Nonnull String id) {
                                return getFromDatabase(id);
                            }
                        });

        try {
            System.out.println("Get data #1");
            System.out.println(employeeCache.get("100"));
            System.out.println(employeeCache.get("103"));
            System.out.println(employeeCache.get("110"));
            // Get data #1
            // Get data from database
            // 100
            // Get data from database
            // 103
            // Get data from database
            // 110

            System.out.println("GetData #2");
            System.out.println(employeeCache.get("100"));
            System.out.println(employeeCache.get("103"));
            System.out.println(employeeCache.get("110"));
            // GetData #2
            // 100
            // 103
            // 110

        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    static int getFromDatabase(String id){
        System.out.println("Get data from database");
        return Integer.parseInt(id);
    }
}

com.google.common.base.Joiner

Joinerは複数の文字列を連結するときに便利なユーティリティ群です。

guava.dev

blog.y-yuki.net

 

Java8以降では、StringJoinerを使ったほうが良さそうですが。

www.baeldung.com

com.google.common.base.Splitter

Splitterは、文字列を分割するためのユーティリティ群です。

guava.dev

com.google.common.primitives

primitivesパッケージの下には、たくさんのプリミティブ型のユーティリティ群が格納されています。

guava.dev

guava.dev

guava.dev

guava.dev

com.google.common.base.Stopwatch

Stopwatchはまさにストップウォッチの機能に実現するクラスです。

簡単にある時点からの経過時間を、様々な単位で計測することができます。

guava.dev

 

import com.google.common.base.Stopwatch;

import java.util.concurrent.TimeUnit;

public class StopWatchSample {
    public static void main(String[] args) throws InterruptedException {
        Stopwatch sw = Stopwatch.createStarted();

        while(true) {
            System.out.println(sw.elapsed(TimeUnit.SECONDS) + "sec");
            Thread.sleep(5000);
            if (sw.elapsed(TimeUnit.SECONDS) > 30) {
                sw.stop();
                sw.reset();
                System.out.println("Done");
                break;
            }
        }
//        0sec
//        5sec
//        10sec
//        15sec
//        20sec
//        25sec
//        30sec
//        Done
    }
}

com.google.common.collect.ImmutableList

ImmutableListは、名前の通り、変更不可能なリストです。

要素の追加や、変更、削除をしようとすると、

UnsupportedOperationExceptionが投げられます。

guava.dev

qiita.com

 

Javaにも、unmodifiableListがありますが、

こちらは元のリストの参照を使うので、元のリストが変更されると

unmodifiableListも変更されてしまいますが、

GuavaのImmutableListは、コピーを格納するので、

元のリストが変更されても影響されません。

 

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;

import java.util.Collection;
import java.util.List;

public class ImmutableListSample {
    public static void main(String[] args) {
        List<String> alist = ImmutableList.of("one", "two");

        try {
            alist.add("Three");//追加不可
        } catch (UnsupportedOperationException e) {
            System.out.println(e);
        }

        try {
            alist.set(0, "Three");//変更不可
        } catch (UnsupportedOperationException e) {
            System.out.println(e);
        }

        try {
            alist.remove(0);//削除不可
        } catch (UnsupportedOperationException e) {
            System.out.println(e);
        }

        // 取得
        System.out.println(alist.get(1));// two

        // 通常のリストからImmutableListへの変換
        Collection<Integer> list = Lists.newArrayList(1, 2, 3);
        Collection<Integer> immutableList = ImmutableList.copyOf(list);
        System.out.println(immutableList);//[1, 2, 3]
    }
}

ImmutableListは、名前の通り、変更不可能なMapです。

要素の追加や、変更、削除をしようとすると、

UnsupportedOperationExceptionが投げられます。

guava.dev

blog1.mammb.com

import com.google.common.collect.ImmutableMap;

import java.util.Map;

public class ImmutableMapSample {
    public static void main(String[] args) {
        Map<Integer, String> immutableMap = ImmutableMap.of(1, "1", 2, "2", 3, "3");
        System.out.println(immutableMap);// {1=1, 2=2, 3=3}

        try {
            immutableMap.put(4, "4");//追加不可
        } catch (UnsupportedOperationException e) {
            System.out.println(e);
        }
        try {
            immutableMap.replace(1, "2");//変更不可
        } catch (UnsupportedOperationException e) {
            System.out.println(e);
        }
        try {
            immutableMap.remove(1);//削除不可
        } catch (UnsupportedOperationException e) {
            System.out.println(e);
        }

        // 取得
        System.out.println(immutableMap.get(1));// 1

        //Chain methodによる初期化
        ImmutableMap<String, Integer> immutableMap2 = new ImmutableMap.Builder<String, Integer>()
                .put("four", 4)
                .put("eight", 8)
                .put("fifteen", 15)
                .put("sixteen", 16)
                .put("twenty-three", 23)
                .put("forty-two", 42)
                .build();
        System.out.println(immutableMap2);//{four=4, eight=8, fifteen=15, sixteen=16, twenty-three=23, forty-two=42}

    }
}

@VisibleForTesting

テスト用にメソッドやフィールドのアクセス権限を変更した場合に、

つけるアノテーション。

コードを読む人がわかりやすくするためのものであり、

つけてもつけなくても、コードとしては影響はない。

guava.dev

qiita.com

 

Unsigned対応

他の言語には普通に存在しているが、

Javaには無いもののとして、Unsigned型がありますが、

GuavaではUnsigned型をサポートしています。

github.com

UnsignedIntegerと、UnsignedLongはそれぞれ、intとlongのUnsigned型です。

guava.dev

guava.dev

下記のクラスはこれらのUnsigned型に関するユーティリティ関数群です。

guava.dev

guava.dev

guava.dev

 

import com.google.common.primitives.UnsignedInteger;

public class UnsignedSample {

    public static void main(String[] args) {
        int a = 0;
        a-=1;
        System.out.println(a);// -1

        UnsignedInteger ua = UnsignedInteger.valueOf(1);
        ua = ua.minus(UnsignedInteger.valueOf(1));
        System.out.println(ua);// 0
        ua = ua.minus(UnsignedInteger.valueOf(1));
        System.out.println(ua);// 4294967295 (underflow)

        a = Integer.MAX_VALUE;
        System.out.println(a);// 2147483647
        a+=1;
        System.out.println(a);//-2147483648 (overflow)

    }
}

Graph

Guavaにはグラフ構造を表現するクラス群も実装されています。

github.com

現時点ではあまり高度なアルゴリズムなどは実装されないようで、

グラフデータ格納用の型として使うのが良さそうです。

 

参考資料

www.tutorialspoint.com

java-study.blog.jp

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

MyEnigma Supporters

もしこの記事が参考になり、

ブログをサポートしたいと思われた方は、

こちらからよろしくお願いします。

myenigma.hatenablog.com