Guava工程包含了若干被Google的 Java項目廣泛依賴 的核心庫,例如:集合 [collections] 、緩存 [caching] 、原生類型支持 [primitives support] 、並發庫 [concurrency libraries] 、通用注解 [common annotations] 、字符串處理 [string processing] 、I/O 等等。
guava類似Apache Commons工具集
基本工具包Base
Optional
guava的Optional類似於Java 8新增的Optional類,都是用來處理null的,不過guava的是抽象類,其實現類為Absent和Present,而java.util的是final類。其中一部分方法名是相同的。
Guava用Optional表示可能為null的T類型引用。一個Optional實例可能包含非null的引用(我們稱之為引用存在),也可能什么也不包括(稱之為引用缺失)。它從不說包含的是null值,而是用存在或缺失來表示。但Optional從不會包含null值引用。
public class OptionalDemo { public static void main(String[] args) { Integer value1=null; Integer value2=10; /*創建指定引用的Optional實例,若引用為null則快速失敗返回absent() absent()創建引用缺失的Optional實例 */ Optional<Integer> a=Optional.fromNullable(value1); Optional<Integer> b=Optional.of(value2); //返回包含給定的非空引用Optional實例 System.out.println(sum(a,b)); } private static Integer sum(Optional<Integer> a,Optional<Integer> b){ //isPresent():如果Optional包含非null的引用(引用存在),返回true System.out.println("First param is present: "+a.isPresent()); System.out.println("Second param is present: "+b.isPresent()); Integer value1=a.or(0); //返回Optional所包含的引用,若引用缺失,返回指定的值 Integer value2=b.get(); //返回所包含的實例,它必須存在,通常在調用該方法時會調用isPresent()判斷是否為null return value1+value2; } }
Preconditions
前置條件Preconditions提供靜態方法來檢查方法或構造函數,被調用是否給定適當的參數。它檢查的先決條件。其方法失敗拋出IllegalArgumentException。
public class PreconditionsDemo { public static void main(String[] args) { try { getValue(5); } catch (IndexOutOfBoundsException e){ System.out.println(e.getMessage()); } try { sum(4,null); } catch (NullPointerException e){ System.out.println(e.getMessage()); } try { sqrt(-1); } catch (IllegalArgumentException e){ System.out.println(e.getMessage()); } } private static double sqrt(double input){ Preconditions.checkArgument(input>0.0, "Illegal Argument passed: Negative value %s.",input); return Math.sqrt(input); } private 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: First parameter is Null."); return a+b; } private static int getValue(int input){ int[] data={1,2,3,4,5}; int index=Preconditions.checkElementIndex(input,data.length, "Illegal Argument passed: Invalid index."); return data[index]; } }
Joiner
Joiner 提供了各種方法來處理字符串加入操作,對象等。
Joiner的實例不可變的,因此是線程安全的。
Warning: joiner instances are always immutable; a configuration method such as { useForNull} has no effect on the instance it is invoked on! You must store and use the new joiner instance returned by the method. This makes joiners thread-safe, and safe to store as {@code static final} constants.
{@code
// Bad! Do not do this!
Joiner joiner = Joiner.on(‘,’);
joiner.skipNulls(); // does nothing!分開寫跳過null就不起作用了,因為實例不可改變
return joiner.join(“wrong”, null, “wrong”);}
public class JoinerDemo { public static void main(String[] args) { /* on:制定拼接符號,如:test1-test2-test3 中的 “-“ 符號 skipNulls():忽略NULL,返回一個新的Joiner實例 useForNull(“Hello”):NULL的地方都用字符串”Hello”來代替 */ StringBuilder sb=new StringBuilder(); Joiner.on(",").skipNulls().appendTo(sb,"Hello","guava"); System.out.println(sb); System.out.println(Joiner.on(",").useForNull("none").join(1,null,3)); System.out.println(Joiner.on(",").skipNulls().join(Arrays.asList(1,2,3,4,null,6))); Map<String,String>map=new HashMap<>(); map.put("key1","value1"); map.put("key2","value2"); map.put("key3","value3"); System.out.println(Joiner.on(",").withKeyValueSeparator("=").join(map)); } }
Splitter
Splitter 能夠將一個字符串按照指定的分隔符拆分成可迭代遍歷的字符串集合,Iterable
public class SplitterDemo { public static void main(String[] args) { /* on():指定分隔符來分割字符串 limit():當分割的子字符串達到了limit個時則停止分割 fixedLength():根據長度來拆分字符串 trimResults():去掉子串中的空格 omitEmptyStrings():去掉空的子串 withKeyValueSeparator():要分割的字符串中key和value間的分隔符,分割后的子串中key和value間的分隔符默認是= */ System.out.println(Splitter.on(",").limit(3).trimResults().split(" a, b, c, d"));//[ a, b, c,d] System.out.println(Splitter.fixedLength(3).split("1 2 3"));//[1 2, 3] System.out.println(Splitter.on(" ").omitEmptyStrings().splitToList("1 2 3")); System.out.println(Splitter.on(",").omitEmptyStrings().split("1,,,,2,,,3"));//[1, 2, 3] System.out.println(Splitter.on(" ").trimResults().split("1 2 3")); //[1, 2, 3],默認的連接符是, System.out.println(Splitter.on(";").withKeyValueSeparator(":").split("a:1;b:2;c:3"));//{a=1, b=2, c=3} } }
Objects
java7及以后的版本建議使用jdk中的Objects類
EventBus
Guava為我們提供了事件總線EventBus庫,它是事件發布-訂閱模式的實現,讓我們能在領域驅動設計(DDD)中以事件的弱引用本質對我們的模塊和領域邊界很好的解耦設計。
Guava為我們提供了同步事件EventBus和異步實現AsyncEventBus兩個事件總線,他們都不是單例的。
Guava發布的事件默認不會處理線程安全的,但我們可以標注@AllowConcurrentEvents來保證其線程安全
如果Listener A監聽Event A, 而Event A有一個子類Event B, 此時Listener A將同時接收Event A和B消息
事件
//Guava 發布-訂閱模式中傳遞的事件,是一個普通的POJO類 public class OrderEvent { //事件 private String message; public OrderEvent(String message) { this.message = message; } public String getMessage() { return message; } }
訂閱
public class EventListener { //訂閱者 //@Subscribe保證有且只有一個輸入參數,如果你需要訂閱某種類型的消息,只需要在指定的方法上加上@Subscribe注解即可 @Subscribe public void listen(OrderEvent event){ System.out.println("receive message: "+event.getMessage()); } /* 一個subscriber也可以同時訂閱多個事件 Guava會通過事件類型來和訂閱方法的形參來決定到底調用subscriber的哪個訂閱方法 */ @Subscribe public void listen(String message){ System.out.println("receive message: "+message); } }
多個訂閱者
public class MultiEventListener { @Subscribe public void listen(OrderEvent event){ System.out.println("receive msg: "+event.getMessage()); } @Subscribe public void listen(String message){ System.out.println("receive msg: "+message); } }
public class EventBusDemo { public static void main(String[] args) { EventBus eventBus=new EventBus("jack"); /* 如果多個subscriber訂閱了同一個事件,那么每個subscriber都將收到事件通知 並且收到事件通知的順序跟注冊的順序保持一致 */ eventBus.register(new EventListener()); //注冊訂閱者 eventBus.register(new MultiEventListener()); eventBus.post(new OrderEvent("hello")); //發布事件 eventBus.post(new OrderEvent("world")); eventBus.post("!"); } }
DeadEvent
如果EventBus發送的消息都不是訂閱者關心的稱之為Dead Event。
public class DeadEventListener { boolean isDelivered=true; @Subscribe public void listen(DeadEvent event){ isDelivered=false; System.out.println(event.getSource().getClass()+" "+event.getEvent()); //source通常是EventBus } public boolean isDelivered() { return isDelivered; } }
Collection
不可變集合
不可變對象有很多優點,包括:
- 當對象被不可信的庫調用時,不可變形式是安全的;
- 不可變對象被多個線程調用時,不存在競態條件問題
- 不可變集合不需要考慮變化,因此可以節省時間和空間。所有不可變的集合都比它們的可變形式有更好的內存利用率(分析和測試細節);
- 不可變對象因為有固定不變,可以作為常量來安全使用。
JDK也提供了Collections.unmodifiableXXX方法把集合包裝為不可變形式,但:
- 笨重而且累贅:不能舒適地用在所有想做防御性拷貝的場景;
- 不安全:要保證沒人通過原集合的引用進行修改,返回的集合才是事實上不可變的;
- 低效:包裝過的集合仍然保有可變集合的開銷,比如並發修改的檢查、散列表的額外空間,等等。
創建不可變集合方法:
- copyOf方法,如ImmutableSet.copyOf(set);
- of方法,如ImmutableSet.of(“a”, “b”, “c”)或 ImmutableMap.of(“a”, 1, “b”, 2);
- Builder工具
public class ImmutableDemo { public static void main(String[] args) { ImmutableSet<String> set=ImmutableSet.of("a","b","c","d"); ImmutableSet<String> set1=ImmutableSet.copyOf(set); ImmutableSet<String> set2=ImmutableSet.<String>builder().addAll(set).add("e").build(); System.out.println(set); ImmutableList<String> list=set.asList(); } }
新型集合類
Multiset
Multiset可統計一個詞在文檔中出現了多少次
public class MultiSetDemo { public static void main(String[] args) { Multiset<String> set=LinkedHashMultiset.create(); set.add("a"); set.add("a"); set.add("a"); set.add("a"); set.setCount("a",5); //添加或刪除指定元素使其在集合中的數量是count System.out.println(set.count("a")); //給定元素在Multiset中的計數 System.out.println(set); System.out.println(set.size()); //所有元素計數的總和,包括重復元素 System.out.println(set.elementSet().size()); //所有元素計數的總和,不包括重復元素 set.clear(); //清空集合 System.out.println(set); } }
Multimap
Multimap可以很容易地把一個鍵映射到多個值。換句話說,Multimap是把鍵映射到任意多個值的一般方式。
public class MultiMapDemo { public static void main(String[] args) { Multimap<String,Integer> map= HashMultimap.create(); //Multimap是把鍵映射到任意多個值的一般方式 map.put("a",1); //key相同時不會覆蓋原value map.put("a",2); map.put("a",3); System.out.println(map); //{a=[1, 2, 3]} System.out.println(map.get("a")); //返回的是集合 System.out.println(map.size()); //返回所有”鍵-單個值映射”的個數,而非不同鍵的個數 System.out.println(map.keySet().size()); //返回不同key的個數 Map<String,Collection<Integer>> mapView=map.asMap(); } }
BiMap
BiMap
public class BitMapDemo { public static void main(String[] args) { BiMap<String,String> biMap= HashBiMap.create(); biMap.put("sina","sina.com"); biMap.put("qq","qq.com"); biMap.put("sina","sina.cn"); //會覆蓋原來的value /* 在BiMap中,如果你想把鍵映射到已經存在的值,會拋出IllegalArgumentException異常 如果對特定值,你想要強制替換它的鍵,請使用 BiMap.forcePut(key, value) */ biMap.put("tecent","qq.com"); //拋出異常 biMap.forcePut("tecent","qq.com"); //強制替換key System.out.println(biMap); System.out.println(biMap.inverse().get("sina.com")); //通過value找key System.out.println(biMap.inverse().inverse()==biMap); //true } }
Table
Table它有兩個支持所有類型的鍵:”行”和”列”。
public class TableDemo { public static void main(String[] args) { //記錄學生在某門課上的成績 Table<String,String,Integer> table= HashBasedTable.create(); table.put("jack","java",100); table.put("jack","c",90); table.put("mike","java",93); table.put("mike","c",100); Set<Table.Cell<String,String,Integer>> cells=table.cellSet(); for (Table.Cell<String,String,Integer> cell : cells) { System.out.println(cell.getRowKey()+" "+cell.getColumnKey()+" "+cell.getValue()); } System.out.println(table.row("jack")); System.out.println(table); System.out.println(table.rowKeySet()); System.out.println(table.columnKeySet()); System.out.println(table.values()); } }
Collections2
filter():只保留集合中滿足特定要求的元素
public class FilterDemo { public static void main(String[] args) { List<String> list= Lists.newArrayList("moon","dad","refer","son"); Collection<String> palindromeList= Collections2.filter(list, input -> { return new StringBuilder(input).reverse().toString().equals(input); //找回文串 }); System.out.println(palindromeList); } }
transform():類型轉換
public class TransformDemo { public static void main(String[] args) { Set<Long> times= Sets.newHashSet(); times.add(91299990701L); times.add(9320001010L); times.add(9920170621L); Collection<String> timeStrCol= Collections2.transform(times, new Function<Long, String>() { @Nullable @Override public String apply(@Nullable Long input) { return new SimpleDateFormat("yyyy-MM-dd").format(input); } }); System.out.println(timeStrCol); } }
多個Function組合
public class TransformDemo { public static void main(String[] args) { List<String> list= Lists.newArrayList("abcde","good","happiness"); //確保容器中的字符串長度不超過5 Function<String,String> f1=new Function<String, String>() { @Nullable @Override public String apply(@Nullable String input) { return input.length()>5?input.substring(0,5):input; } }; //轉成大寫 Function<String,String> f2=new Function<String, String>() { @Nullable @Override public String apply(@Nullable String input) { return input.toUpperCase(); } }; Function<String,String> function=Functions.compose(f1,f2); Collection<String> results=Collections2.transform(list,function); System.out.println(results); } }
集合操作:交集、差集、並集
public class CollectionsDemo { public static void main(String[] args) { Set<Integer> set1= Sets.newHashSet(1,2,3,4,5); Set<Integer> set2=Sets.newHashSet(3,4,5,6); Sets.SetView<Integer> inter=Sets.intersection(set1,set2); //交集 System.out.println(inter); Sets.SetView<Integer> diff=Sets.difference(set1,set2); //差集,在A中不在B中 System.out.println(diff); Sets.SetView<Integer> union=Sets.union(set1,set2); //並集 System.out.println(union); } }
Cache
緩存在很多場景下都是相當有用的。例如,計算或檢索一個值的代價很高,並且對同樣的輸入需要不止一次獲取值的時候,就應當考慮使用緩存。
Guava Cache與ConcurrentMap很相似,但也不完全一樣。最基本的區別是ConcurrentMap會一直保存所有添加的元素,直到顯式地移除。相對地,Guava Cache為了限制內存占用,通常都設定為自動回收元素。在某些場景下,盡管LoadingCache 不回收元素,它也是很有用的,因為它會自動加載緩存。
Guava Cache是一個全內存的本地緩存實現,它提供了線程安全的實現機制。
通常來說,Guava Cache適用於:
- 你願意消耗一些內存空間來提升速度。
- 你預料到某些鍵會被查詢一次以上。
- 緩存中存放的數據總量不會超出內存容量。(Guava Cache是單個應用運行時的本地緩存。它不把數據存放到文件或外部服務器。
如果這不符合你的需求,請嘗試Memcached這類工具)
Guava Cache有兩種創建方式:
- cacheLoader
- callable callback
LoadingCache是附帶CacheLoader構建而成的緩存實現
public class LoadingCacheDemo { public static void main(String[] args) { LoadingCache<String,String> cache= CacheBuilder.newBuilder() .maximumSize(100) //最大緩存數目 .expireAfterAccess(1, TimeUnit.SECONDS) //緩存1秒后過期 .build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { return key; } }); cache.put("j","java"); cache.put("c","cpp"); cache.put("s","scala"); cache.put("g","go"); try { System.out.println(cache.get("j")); TimeUnit.SECONDS.sleep(2); System.out.println(cache.get("s")); //輸出s } catch (ExecutionException | InterruptedException e) { e.printStackTrace(); } } }
public class CallbackDemo { public static void main(String[] args) { Cache<String,String> cache= CacheBuilder.newBuilder() .maximumSize(100) .expireAfterAccess(1, TimeUnit.SECONDS) .build(); try { String result=cache.get("java", () -> "hello java"); System.out.println(result); } catch (ExecutionException e) { e.printStackTrace(); } } }
refresh機制:
- LoadingCache.refresh(K) 在生成新的value的時候,舊的value依然會被使用。
- CacheLoader.reload(K, V) 生成新的value過程中允許使用舊的value
- CacheBuilder.refreshAfterWrite(long, TimeUnit) 自動刷新cache
並發
ListenableFuture
傳統JDK中的Future通過異步的方式計算返回結果:在多線程運算中可能或者可能在沒有結束返回結果,Future是運行中的多線程的一個引用句柄,確保在服務執行返回一個Result。
ListenableFuture可以允許你注冊回調方法(callbacks),在運算(多線程執行)完成的時候進行調用, 或者在運算(多線程執行)完成后立即執行。這樣簡單的改進,使得可以明顯的支持更多的操作,這樣的功能在JDK concurrent中的Future是不支持的。
public class ListenableFutureDemo { public static void main(String[] args) { //將ExecutorService裝飾成ListeningExecutorService ListeningExecutorService service= MoreExecutors.listeningDecorator( Executors.newCachedThreadPool()); //通過異步的方式計算返回結果 ListenableFuture<String> future=service.submit(() -> { System.out.println("call execute.."); return "task success!"; }); //有兩種方法可以執行此Future並執行Future完成之后的回調函數 future.addListener(() -> { //該方法會在多線程運算完的時候,指定的Runnable參數傳入的對象會被指定的Executor執行 try { System.out.println("result: "+future.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } },service); Futures.addCallback(future, new FutureCallback<String>() { @Override public void onSuccess(@Nullable String result) { System.out.println("callback result: "+result); } @Override public void onFailure(Throwable t) { System.out.println(t.getMessage()); } },service); } }
IO
public class FileDemo { public static void main(String[] args) { File file=new File(System.getProperty("user.dir")); } //寫文件 private void writeFile(String content,File file) throws IOException { if (!file.exists()){ file.createNewFile(); } Files.write(content.getBytes(Charsets.UTF_8),file); } //讀文件 private List<String> readFile(File file) throws IOException { if (!file.exists()){ return ImmutableList.of(); //避免返回null } return Files.readLines(file,Charsets.UTF_8); } //文件復制 private void copyFile(File from,File to) throws IOException { if (!from.exists()){ return; } if (!to.exists()){ to.createNewFile(); } Files.copy(from,to); } }