1、使用 Stream 简化集合操作
Java8 Stream流操作总结_出世&入世的博客-CSDN博客
2、使用 Optional 简化判空逻辑
空指针异常(NullPointerExceptions
)是 Java 最常见的异常之一,一直以来都困扰着 Java 程序员。一方面,程序员不得不在代码中写很多null
的检查逻辑,让代码看起来非常臃肿;另一方面,由于其属于运行时异常,是非常难以预判的。
为了预防空指针异常,Google
的Guava
项目率先引入了Optional
类,通过使用检查空值的方式来防止代码污染,受到Guava
项目的启发,随后在Java 8
中也引入了Optional
类。
Optional 类位于java.util
包下,是一个可以为 null
的容器对象,如果值存在则isPresent()
方法会返回 true
,调用 get()
方法会返回该对象,可以有效避免空指针异常。
2.1、创建Optional对象
empty()、of()、ofNullable()
//可以使用静态方法 empty() 创建一个空的 Optional 对象
Optional<String> empty = Optional.empty();
System.out.println(empty); // 输出:Optional.empty
//可以使用静态方法 of() 创建一个非空的 Optional 对象
Optional<String> opt = Optional.of("沉默王二");
System.out.println(opt); // 输出:Optional[沉默王二]
//当然了,传递给 of() 方法的参数必须是非空的,也就是说不能为 null,否则仍然会抛出 NullPointerException。
String name = null;
Optional<String> optnull = Optional.of(name);
//可以使用静态方法 ofNullable() 创建一个即可空又可非空的 Optional 对象
String name = null;
Optional<String> optOrNull = Optional.ofNullable(name);
System.out.println(optOrNull); // 输出:Optional.empty
2.2、判断值是否存在
isPresent(); isEmpty()
//可以通过方法 isPresent() 判断一个 Optional 对象是否存在,如果存在,该方法返回 true,否则返回 false——取代了 obj != null 的判断。
Optional<String> opt = Optional.of("沉默王二");
System.out.println(opt.isPresent()); // 输出:true
Optional<String> optOrNull = Optional.ofNullable(null);
System.out.println(opt.isPresent()); // 输出:false
//Java 11 后还可以通过方法 isEmpty() 判断与 isPresent() 相反的结果。
Optional<String> opt = Optional.of("沉默王二");
System.out.println(opt.isPresent()); // 输出:false
Optional<String> optOrNull = Optional.ofNullable(null);
System.out.println(opt.isPresent()); // 输出:true
2.3、非空表达式
ifPresent(),允许我们使用函数式编程的方式执行一些代码,因此,我把它称为非空表达式。如果没有该方法的话,我们通常需要先通过 isPresent() 方法对 Optional 对象进行判空后再执行相应的代码:
Optional<String> optOrNull = Optional.ofNullable(null);
if (optOrNull.isPresent()) {
System.out.println(optOrNull.get().length());
}
有了 ifPresent()
之后,情况就完全不同了,可以直接将 Lambda 表达式传递给该方法,代码更加简洁,更加直观。
Optional<String> opt = Optional.of("沉默王二");
opt.ifPresent(str -> System.out.println(str.length()));
//Java 9 后还可以通过方法 ifPresentOrElse(action, emptyAction) 执行两种结果,非空时执行 action,空时执行 emptyAction。
Optional<String> opt = Optional.of("沉默王二");
opt.ifPresentOrElse(str -> System.out.println(str.length()), () -> System.out.println("为空"));
2.4、设置(获取)默认值
有时候,我们在创建(获取) Optional 对象的时候,需要一个默认值,orElse() 和 orElseGet() 方法就派上用场了。
orElse() 方法用于返回包裹在 Optional 对象中的值,如果该值不为 null,则返回;否则返回默认值。该方法的参数类型和值得类型一致。
String nullName = null;
String name = Optional.ofNullable(nullName).orElse("沉默王二");
System.out.println(name); // 输出:沉默王二
orElseGet() 方法与 orElse() 方法类似,但参数类型不同。如果 Optional 对象中的值为 null,则执行参数中的函数。
String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(()->"沉默王二");
System.out.println(name); // 输出:沉默王二
orElseGet性能更好一点,因为无论Optional 对象中的值是不是为空,orElse都会执行,只是不返回而已。但是通常这里的逻辑不会很复杂,所以影响不大。
2.5、获取值
直观从语义上来看,get() 方法才是最正宗的获取 Optional 对象值的方法,但很遗憾,该方法是有缺陷的,因为假如 Optional 对象的值为 null,该方法会抛出 NoSuchElementException 异常。这完全与我们使用 Optional 类的初衷相悖。
public class GetOptionalDemo {
public static void main(String[] args) {
String name = null;
Optional<String> optOrNull = Optional.ofNullable(name);
System.out.println(optOrNull.get());
}
}
建议 orElseGet()
方法获取 Optional 对象的值。
2.6、filter 、map
-
filter() 方法的参数类型为 Predicate(Java 8 新增的一个函数式接口),也就是说可以将一个 Lambda 表达式传递给该方法作为条件,如果表达式的结果为 false,则返回一个 EMPTY 的 Optional 对象,否则返回过滤后的 Optional 对象。
-
map() 方法,该方法可以按照一定的规则将原有 Optional 对象转换为一个新的 Optional 对象,原有的 Optional 对象不会更改。
public class OptionalMapFilterDemo {
public static void main(String[] args) {
String pass word = "pass word";//测试时pass和word合在一起
Optional<String> opt = Optional.ofNullable(pass word);//测试时pass和word合在一起
Predicate<String> len6 = pwd -> pwd.length() > 6;
Predicate<String> len10 = pwd -> pwd.length() < 10;
Predicate<String> eq = pwd -> pwd.equals("pass word");//测试时pass和word合在一起
boolean result = opt.map(String::toLowerCase).filter(len6.and(len10 ).and(eq)).isPresent();
System.out.println(result);
}
}
总结:使用 Optional,不仅可以避免使用 Stream 进行级联调用的空指针问题;更重要的是,它提供了一些实用的方法帮我们避免判空逻辑。
如下是一些例子,演示了如何使用 Optional 来避免空指针,以及如何使用它的 fluent API 简化冗长的 if-else 判空逻辑:
@Test(expected = IllegalArgumentException.class)
public void optional() {
//通过get方法获取Optional中的实际值,很少用到
assertThat(Optional.of(1).get(), is(1));
//通过ofNullable来初始化一个null,通过orElse方法实现Optional中无数据的时候返回一个默认值
assertThat(Optional.ofNullable(null).orElse("A"), is("A"));
//OptionalDouble是基本类型double的Optional对象,isPresent判断有无数据
assertFalse(OptionalDouble.empty().isPresent());
//通过map方法可以对Optional对象进行级联转换,不会出现空指针,转换后还是一个Optional
assertThat(Optional.of(1).map(Math::incrementExact).get(), is(2));
//通过filter实现Optional中数据的过滤,得到一个Optional,然后级联使用orElse提供默认值
assertThat(Optional.of(1).filter(integer -> integer % 2 == 0).orElse(null), is(nullValue()));
//通过orElseThrow实现无数据时抛出异常
Optional.empty().orElseThrow(IllegalArgumentException::new);
}
3、Map接口 Map接口 putIfAbsent / computeIfAbsent/computeIfPresent
3.1、putIfAbsent
如果给定的key不存在(或者key对应的value为null),关联给定的key和给定的value,并返回null;如果存在,返回当前值(不会把value放进去);
使用场景:如果我们要变更某个key的值,我们又不知道key是否存在的情况下,而又不希望增加key的情况使用。
demo:
// 创建一个 HashMap
HashMap<Integer, String> sites = new HashMap<>();
// 往 HashMap 添加一些元素
sites.put(1, "Google");
sites.put(2, "Runoob");
sites.put(3, "Taobao");
System.out.println("sites HashMap: " + sites);
// HashMap 不存在该key
sites.putIfAbsent(4, "Weibo");
// HashMap 中存在 Key
sites.putIfAbsent(2, "Wiki");
System.out.println("Updated Languages: " + sites);
//结果为
sites HashMap: {1=Google, 2=Runoob, 3=Taobao}
Updated sites HashMap: {1=Google, 2=Runoob, 3=Taobao, 4=Weibo}
3.2、computeIfAbsent
如果给定的 key 不存在(或者 key 对应的 value 为 null),就去计算 mappingFunction 的值;
如果 mappingFunction 的值不为 null,就把 key = value 放进去;
如果 mappingFunction 的值为 null,就不会记录该映射关系,返回值为 null;
如果计算 mappingFunction 的值的过程出现异常,再次抛出异常,不记录映射关系,返回 null;
如果存在该 key,返回的是新的 value,就算 key 对应的 value 部位 null,返回 null;(HashMap 返回的是旧 value)
default V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)
map.computeIfAbsent(key, k -> new Value(f(k)));
map.computeIfAbsent(key, k -> new HashSet<V>()).add(v);
demo:
public void test() {
Map<String, List<String>> map = new HashMap<>();
// 如果key不存在,则创建新list并放入数据;key存在,则直接往list放入数据
map.computeIfAbsent("fruit", k -> new ArrayList<>()).add("apple");
map.computeIfAbsent("fruit", k -> new ArrayList<>()).add("orange");
map.computeIfAbsent("language", k -> new ArrayList<>()).add("english");
// 遍历
map.forEach((k, v) -> System.out.println(k + " " + v));
}
map.computeIfAbsent("fruit", k -> new ArrayList<>()).add("apple");
这一句的意思是
key为fruit的键值对是否存在
1、如果存在,则给当前操作的List集合添加 apple
2、如果不存在,则新创建List结构,将apple添加到List集合,再存入Map集合
putIfAbsent 和 computeIfAbsent
- 都是在 key 不存在的时候才会建立 key 和 value 的映射关系;
- putIfAbset 不论传入的 value 是否为空,都会建立映射(并不适合所有子类,例如 HashTable),而computeIfAbsent 方法,当存入value为空时,不做任何操作
- putIfAbsent返回的是旧value,当key不存在时返回null;computeIfAbsent返回的都是新的value,即使computeIfAbsent在传入的value为null时,不会新建映射关系,但返回的也是null;
3.3、computeIfPresent
key 存在并且不为空,计算 remappingFunction 的值 value;
如果 value 不为空,保存指定 key 和 value 的映射关系;
如果 value 为 null,remove(key);
如果计算 value 的过程抛出了异常,computeIfPresent 方法中会再次抛出,key 和其对应的值不会改变
default V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)
demo:
// 创建一个 HashMap
HashMap<String, Integer> prices = new HashMap<>();
// 往HashMap中添加映射关系
prices.put("Shoes", 200);
prices.put("Bag", 300);
prices.put("Pant", 150);
System.out.println("HashMap: " + prices);
// 重新计算鞋加上10%的增值税后的价值
int shoesPrice = prices.computeIfPresent("Shoes", (key, value) -> value + value * 10/100);
System.out.println("Price of Shoes after VAT: " + shoesPrice);
// 输出更新后的HashMap
System.out.println("Updated HashMap: " + prices);
执行以上程序输出结果为:
HashMap: {Pant=150, Bag=300, Shoes=200}
Price of Shoes after VAT: 220
Updated HashMap: {Pant=150, Bag=300, Shoes=220}}
computeIfPresent 和 computeIfAbsent
-
这两个方法正好相反,computeIfPresent 在key存在时,才会用新的value替换oldValue
-
当传入的key存在,并且传入的value为null时,computeIfPresent 会remove(key),把传入的key对应的映射关系移除;而computeIfAbsent不论何时都不会remove();
-
computeIfPresent 只有在key存在,并且传入的value不为空的时候,返回值是value,其他情况都是返回null;
computeIfAbsent 只有在key不存在,并且传入的value不为null的时候才会返回value,其他情况都返回null;
4、函数式接口 Lambda 表达式
函数式接口是一种只有单一抽象方法的接口,使用 @FunctionalInterface 来描述,可以隐式地转换成 Lambda 表达式。使用 Lambda 表达式来实现函数式接口,不需要提供类名和方法定义,通过一行代码提供函数式接口的实例,就可以让函数成为程序中的头等公民,可以像普通数据一样作为参数传递,而不是作为一个固定的类中的固定方法。
那,函数式接口到底是什么样的呢?java.util.function 包中定义了各种函数式接口。
比如,用于提供数据的 Supplier 接口,就只有一个 get 抽象方法,没有任何入参、有一个返回值:
@FunctionalInterfacepublic interface Supplier {
/** * Gets a result. * * @return a result */
T get();
}
我们可以使用 Lambda 表达式或方法引用,来得到 Supplier 接口的实例:
//使用Lambda表达式提供Supplier接口实现,返回OK字符串
Supplier stringSupplier = ()->"OK";
//使用方法引用提供Supplier接口实现,返回空字符串
Supplier supplier = String::new;
4.1、四大基本函数式接口
Function<T, R>
接受一个入参T,返回R类型对象,使用apply方法获取方法执行的内容
R apply(T t);
demo1:
//Function接口是输入一个数据,计算后输出一个数据。我们先把字符串转换为大写,然后通过andThen组合另一个Function实现字符串拼接
Function<String, String> upperCase = String::toUpperCase;
Function<String, String> duplicate = s -> s.concat(s);
upperCase.andThen(duplicate).apply("test");
demo2:
User user = new User(88, "bb");
String name = uft.apply(user);
System.out.println(name);
/**
* Function<T, R> lambda写法
*/
private static Function<User, String> uft = u -> u.getName();
Consumer<T>
接受一个参数T,没有返回值,使用accept方法对参数执行操作
oid accept(T t);
demo1:
//Consumer接口是消费一个数据。我们通过andThen方法组合调用两个Consumer,输出两行abcdefg
Consumer<String> println = System.out::println;
println.andThen(println).accept("abcdefg");
demo2:
User user = new User(88, "bb");
uc.accept(user);
/**
* Consumer<T> lambda写法
*/
private static Consumer<User> uc = u -> System.out.println(u.getName());
Supplier<T>
没有入参,返回T类型结果,使用get方法获取返回结果
T get();
demo1:
//Supplier是提供一个数据的接口。这里我们实现获取一个随机数
Supplier<Integer> random = ()->ThreadLocalRandom.current().nextInt();
System.out.println(random.get());
demo2:
User user1 = us.get();
System.out.println(user1.getName());
/**
* Supplier<T> lambda写法
*/
private static Supplier<User> us = () -> new User(1, "us");
Predicate<T>
接受一个入参,返回结果为true或者false,使用test方法进行测试并返回测试结果
boolean test(T t);
demo1:
//Predicate接口是输入一个参数,返回布尔值。我们通过and方法组合两个Predicate条件,判断是否值大于0并且是偶数
Predicate<Integer> positiveNumber = i -> i > 0;
Predicate<Integer> evenNumber = i -> i % 2 == 0;
assertTrue(positiveNumber.and(evenNumber).test(2));
demo2:
boolean test = up.test(user);
System.out.println(test);
/**
* Predicate<T>
*/
private static Predicate<User> up = u -> !u.getName().isEmpty();
4.2、自定义函数式接口
参见Java 函数式编程实例_出世&入世的博客-CSDN博客