一、简介
Optional类是Java8为了解决null值判断问题,借鉴google guava类库的Optional类而引入的一个同名Optional类,使用Optional类可以避免显式的null值判断(null的防御性检查),避免null导致的NPE(NullPointerException)。
二、Optional 类典型接口的使用
2.1 get()方法
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
get()方法主要用于获取实体值,当值为null时,跑出异常,所以一般不用这个方法,建议用orElse()
2.2 isPresent()方法
public boolean isPresent() {
return value != null;
}
其实就是判断一下这个optional对象是不是为空,觉得用处不大,因为optional本身就是避免盘空逻辑才出现的
public static String getGender(Student student)
{
Optional<Student> stuOpt = Optional.ofNullable(student);
if(stuOpt.isPresent()){
return stuOpt.get().getGender();
}
return "Unkown";
}
以上就是回归了以前的代码逻辑,所以个人觉得没啥用处
2.3 ifPresent()方法
public void ifPresent(Consumer<? super T> consumer) {
if (value != null){
consumer.accept(value);
}
}
用来给消费者场景做NPE判断,避免空指针问题。
public static void printName(Student student){
Optional.ofNullable(student).ifPresent(u -> System.out.println("The student name is : " + u.getName()));
}
2.4 filter()方法
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent()){
return this;
}else{
return predicate.test(value) ? this : empty();
}
}
对optional对象按一定条件进行过滤,所以可以理解为代替stream了吧
public static void filterAge(Student student){
Optional.ofNullable(student).filter( u -> u.getAge() > 18).ifPresent(u -> System.out.println("The student age is more than 18."));
}
2.5 map()方法
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()){
return empty();
}else {
return Optional.ofNullable(mapper.apply(value));
}
}
map方法主要实现一些映射,可以实现一个数据的处理
public static Optional<Integer> getAge(Student student){
return Optional.ofNullable(student).map(u -> u.getAge());
}
2.6 flatMap()方法
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()){
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
不太懂,二维变一维
public static Optional<Integer> getAge(Student student){
return Optional.ofNullable(student).flatMap(u -> Optional.ofNullable(u.getAge()));
}
2.7 orElse()方法
public T orElse(T other) {
return value != null ? value : other;
}
这个应该是最常见的操作了吧
public static String getGender(Student student){
return Optional.ofNullable(student).map(u -> u.getGender()).orElse("Unkown");
}
为什么要使用Preconditions
那么你可能会问,为啥要用Preconditions,如果是空指针在用的时候,他自己就会抛出异常啊。
但是我们期望的是尽早抛出异常,而不是等到数据被层层传递,传递到非常深的位置,这样浪费系统资源更不利于我们开发精确定位错误。
所以推荐在方法的入口,或运算开始前,先检查数据。
int length = datas.get(5).getDescription().concat("XX").split(",").length;
像这种代码,就算抛出空指针也不知道到底是那个对象空指针了,需要一步一步调试
这就是Preconditions另一个作用:尽早发现错误,精确控制出错的位置。
1 .checkArgument(boolean) :
功能描述:检查boolean是否为真。 用作方法中检查参数
失败时抛出的异常类型: IllegalArgumentException
public static void main(String[] args) {
Boolean value = new Boolean(Boolean.FALSE);
Preconditions.checkArgument(value, "参数不对");
}
//Exception in thread "main" java.lang.IllegalArgumentException: 参数不对
2.checkNotNull(T):
功能描述:检查value不为null, 直接返回value;
失败时抛出的异常类型:NullPointerException
Preconditions.checkNotNull(null, "参数不对");
//Exception in thread "main" java.lang.NullPointerException: 参数不对
3.checkState(boolean):
功能描述:检查对象的一些状态,不依赖方法参数。 例如, Iterator可以用来next是否在remove之前被调用。
失败时抛出的异常类型:IllegalStateException
4.checkElementIndex(int index, int size):
功能描述:检查index是否为在一个长度为size的list, string或array合法的范围。 index的范围区间是[0, size)(包含0不包含size)。无需直接传入list, string或array, 只需传入大小。返回index。
失败时抛出的异常类型:IndexOutOfBoundsException
5.checkPositionIndex(int index, int size):
功能描述:检查位置index是否为在一个长度为size的list, string或array合法的范围。 index的范围区间是[0, size)(包含0不包含size)。无需直接传入list, string或array, 只需传入大小。返回index。
失败时抛出的异常类型:IndexOutOfBoundsException
6.checkPositionIndexes(int start, int end, int size):
功能描述:检查[start, end)是一个长度为size的list, string或array合法的范围子集。伴随着错误信息。
失败时抛出的异常类型:IndexOutOfBoundsException
连接器 Joiner
用分隔符将多个字符串(或数组元素)连接成一个字符串。
常用方法如下:
-
on(String):静态工厂方法,生成一个新的 Joiner 对象,参数为连接符
-
skipNulls():如果元素为空,则跳过
-
useForNull(String):如果元素为空,则用这个字符串代替
-
join(数组/链表):要连接的数组/链表
-
appendTo(String,数组/链表):在第一个参数后面新加上 拼接后的字符串
-
withKeyValueSeparator(String):得到 MapJoiner,连接Map的键、值
@Test
public void test(){
List<String> list1 = Arrays.asList("aa", "bb", "cc");
System.out.println(Joiner.on("-").join(list1));
List<String> list2 = Arrays.asList("aa", "bb", "cc", null, "dd");
System.out.println(Joiner.on("-").skipNulls().join(list2));
System.out.println(Joiner.on("-").useForNull("nulla").join(list2));
Map map = ImmutableMap.of("k1", "v1", "k2", "v2");
System.out.println(Joiner.on("-").withKeyValueSeparator("=").join(map));
}
aa-bb-cc
aa-bb-cc-dd
aa-bb-cc-null-dd
k1=v1-k2=v2
public class JoinerTest {
// on(String separator) 初始化Joiner连接器,separator为Joiner连接器的连接符.
// join(Iterable<?> parts) 将parts通过连接器的连接符连接成字符串
// skipNulls() 用于过滤集合中为null的元素,然后返回一个新的Joiner对象实例.
// useForNull(String nullText) 连接器做join连接操作时用nullText替换null元素值
private static final List<String> list = Lists.newArrayList("java","php","python");
private static final List<String> listWithNull = Lists.newArrayList("java",null,"php","python");
@Test
public void on_join(){
String join = Joiner.on(";").join(list);
System.out.println(join); //java;php;python
}
@Test(expected = NullPointerException.class)
public void on_join_listWithNull(){
String join = Joiner.on(":").join(listWithNull); //空元素会导致NullPointerException
System.out.println(join);
}
@Test
public void skipNulls_useForNull(){
//跳过null对象
String join = Joiner.on(",").skipNulls().join(listWithNull);
System.out.println(join); //java,php,python
//null对象用默认值代替
String join1 = Joiner.on(",").useForNull("null").join(listWithNull);
System.out.println(join1); //java,null,php,python
}
// appendTo 将结果输出到文件中
// appendTo 将结果追加到StringBuilder中
@Test
public void appendTo(){
try (FileWriter writer = new FileWriter(new File("D:\\temp.txt"))){
FileWriter fileWriter = Joiner.on(":").useForNull("默认值").appendTo(writer, listWithNull);
} catch (IOException e) {
System.out.println(e.getMessage());
}
StringBuilder stringBuilder = Joiner.on(",").appendTo(new StringBuilder(), list);
System.out.println(stringBuilder.toString()); //java,php,python
}
// MapJoiner 的使用,将 map 转换为字符串
// withKeyValueSeparator 初始化一个Map连接器,连接器连接Map对象时,keyValueSeparator为key和value之间的分隔符
@Test
public void mapJoiner(){
Map<String, String> map = ImmutableMap.of("hello", "java", "hi", "python");
String join = Joiner.on("; ").withKeyValueSeparator("=").join(map);
System.out.println(join); //hello=java; hi=python
}
// java8 测试joining
@Test
public void joining(){
String joining = listWithNull.stream().filter(s -> s != null && !"".equals(s)).collect(Collectors.joining(":"));
System.out.println(joining); //java:php:python
}
}
Splitter 能将一个字符串按照分隔符生成字符串集合,是 Joiner的反向操作。
常用方法如下:
-
on(String):静态工厂方法,生成一个新的 Splitter 对象,参数为连接符
-
trimResults():结果去除子串中的空格
-
omitEmptyStrings():去除null的子串
-
split(String):拆分字符串
-
withKeyValueSeparator(String):得到 MapSplitter,拆分成Map的键、值。注意,这个对被拆分字符串格式有严格要求,否则会抛出异常
public static void main(String[] args) {
// Preconditions.checkNotNull(null, "参数不对");
System.out.printf("sss");
String string = " ,a,b,";
System.out.println(Splitter.on(",").split(string));
System.out.println(Splitter.on(",").trimResults().split(string));
System.out.println(Splitter.on(",").omitEmptyStrings().split(string));
System.out.println(Splitter.on(",").trimResults().omitEmptyStrings().split(string));
// 根据长度拆分
string = "12345678";
System.out.println(Splitter.fixedLength(2).split(string));
// 拆分成Map
System.out.println(Splitter.on("#").withKeyValueSeparator(":").split("1:2#3:4"));
}
[ , a, b, ]
[, a, b, ]
[ , a, b]
[a, b]
[12, 34, 56, 78]
{1=2, 3=4}
Strings 类主要提供了对字符串的一些操作。主要方法如下:
-
nullToEmpty(String string) :null字符串转空字符串
-
emptyToNull(String string):空字符串转null字符串
-
isNullOrEmpty(String string):判断字符串为null或空字符串
-
padStart(String string, int minLength, char padChar):如果string的长度小于minLeng,在string前添加padChar,直到字符串长度为minLeng。
-
String padEnd(String string, int minLength, char padChar):和padStart类似,不过是在尾部添加新字符串
-
commonPrefix(CharSequence a, CharSequence b):返回共同的前缀
-
commonSuffix(CharSequence a, CharSequence b):返回共同的后缀
public void test(){
String aa = "12345";
// A12345
System.out.println(Strings.padStart(aa, 6, 'A'));
// 12345
System.out.println(Strings.padStart(aa, 5, 'A'));
}
public void test2(){
String aa = "abc123def";
String bb = "abc789def";
System.out.println(Strings.commonPrefix(aa, bb));
System.out.println(Strings.commonSuffix(aa, bb));
}
abc
def
集合工具(Collections)
1. 不可变集合
不可变集合,即创建后就只可读,不可修改的集合。为什么要使用不可变集合呢?主要有如下优点:
-
当对象被不可信的库调用时,不可变形式是安全的;
-
不可变对象被多个线程调用时,不存在竞态条件问题
-
不可变集合不需要考虑变化,因此可以节省时间和空间。所有不可变的集合都比它们的可变形式有更好的内存利用率(分析和测试细节);
-
不可变对象因为有固定不变,可以作为常量来安全使用。
JDK也提供了Collections.unmodifiableXXX方法把集合包装为不可变形式,但我们认为不够好:
-
笨重而且累赘:不能舒适地用在所有想做防御性拷贝的场景;
-
不安全:要保证没人通过原集合的引用进行修改,返回的集合才是事实上不可变的;
public void test3(){
List<Integer> list = Lists.newArrayList(1,2,3);
List<Integer> list1 = Collections.unmodifiableList(list);
System.out.println(list);
// [1, 2, 3]
System.out.println(list1);
// [1, 2, 3]
// list修改,list1也会被修改
list.add(4);
System.out.println(list1);
// [1, 2, 3, 4]
}
-
低效:包装过的集合仍然保有可变集合的开销,比如并发修改的检查、散列表的额外空间,等等。
注意:所有Guava不可变集合的实现都不接受null值。因为谷歌内部调查代码发现,只有5%的情况需要在集合中允许null元素。如果要存储null值,请使用JDK的Collections.unmodifiable方法
创建不可变集合的几个方法:
-
copyOf 方法,如ImmutableSet.copyOf(set);
-
of方法,如ImmutableSet.of(“a”, “b”, “c”)或 ImmutableMap.of(“a”, 1, “b”, 2);
-
Builder工具,如
public static final ImmutableSet<Color> GOOGLE_COLORS =
ImmutableSet.<Color>builder()
.addAll(WEBSAFE_COLORS)
.add(new Color(0, 191, 255))
.build();
copyOf 是很智能和高效的,在特定会避免线性拷贝。下期有机会来分析下它的实现原理。
关联可变集合和不可变集合
可变集合接口 | 属于JDK还是Guava | 不可变版本 |
---|---|---|
Collection | JDK | |
List | JDK | |
Set | JDK | |
SortedSet/NavigableSet | JDK | |
Map | JDK | |
SortedMap | JDK | |
Guava | ||
SortedMultiset | Guava | |
Guava | ||
ListMultimap | Guava | |
SetMultimap | Guava | |
Guava | ||
Guava | ||
Guava |
Multiset继承自JDK中的Collection接口,而不是Set接口,所以可以包含重复元素。可以从以下角度理解:
-
没有元素顺序限制的ArrayList
-
Map<E, Integer>,键为元素,值为计数
Multiset提供像无序的ArrayList的基本操作:
-
add(E)添加单个给定元素
-
iterator()返回一个迭代器,包含Multiset的所有元素(包括重复的元素)
-
size()返回所有元素的总个数(包括重复的元素)
当把Multiset看作Map<E, Integer>时,它也提供了Map的查询操作:
-
count(Object)返回给定元素的计数。
-
entrySet()返回Set<Multiset.Entry>,和Map的entrySet类似。
-
elementSet()返回所有不重复元素的Set,和Map的keySet()类似。
常用方法如下:
方法 | 描述 |
---|---|
count(E) | 给定元素在Multiset中的计数 |
elementSet() | Multiset中不重复元素的集合,类型为Set |
entrySet() | 和Map的entrySet类似,返回Set<Multiset.Entry>, |
add(E, int) | 增加给定元素在Multiset中的计数 |
remove(E, int) | 减少给定元素在Multiset中的计数 |
setCount(E, int) | 设置给定元素在Multiset中的计数,不可以为负数 |
size() | 返回集合元素的总个数(包括重复的元素) |
统计一个词在文档中出现了多少次。
1.传统的做法是这样的
Map<String, Integer> counts = new HashMap<String, Integer>();
for (String word : words) {
Integer count = counts.get(word);
if (count == null) {
counts.put(word, 1);
} else {
counts.put(word, count + 1);
}
}
2.使用Multiset操作:
Multiset<String> multiset = HashMultiset.create();
for (String word : words) {
multiset.add(word);
}
int count = multiset.count("today");
Multimap
通俗来讲,Multimap 是一键对多值的HashMap,类似于 Map<K, List> 的数据结构。
@Test
public void test2() {
Multimap<String, String> multimap = ArrayListMultimap.create();
multimap.put("name", "Jack");
multimap.put("name", "Jack");
multimap.put("name", "Tom");
multimap.put("age", "10");
multimap.put("age", "12");
System.out.println(multimap);
System.out.println(multimap.get("name").size());
}
输出:
{name=[Jack, Jack, Tom], age=[10, 12]}
3
常用操作如下:
方法签名 | 描述 | 等价于 |
---|---|---|
put(K, V) | 添加键到单个值的映射 | multimap.get(key).add(value) |
putAll(K, Iterable) | 依次添加键到多个值的映射 | Iterables.addAll(multimap.get(key), values) |
remove(K, V) | 移除键到值的映射;如果有这样的键值并成功移除,返回true。 | multimap.get(key).remove(value) |
removeAll(K) | 清除键对应的所有值,返回的集合包含所有之前映射到K的值,但修改这个集合就不会影响Multimap了。 | multimap.get(key).clear() |
replaceValues(K, Iterable) | 清除键对应的所有值,并重新把key关联到Iterable中的每个元素。返回的集合包含所有之前映射到K的值。 | multimap.get(key).clear(); Iterables.addAll(multimap.get(key), values) |
主要操作:
-
asMap:为Multimap<K, V>提供Map<K,Collection>形式的视图
-
entries:返回所有”键-单个值映射”,包括重复键。Collection<Map.Entry<K, V>>类型
-
keySet:返回所有不同的键,Set类型
-
keys:用Multiset表示Multimap中的所有键,每个键重复出现的次数等于它映射的值的个数。可以从这个Multiset中移除元素,但不能做添加操作;移除操作会反映到底层的Multimap
-
values:用一个”扁平”的Collection包含Multimap中的所有值,包括重复键
Table
看到table的使用时候真的是眼前一亮,之前的代码中写过很多的Map<String,Map<String,String>> 这种格式的代码,这种阅读起来非常的不友好,甚至都不知道map中的key到底是什么还要联系上下文联想才可以,而table类型的出现彻底解决掉了这个麻烦。
Table类似多个索引的表,类似 Map<R, Map<C, V>> 的数据结构。它有两个支持所有类型的键:”行”和”列”,可以通过以下方法获取多个视图:
-
rowMap():用Map<R, Map<C, V>>表现Table<R, C, V>。同样的, rowKeySet()返回”行”的集合Set。
-
row(r):用Map<C, V>返回给定”行”的所有列,对这个map进行的写操作也将写入Table中。
-
cellSet():用元素类型为Table.Cell的Set表现Table<R, C, V>。Cell类似于Map.Entry,但它是用行和列两个键区分的。
Table支持 row、column、value 我们把上面定义的map结构想象成一张数据表就可以了:
Table<R,C,V> == Map<R,Map<C,V>>
S.N. | 方法 & 描述 |
---|---|
1 | Set<Table.Cell<R,C,V>> cellSet() |
2 | void clear() |
3 | Map<R,V> column(C columnKey) |
4 | Set<C> columnKeySet() |
5 | Map<C,Map<R,V>> columnMap() |
6 | boolean contains(Object rowKey, Object columnKey) |
7 | boolean containsColumn(Object columnKey) |
8 | boolean containsRow(Object rowKey) |
9 | boolean containsValue(Object value) |
10 | boolean equals(Object obj) |
11 | V get(Object rowKey, Object columnKey) |
12 | int hashCode() |
13 | boolean isEmpty() |
14 | V put(R rowKey, C columnKey, V value) |
15 | void putAll(Table<? extends R,? extends C,? extends V> table) |
16 | V remove(Object rowKey, Object columnKey) |
17 | Map<C,V> row(R rowKey) |
18 | Set<R> rowKeySet() |
19 | Map<R,Map<C,V>> rowMap() |
20 | int size() |
21 | Collection<V> values() |
/*
* Company: IBM, Microsoft, TCS
* IBM -> {101:Mahesh, 102:Ramesh, 103:Suresh}
* Microsoft -> {101:Sohan, 102:Mohan, 103:Rohan }
* TCS -> {101:Ram, 102: Shyam, 103: Sunil }
*
* */
//create a table
Table<String, String, String> employeeTable = HashBasedTable.create();
//initialize the table with employee details
employeeTable.put("IBM", "101","Mahesh");
employeeTable.put("IBM", "102","Ramesh");
employeeTable.put("IBM", "103","Suresh");
employeeTable.put("Microsoft", "111","Sohan");
employeeTable.put("Microsoft", "112","Mohan");
employeeTable.put("Microsoft", "113","Rohan");
employeeTable.put("TCS", "121","Ram");
employeeTable.put("TCS", "102","Shyam");
employeeTable.put("TCS", "123","Sunil");
//所有行数据
System.out.println(employeeTable.cellSet());
//所有公司
System.out.println(employeeTable.rowKeySet());
//所有员工编号
System.out.println(employeeTable.columnKeySet());
//所有员工名称
System.out.println(employeeTable.values());
//公司中的所有员工和员工编号
System.out.println(employeeTable.rowMap());
//员工编号对应的公司和员工名称
System.out.println(employeeTable.columnMap());
//row+column对应的value
System.out.println(employeeTable.get("IBM","101"));
//IBM公司中所有信息
Map<String,String> ibmEmployees = employeeTable.row("IBM");
System.out.println("List of IBM Employees");
for(Map.Entry<String, String> entry : ibmEmployees.entrySet()){
System.out.println("Emp Id: " + entry.getKey() + ", Name: " + entry.getValue());
}
//table中所有的不重复的key
Set<String> employers = employeeTable.rowKeySet();
System.out.print("Employers: ");
for(String employer: employers){
System.out.print(employer + " ");
}
System.out.println();
//得到员工编号为102的所有公司和姓名
Map<String,String> EmployerMap = employeeTable.column("102");
for(Map.Entry<String, String> entry : EmployerMap.entrySet()){
System.out.println("Employer: " + entry.getKey() + ", Name: " + entry.getValue());
}
/*
[(IBM,101)=Mahesh, (IBM,102)=Ramesh, (IBM,103)=Suresh, (Microsoft,111)=Sohan, (Microsoft,112)=Mohan, (Microsoft,113)=Rohan, (TCS,121)=Ram, (TCS,102)=Shyam, (TCS,123)=Sunil]
[IBM, Microsoft, TCS]
[101, 102, 103, 111, 112, 113, 121, 123]
[Mahesh, Ramesh, Suresh, Sohan, Mohan, Rohan, Ram, Shyam, Sunil]
{IBM={101=Mahesh, 102=Ramesh, 103=Suresh}, Microsoft={111=Sohan, 112=Mohan, 113=Rohan}, TCS={121=Ram, 102=Shyam, 123=Sunil}}
{101={IBM=Mahesh}, 102={IBM=Ramesh, TCS=Shyam}, 103={IBM=Suresh}, 111={Microsoft=Sohan}, 112={Microsoft=Mohan}, 113={Microsoft=Rohan}, 121={TCS=Ram}, 123={TCS=Sunil}}
Mahesh
List of IBM Employees
Emp Id: 101, Name: Mahesh
Emp Id: 102, Name: Ramesh
Emp Id: 103, Name: Suresh
Employers: IBM Microsoft TCS
Employer: IBM, Name: Ramesh
Employer: TCS, Name: Shyam
*/