1.java.util.Objects类
Object 是 Java 中所有类的基类,位于java.lang包。Objects 是 Object 的工具类,位于java.util包。
java.util.Objects由 Java 7 引入的 ,它由一些静态的实用方法组成,用于操作对象或在操作前检查某些条件。
方法 | 作用 |
---|---|
static boolean equals(Object a, Object b) | 验证两个参数是否相等,是空安全的 |
static boolean deepEquals(Object a, Object b) | 实现了两个对象是否相等的深度判断,如果对象是一个数组,则对数组的每一个元素进行比较;如果数组中的每一个元素仍然是object,则对其进行递归的比较。 |
static int hashCode(Object o) | 返回一个整型数值,表示该对象的哈希码值。若参数对象为空,则返回整数0;若不为空,则直接调用了Object.hashCode方法 |
static int hash(Object... values) | 为一系列的输入值生成哈希码,该方法的参数是可变参数为一系列的输入值生成哈希码 |
static String toString(Object o) | 返回指定对象的字符串表示形式。如果参数为null,则返回字符串“null” |
static String toString(Object o, String nullDefault) | 若第一个参数不是 null ,则返回在第一个参数上调用 toString的结果,否则返回第二个参数 |
static <T> T requireNonNull(T obj) | 检查指定的对象引用是不是null 。若为null,则抛出空指针异常,否则返回对象本身 |
static <T> T requireNonNull(T obj, String message) | 当被校验的参数为null时,根据第二个参数message抛出自定义的NullPointerException |
static boolean isNull(Object obj) | 判空方法,如果参数为null则返回true,否则返回false |
static boolean nonNull(Object obj) | 判断非空方法,若参数是非空,返回true,否则返回false |
2.java.util.Random类
我们希望获得在指定范围内生成整型、长整型或双精度的随机数流,那就需要使用java.util.Random类下定义的ints、longs、doubles方法,这三个方法生成的是随机数流。
通过这三个方法的重载形式,我们可以指定结果流的大小以及生成数的最大值和最小值(这里只列举了ints方法的重载形式,其它两个方法的重载形式类似)。
方法 | 作用 |
---|---|
IntStream ints(long streamSize) | 返回一个指定流大小的随机顺序流 |
IntStream ints(int randomNumberOrigin, int randomNumberBound) | 指定结果流生成数的最小值和最大值 |
IntStream ints(long streamSize, int randomNumberOrigin,int randomNumberBound) | 返回一个指定结果流的大小及生成数最小值和最大值的IntStream |
如果未指定streamSize,方法将返回一个"有效无限流",这里的有效无限相当于Long.MAX_VALUE。
如果不指定最小值 randomNumberOrigin和最大值randomNumberBound,对于doubles方法,最小值默认为0,最大值默认为1;对于ints方法,最小值和最大值默认为整型数据的完整范围;对于longs方法,最小值和最大值默认为长整型数据的有效范围。
参数中的最小值和最大值遵守”闭开原则“。
@Test
public void testRandom(){
Random r = new Random();
//没有指定streamSize,返回无限流
r.ints().limit(5).forEach(System.out::println);
List<Integer> collect = r.ints(5, 0, 100)
.boxed().collect(Collectors.toList());
System.out.println(collect);
//返回0-10的无限整型流
r.ints(0,10).limit(5).forEach(System.out::println);
//返回五个位于 0(包括)到 0.5(不包括)之间的双精度随机数
r.doubles(5,0,0.5).sorted().forEach(System.out::println);
//返回五个随机长整型数
r.longs(5).sorted().forEach(System.out::println);
}
3.java.util.Map接口的默认方法
java8 中 java.util.Map 接口新增默认方法说明:
方法 | 说明 |
---|---|
default V compute(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction) | 根据现有的键和值计算新的值 |
default V computeIfAbsent(K key,Function<? super K, ? extends V> mappingFunction) | 如果键存在,返回对应的值,否则通过提供的函数计算新的值并保存 |
default V computeIfPresent(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction) | 对 hashMap 中指定 key 的值进行重新计算,前提是该 key 存在于 hashMap 中 |
default void forEach(BiConsumer<? super K, ? super V> action) | 对 Map 进行迭代,将所有键和值传递给 BiConsumer |
default V getOrDefault(Object key, V defaultValue) | 如果键在 Map 中存在,返回对应的值,否则返回默认值 |
default V merge(K key, V value,BiFunction<? super V, ? super V, ? extends V> remappingFunction) | 如果键在 Map 中不存在,返回提供的值,否则计算新的值 |
default V putIfAbsent(K key, V value) | 如果键在 Map 中不存在,将其关联到给定的值 |
default boolean remove(Object key, Object value) | 如果键的值与给定的值匹配,删除该键返回true,否则返回false |
default V replace(K key, V value) | 将现有键的值替换为新的值 |
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) | 将 Map 中每个条目的值替换为对当前条目调用给定函数后的结果 |
@Test
public void testMethod(){
HashMap<Object, Object> map = new HashMap<>();
map.put("key","value");
System.out.println("get:"+map.get("key1"));
System.out.println("getOrDefault:"+map.getOrDefault("key1", ""));
System.out.println("初始:"+map);
map.putIfAbsent("key1","value1");//如果键不存在,才会添加该键值对
System.out.println("putIfAbsent后:"+map);
map.remove("key1","value");//参数中的键值对和map中的键值对匹配才执行删除操作
System.out.println("remove后:"+map);
map.replace("key1","value2");
System.out.println("replace后:"+map);
map.compute("key",(key,value) -> value.toString().toUpperCase());
System.out.println("compute后:"+map);
}
//将所有值更改为大写
@Test
public void testReplaceAll(){
HashMap<Integer, String> map = new HashMap<>();
map.put(1,"java");
map.put(2,"javaScript");
map.put(3,"python");
map.put(4,null);
System.out.println("HashMap:"+map);
//replaceAll方法不返回任何值,它用来自函数的新值替换了哈希map原来的值
map.replaceAll((key,value) -> {
if (value != null)
return value.toUpperCase();
return value;
});
System.out.println("Updated HashMap:"+map);
}
//给定一段文本,统计所有单词的出现次数(merge方法)
@Test
public void fullWordsCount(){
HashMap<String, Integer> wordsCount = new HashMap<>();
String passage = "This is a short passage for counting the number of words";
//merge方法的三个参数:key——map中的键,value——使用者传入的值,BiFunction函数式接口——执行自定义功能并返回最终值
//当map中不存在指定key值时,将传入的value设置为该键对应的值;若key值存在,执行后面的方法
//BiFunction函数式接口接受两个参数,分别是key的旧值和传入的value值,完成操作后把返回值设为对应key的新值
Arrays.stream(passage.split("[!._,'@?]|\\s+")).forEach(word -> wordsCount.merge(word,1,(oldvalue,newvalue) -> oldvalue + newvalue));
// Arrays.stream(passage.split("[!._,'@?]|\\s+")).forEach(word -> wordsCount.merge(word,1,Integer::sum));
System.out.println(wordsCount);
}
//仅更新特定单词的出现次数
@Test
public void test02(){
String passage = "this is a passage a a a";
String[] strings = {"is","a"};
HashMap<String, Integer> wordsCount = new HashMap<>();
Arrays.stream(strings).forEach(s -> wordsCount.put(s,0));//将特定单词置于映射中,并初始化次数为0
//computeIfPresent方法有两个参数:key - 键,BiFunction - 重新映射函数,用于重新计算值
//BiFunction接收的两个参数分别时map的键和值,完成操作后将返回值设为对应key的新值
Arrays.stream(passage.split(" ")).forEach(word -> wordsCount.computeIfPresent(word,(key,val) -> val+1));
System.out.println(wordsCount);
}
//给定一个 List<String>,统计每个元素出现的所有位置。
//比如,给定 list: ["a", "b", "b", "c", "c", "c", "d", "d", "d", "f", "f", "g"] ,
//那么应该返回:a : [0] b : [1, 2] c : [3, 4, 5] d : [6, 7, 8] f : [9, 10] g : [11]
@Test
public void getElePos(){
List<String> list = Arrays.asList("a","b","c","a","c","d","f","f","g");
Map<String, List<Integer>> positionsMap1 = new HashMap<>();
//1.put方式
for (int i = 0; i < list.size(); i++) {
String str = list.get(i);
List<Integer> positions = positionsMap1.get(str);
if (positions == null) { // 如果 positionsMap 还不存在 str 这个键及其对应的 List<Integer>
positions = new ArrayList<>(1);
positionsMap1.put(str, positions); // 将 str 及其对应的 positions 放入 positionsMap
}
positions.add(i); // 将索引加入 str 相关联的 List<Integer> 中
}
System.out.println("put方式:"+positionsMap1);
Map<String, List<Integer>> positionsMap2 = new HashMap<>();
//2.putIfAbsent方式
for (int i = 0; i < list.size(); i++) {
String str = list.get(i);
List<Integer> positions = new ArrayList<>(1);
positionsMap2.putIfAbsent(str, positions); // 将 str 及其对应的 positions 放入 positionsMap
positionsMap2.get(str).add(i); // 将索引加入 str 相关联的 List<Integer> 中
}
System.out.println("putIfAbsent方式:"+positionsMap2);
Map<String, List<Integer>> positionsMap3 = new HashMap<>();
//3.computeIfAbsent方式
for (int i = 0; i < list.size(); i++) {
String str = list.get(i);
//computeIfAbsent()方法对 hashMap 中指定 key 的值进行重新计算,如果不存在这个 key,则添加到 hashMap 中,对应值为mappingFunction计算返回的值
//这个方法有两个参数,Key 和一个根据 Key 来产生 Value 的 Function;然后返回一个 Value
List<Integer> positions = positionsMap3.computeIfAbsent(str, k -> new ArrayList<>(1));
positions.add(i); // 将索引加入 str 相关联的 List<Integer> 中
}
System.out.println("computeIfAbsent方式:"+positionsMap3);
}
对集合和映射(Map)进行迭代:使用 java.lang.Iterable 或 java.util.Map 接口中新增的默认方法 forEach。
在 Java8 以前我们使用循环方式对线性集合进行迭代:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
//迭代器方式
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("====================");
//增强for循环方式
for (int i:list) {
System.out.println(i);
}
在 Java8 之后我们使用 java.lang.Iterable 接口中新增默认方法 forEach 方法对线性集合进行迭代。
default void forEach(Consumer<? super T> action)
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
list.forEach(i-> System.out.println(i));
Map接口中同样引入了 forEach 方法作为默认方法对 Map 集合进行迭代,它的方法签名如下:
default void forEach(BiConsumer<? super K, ? super V> action)
在 Java8 以前我们用循环的方式对 Map 集合进行迭代:
Map<String,String> map = new HashMap<>();
map.put("1001","zhangsan");
map.put("1002","lisi");
map.put("1003","wangwu");
map.put("1004","zhaoliu");
Set<String> keys = map.keySet();
for (String key:keys) {
System.out.println(key+":"+map.get(key));
}
在 Java8 之后我们使用 forEach 方法对 Map 集合进行迭代
Map<String,String> map = new HashMap<>();
map.put("1001","zhangsan");
map.put("1002","lisi");
map.put("1003","wangwu");
map.put("1004","zhaoliu");
map.forEach((num,name)-> System.out.println(num+":"+name));
4.默认方法的冲突
默认方法冲突问题 -> 继承的多个接口中有相同的默认方法
因为接口默认方法可以被继承并重写,如果继承的多个接口都存在相同的默认方法,那就存在冲突问题。结构示例:
此时创建接口Pet继承Creature和Animal,编译器出现编译异常:
因为 Pet继承了 Animal 和 Creature 的 show() 方法,两个show 方法又不同,编译器并不知道继承哪一个,此时就需要在 Pet 中重写 show() 方法。
public interface Pet extends Creature,Animal{
@Override
default void show() {
System.out.println("我是宠物");
}
}
默认方法冲突问题 -> 继承的多个接口中有相同的默认方法,多个接口中存在继承关系
如果两个接口(其中一个接口是另一个接口的后代)发生冲突,则后代接口优先;如果两个类(其中一个类是了另一个类的后代)发生冲突,则后代类优先。
默认方法冲突问题 -> 接口的默认方法和类的方法冲突
如果类的方法和接口的默认方法冲突,则类的方法始终优先。
默认方法冲突问题 -> 借由关键字super使用提供的默认方法
public class Dog implements Creature,Animal{
@Override
public void show() {
Creature.super.show();
Animal.super.show();
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.show();
}
}
5.利用Supplier创建日志消息
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。
性能浪费的日志案例:一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下进行打印输出。
public class Demo01Logger {
private static void log(int level, String msg) {
if (level == 1) {
System.out.println(msg);
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, msgA + msgB + msgC);
}
}
这段代码存在问题:无论级别是否满足要求,作为 log 方法的第二个参数,三个字符串一定会首先被拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。
利用Supplier函数式接口优化,对log方法进行改造:
public class Demo02LoggerLambda {
private static void log(int level, Supplier builder) {
if (level == 1) {
System.out.println(builder.buildMessage());
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, () -> msgA+msgB+msgC);
}
}
这样一来,只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接。
使用java.util.logging.Logger类新增的各种日志方法来创建由日志级别控制是否可见的日志消息。它们的各种重载形式如下:
这些方法包括两种重载形式,一种传入单个String作为参数,另一种传入Supplier作为参数(java8引入)。通过调用get()
方法来实现具体对象的计算和生成并返回,而不是在定义Supplier的时候计算,从而达到了_延迟初始化_的目的。
Logger类的实现细节如下:
//无论是否显示info消息,都会创建结果字符串
public void info(String msg) {
log(Level.INFO, msg);
}
public void log(Level level, String msg) {
if (!isLoggable(level)) {
return;
}
LogRecord lr = new LogRecord(level, msg);
doLog(lr);
}
//仅当日志级别显示info消息时,才会调用Supplier的get方法
public void info(Supplier<String> msgSupplier) {
log(Level.INFO, msgSupplier);
}
public void log(Level level, Supplier<String> msgSupplier) {
if (!isLoggable(level)) {
return;
}
LogRecord lr = new LogRecord(level, msgSupplier.get());
doLog(lr);
}
采用相同类型的Supplier来替换参数的技术称为延迟执行,可以在任何对象创建成本较高的上下文中使用。
6.闭包复合
简单理解什么是闭包:
闭包能够将一个方法作为一个变量去存储,这个方法有能力去访问所在类的自由变量。
闭包复合可以理解为函数组合。函数式编程的优点之一在于创建若干简单、可重复使用的函数,将这些函数组合在一起就能解决复杂的问题。
函数组合:创建一些小的可重用函数,然后将这些小函数组合为新函数。
Function、Consumer、Predicate接口中定义了默认方法的复合方法。
Function接口中通过compose 和 andThen 来实现函数的组合
compose 和 andThen 的不同之处是函数执行的顺序不同。compose 函数先执行参数,然后执行调用者,而 andThen 先执行调用者,然后再执行参数,它们二者的方法签名如下:
//compose 函数先执行参数,然后执行调用者
default <V> Function<V, R> compose(Function<? super V, ? extends T> before)
//andThen 先执行调用者,然后再执行参数
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after)
//Function复合
@Test
public void test01(){
Function<Integer,Integer> add2 = x -> x + 2;
Function<Integer,Integer> mult3 = x -> x * 3;
//compose先执行参数然后执行调用者
Function<Integer,Integer> mult3add2 = add2.compose(mult3);//先mult3、再add2
//andThen先执行调用者,再执行参数
Function<Integer,Integer> add2mult3 = add2.andThen(mult3);//先add2、再mult3
System.out.println(mult3add2.apply(1)); //(1*3) + 2
System.out.println(add2mult3.apply(1)); //(1+2) * 3
}
Consumer 接口定义的复合方法 andThen
// andThen 先执行调用者,然后再执行参数
default Consumer<T> andThen(Consumer<? super T> after)
//Consumer复合
//下面的字符串数组当中存有多条信息,请按照格式“ 姓名:XX,性别:XX。 ”的格式将信息打印出来。
private void printInfo(Consumer<String> one, Consumer<String> two, String[] array) {
for (String info : array) {
one.andThen(two).accept(info);
}
}
@Test
public void testPrint(){
//格式化打印数组
String[] array = { "张三,男", "李四,男", "王五,男" };
//要求将打印姓名的动作作为第一个 Consumer 接口的Lambda实例,
//将打印性别的动作作为第二个 Consumer 接口的Lambda实例,将两个 Consumer 接口按照顺序“拼接”到一起。
printInfo(s -> System.out.print("姓名:"+s.split(",")[0]),
s -> System.out.println(",性别:"+s.split(",")[1]),
array);
}
Predicate接口定义的复合方法
//逻辑与
default Predicate<T> and(Predicate<? super T> other)
//逻辑或
default Predicate<T> or(Predicate<? super T> other)
//逻辑非
default Predicate<T> negate()
//谓词复合
@Test
public void test05(){
//判断三角形数
IntPredicate trianular = x -> Math.sqrt(x) % 1 == 0;
//判断完全平方数
IntPredicate perfect = x -> {
double val = (Math.sqrt(8 * x + 1) - 1) / 2;
return val % 1 == 0;
};
IntPredicate both = trianular.and(perfect);
IntStream.rangeClosed(1,1000).filter(both).forEach(System.out::println);
}
//比较器复合
@Test
public void testCompare(){
List<Goods> list = Arrays.asList(
new Goods("mete30", 3999),
new Goods("mete30 pro", 4999),
new Goods("redmi k20", 2999),
new Goods("iqoo", 2999),
new Goods("iphone11", 5000)
);
//按价格排序的比较器
Comparator<Goods> comparatorForPrice = (Goods goods1, Goods goods2) -> {
return Integer.compare(goods1.getPrice(), goods2.getPrice());
};
list.sort(comparatorForPrice);
System.out.println("---------------------按价格排序---------------------");
System.out.println(list);
//按名称排序
Comparator<Goods> comparatorForName = (Goods goods1, Goods goods2) -> {
return goods1.getName().compareTo(goods2.getName());
};
System.out.println("---------------------按名称排序---------------------");
list.sort(comparatorForName);
System.out.println(list);
// 把两个函数式接口进行复合,组成一个新的接口
Comparator<Goods> finalComparator = comparatorForPrice.thenComparing(comparatorForName);
list.sort(finalComparator);
System.out.println("-------------------按价格、名称排序-------------------");
System.out.println(list);
}
7.lambda内部访问局部变量
Java 8 的 Lambda 可以捕获什么变量呢?
(1)捕获实例变量或静态变量是没有限制的;
(2)捕获的局部变量必须显式的声明为 final 或实际效果的的 final 类型。
注意:如果在 Lambda 表达式中使用局部变量,即使我们没有声明成final类型的,编译器也会帮助我们将他们声明成 final 的,所以如果重新赋值会出错。
为什么 Lambda 表达式不能访问非 final 的局部变量呢?
这是 java 为了防止数据不同步而规定的,也就是防止你在 lambda 内使用的外层局部变量被外层代码修改了,但是 lambda 内部无法同步这个修改。
为什么lambda无法感知外层局部变量的变化
那么问题就转移到,为什么你在外层代码修改了变量,lambda 内部会感知不到呢?这个问题就要回到lambda表达式的本质了。
lambda 表达式的本质是什么?是实例化对象,或者再精确一点,就是一个函数式接口的实现的实例。
实例变量:存储在堆中
局部变量:存储在栈中
因此,lambda表达式保存在堆中,而局部变量保存在栈中,可能造成实例对象的生命周期很可能超过局部变量的生命周期:
1.局部变量生命周期:当该方法被调用时,该方法中的局部变量在栈中被创建。当方法调用结束时,退栈,这些局部变量全部销毁。而函数式接口实例对象的生命周期和其他类对象一样,系统为该对象分配内存,知道没有引用变量指向分配该对象的内存,最终被JVM垃圾回收。所以完全可能出现方法调用已结束,局部变量销毁,但实例变量仍然存在的情况。
2.如果实例对象访问了同一个方法中的局部变量,就只要求实例对象还或者,那么栈中的那些它要访问的局部变量就不能销毁。
lambda只是声明,和声明变量是一样的道理
最后要说的一点就是,在写 lambda 表达式的时候,是不会直接去执行这个 lambda 表达式的,lambda 只是一种声明,和声明变量一样,你声明一个int x;仅仅是声明,可能在很多行代码之后才去调用这个 lambda 表达式的执行,例如:
Thread thread = new Thread(() -> System.out.println("call"));
System.out.println("main call");
thread.start();
第一行代码声明了lambda表达式,但此时不执行。第三行代码才会执行lambda表达式。
总结:在Lambda表达式中可以捕获静态变量和实例变量,但是如果想要捕获局部变量的时候就需要声明成final的,即使我们不主动声明,编译器也会为我们自动声明成final的,不能再重新赋值。也就是Lambda表达式中访问的局部变量(隐式被声明为final的)是可读不可写的,但是Lambda表达式中访问的实例变量和静态变量是可读可写的。
8.lambda中异常处理
java 8中引入了lambda表达式,lambda表达式可以让我们的代码更加简介,业务逻辑更加清晰,但是在lambda表达式中使用的函数式接口并没有很好的处理异常,因为JDK提供的这些Functional Interface通常都是没有抛出异常的,这意味着需要我们自己手动来处理异常。
因为异常分为Unchecked Exception和checked Exception,我们分别来讨论。
非受检异常:编译器不检查这类异常是否进行了处理,但由于没有进行异常处理, 一旦运行时异常发生就会导致程序终止。
受检异常:编译器会检查这类异常是否进行了处理, 即要么捕获 (try-catch 语句), 要么抛出 (通过在方法后声明 throws), 否则会发生编译错误。
处理Unchecked Exception(非受检异常)
Unchecked exception也叫做RuntimeException,出现RuntimeException通常是因为我们的代码有问题。RuntimeException是不需要被捕获的。也就是说如果有RuntimeException,没有捕获也可以通过编译。
List<Integer> integers = Arrays.asList(1,2,3,4,5);
integers.forEach(i -> System.out.println(1 / i));
这个例子是可以编译成功的,但是上面有一个问题,如果list中有一个0的话,就会抛出ArithmeticException。
integers.forEach(i -> {
try {
System.out.println(1 / i);
} catch (ArithmeticException e) {
System.err.println("Arithmetic Exception occured : " + e.getMessage());
}
});
上面的例子我们使用了 try,catch 来处理异常,简单但是破坏了 lambda 表达式的最佳实践。代码变得臃肿。
我们将 try,catch 移到一个 wrapper 方法中:
static Consumer<Integer> lambdaWrapper(Consumer<Integer> consumer) {
return i -> {
try {
consumer.accept(i);
} catch (ArithmeticException e) {
System.err.println("Arithmetic Exception occured : " + e.getMessage());
}
};
}
则原来的调用变成这样:
integers.forEach(lambdaWrapper(i -> System.out.println(1 / i)));
但是上面的 wrapper 固定了捕获 ArithmeticException,我们再将其改编成一个更通用的类:
static <T, E extends Exception> Consumer<T> consumerWrapperWithExceptionClass(Consumer<T> consumer, Class<E> clazz) {
return i -> {
try {
consumer.accept(i);
} catch (Exception ex) {
try {
E exCast = clazz.cast(ex);
System.err.println("Exception occured : " + exCast.getMessage());
} catch (ClassCastException ccEx) {
throw ex;
}
}
};
}
上面的类传入一个class,并将其cast到异常,如果能cast,则处理,否则抛出异常。
这样处理之后,我们这样调用:
integers.forEach(
consumerWrapperWithExceptionClass(
i -> System.out.println(1 / i),
ArithmeticException.class));
处理 checked Exception(受检异常)
checked Exception是必须要处理的异常,我们还是看个例子:
return Arrays.stream(values).map(s -> URLEncoder.encode(s,"UTF-8"))
.collect(Collectors.toList());
上面代码的lambda表达式中抛出一个异常UnsupportedEncodingException,这是一个受检异常,需要被处理。lambda表达式内部抛出的异常仍然需要在内部对象而非周围对象中进行处理或声明。
最简单的办法就是try,catch,如下所示:
return Arrays.stream(values).map(s -> {
try {
return URLEncoder.encode(s,"UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return "";
}
}).collect(Collectors.toList());
此外,我们也可以定义一个新的方法负责处理异常:
private String encodeString(String s){
try {
return URLEncoder.encode(s,"UTF-8");
}catch (UnsupportedEncodingException e){
throw new RuntimeException(e);
}
}
这样调用:
Arrays.stream(values).map(this::encodeString).collect(Collectors.toList())
也可以封装一下异常,为了能在map方法中使用可能抛出受检异常的lambda表达式,我们创建一个单独的函数式接口,声明它并抛出Exception。
@FunctionalInterface
public interface FunctionWithException<T,R,E extends Exception> {
//抛出Exception的函数式接口
R apply(T t) throws E;
}
private static <T,R,E extends Exception> Function<T,R> wrapper(FunctionWithException<T,R,E> fe){
return arg -> {
try {
return fe.apply(arg);
}catch (Exception e){
throw new RuntimeException(e);
}
};
}
然后这样调用:
Arrays.stream(values).map(wrapper(s -> URLEncoder.encode(s,"UTF-8")))
.collect(Collectors.toList());
我们还可以根据需要创建其他函数式接口的包装器,如ConsumerWithException,SupplierWithException。