目次
- 目次
- はじめに
- com.google.common.base.Optional
- com.google.common.base.Preconditions
- com.google.common.collect.Ordering
- com.google.common.collect.Range
- com.google.common.collect.Multiset
- com.google.common.collect.Multimap
- com.google.common.collect.Bimap
- com.google.common.collect.Table
- com.google.common.cache
- com.google.common.base.Joiner
- com.google.common.base.Splitter
- com.google.common.primitives
- com.google.common.base.Stopwatch
- com.google.common.collect.ImmutableList
- @VisibleForTesting
- Unsigned対応
- Graph
- 参考資料
- MyEnigma Supporters
はじめに
PythonやJuliaを使っている人が、
Javaを使うと、
言語のデフォルト機能が足りないなと
思うことがあると思います。
そんな時に便利なのが、
Googleが開発公開しているJavaライブラリであるGuavaです。
Guavaは、Javaのデフォルト機能で実装すると、
コードの量が長くなってしまったり、
バグが入りやすくなってしまうコードを、
簡潔に、バグが入りにくく実装することを
目的に開発されています。
Guavaはかなり古くから開発されているため、
Java8などで導入された機能とかぶっている部分も多いですが、
それでもまだデフォルトの機能にはない沢山の機能が実装されています。
今回の記事では、
Guavaの代表的な機能の概要と
コードサンプルを紹介したいと思います。
すべてのコードサンプルはこちらでも公開しています。
com.google.common.base.Optional
OptionalはJava 8から導入されたOptionalと同じく、
nullになる可能性があるオブジェクトを取り扱うことができます。
Java8以前を使っている場合は、非常に便利だと思います。
com.google.common.base.Preconditions
Preconditionは、関数の引数の条件などをチェックできるユーティリティ群です。
このPreconditionを使うことで、エラーメッセージがわかりやすく表示されます。
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が存在していても、ハンドリングできるようにする便利ルーチンです。
メソッドチェーンでつないで処理できるのも特徴です。
com.google.common.collect.Range
Rangeは、範囲を表すクラスで、
その範囲にある値が入っているかや、
複数の範囲がつながっているか、含んでいるか、
複数の範囲の間の範囲の計算などを
簡単に実現することができます。
java.time との組み合わせることで期間を表現できたりする。
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から取得したり、
ある要素を指定した数だけ削除したりできます。
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です。
特に、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です。
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を表すクラスになります。
行と列で表される二次元の表のようなデータを表現できます。
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で表現されるキャッシュを
簡単に実装することができるようになります。
下記のコードのように、
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は複数の文字列を連結するときに便利なユーティリティ群です。
Java8以降では、StringJoinerを使ったほうが良さそうですが。
com.google.common.base.Splitter
Splitterは、文字列を分割するためのユーティリティ群です。
com.google.common.primitives
primitivesパッケージの下には、たくさんのプリミティブ型のユーティリティ群が格納されています。
com.google.common.base.Stopwatch
Stopwatchはまさにストップウォッチの機能に実現するクラスです。
簡単にある時点からの経過時間を、様々な単位で計測することができます。
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が投げられます。
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が投げられます。
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
テスト用にメソッドやフィールドのアクセス権限を変更した場合に、
つけるアノテーション。
コードを読む人がわかりやすくするためのものであり、
つけてもつけなくても、コードとしては影響はない。
Unsigned対応
他の言語には普通に存在しているが、
Javaには無いもののとして、Unsigned型がありますが、
GuavaではUnsigned型をサポートしています。
UnsignedIntegerと、UnsignedLongはそれぞれ、intとlongのUnsigned型です。
下記のクラスはこれらのUnsigned型に関するユーティリティ関数群です。
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にはグラフ構造を表現するクラス群も実装されています。
現時点ではあまり高度なアルゴリズムなどは実装されないようで、
グラフデータ格納用の型として使うのが良さそうです。
参考資料
MyEnigma Supporters
もしこの記事が参考になり、
ブログをサポートしたいと思われた方は、
こちらからよろしくお願いします。