jdk8 + guava杂记
一. 函数式接口
所谓函数式接口,是指有且仅有一个抽象方法的接口,可包含其他default关键字定义的方法。
一般都会使用@FunctionalInterface来注解此接口。
常用的函数接口:
1. Predicate<T> 断言,一般用来判断是否满足某条件
2. Consumer<T> 接收一个参数,无返回值
3. Function<T,R> 接收T对象,返回R对象
4. Supplier<T> 类似工厂,调用时会返回一个指定类型的对象
5. UnaryOperator<T> 执行一元操作(与、或、非)
6. BinaryOperator<T,T> 接收两个参数,返回一个值
示例:
1. Predicate:
Predicate<String> predicate01 = (s) -> s.length() > 7; Predicate<String> predicate02 = (s) -> s.contains("s"); assertTrue(predicate01.test("abcdefgh")); //true assertTrue(predicate01.and(predicate02).test("abcdefgh")); //false assertTrue(predicate01.and(predicate02).negate().test("abcdefgh")); //true assertTrue(predicate01.and(predicate02).test("abcdefghs")); //true assertTrue(predicate01.or(predicate02).test("abcdefgh")); //true assertTrue(predicate01.or(predicate02).test("abs")); //true assertTrue(Predicate.isEqual("asd").test("asd"));
2. Consumer: 无返回值
Consumer<String> c = s -> System.out.println(s.toUpperCase()); c.accept("asdf"); //ASDF
3. JDK中的Supplier接口,仅包含一个get()的抽象方法
在guava中为Supplier类,提供更多的方法:
如:每隔2分钟刷新一次delegate中的缓存
Supplier<T> supplier = Suppliers.memoizeWithExpiration(t, 2, TimeUnit.SECONDS);
4. UnaryOperator: 返回自身
一元操作,即只有一个操作数,既是源又是目的,常用的如a++等类似的操作。即接收自己返回自己。
UnaryOperator<String> str = s -> s.toUpperCase(); System.out.println(str.apply("asdf")); //ASDF
6. BinaryOperator
BinaryOperator<Integer> bo = (x,y) -> (x*y); System.out.println(bo.apply(1, 7)); //7
二、 目标类型的推断
即有时候不需要再去显式的声明泛型的参数类型,但程序依然需要经过类型检查来保证运行的安全,采用的是javac根据Lambda表达式的上下文进行推测得出的,
方法:public void save(Map<String, String> map){}; 调用:save(new HashMap<>()); //此处没有指定泛型的类型,而是根据上下文推测得出
在方法重载的情况下,当lambda表达式作为参数时,推导其目标类型所遵循的规则:
1. 若只有一个可能的目标,则根据相应函数接口里的参数类型推导而出
2. 若有多个可能的目标类型,则由具体的那个类型推导而出。如String和Object中则推断为String类型
3. 若存在多个可能的目标类型,且具体的类型不明确,则需要人为指定目标类型
三、 jdk + guava中迭代器的区别大致描述
1) jdk中的迭代器(全为接口类型)
public interface Iterable<T>: 为一个接口
拥有的方法:
Iterator<T> iterator(); 返回一个Iterator迭代器 default void forEach(Consumer<? super T> action): 循环
public interface Iterator<E>: 也为一个接口
拥有的方法:
boolean hasNext(); 检测是否有下一个元素 E next(); 获取下一个元素 default void remove(): 移除 default void forEachRemaining(Consumer<? super E> action): 循环
public interface Collection<E> extends Iterable<E>: 集合类接口
拥有方法:
int size(); boolean isEmpty(); Iterator<E> iterator(); boolean contains(Object o); boolean add(E e); boolean remove(Object o); default boolean removeIf(Predicate<? super E> filter)
2) guava中的迭代器
public final class Iterables: 为一个类
常用方法:
(1). public static <T> boolean addAll(Collection<T> addTo, Iterable elementsToAdd):
Collection为jdk中的接口,Iterable也为jdk中的接口,即其内部也是通过
while (iterator.hasNext()) {addTo.add(iterator.next());}来迭代实现操作的。
(2). public static Iterable filter(Iterable<T> unfiltered, Predicate<? super T> predicate):
通过指定条件过滤出集合中的元素,其内部也是通过while + if中的断言条件来进行过滤。
需要说明的是在filter中返回了一个AbstractIterator类对象。
public static Iterator<String> skipNulls(final Iterator<String> in) { return new AbstractIterator<String>() { protected String computeNext() { while (in.hasNext()) { String s = in.next(); if (s != null) { return s; } } return endOfData(); } }; }}
(3). public static <T> boolean any(Iterable<T> iterable, Predicate<? super T> predicate)
迭代器中至少有一个元素满足条件才返回true,则否返回false
(4). public static <T> boolean all(Iterable<T> iterable, Predicate<? super T> predicate)
而all表示地阿尔代其中每一个元素都需要满足条件才会返回true,否则返回false
(5). public static <T> T find(Iterable<T> iterable, Predicate<? super T> predicate)
查找满足条件中的第一个元素
(6). public static <T> T find(
Iterable<? extends T> iterable, Predicate<? super T> predicate, @Nullable T defaultValue)
查找满足条件中的第一个元素,当元素为Null时返回默认值
(7). public static <T> Optional<T> tryFind(Iterable<T> iterable, Predicate<? super T> predicate)
查找满足条件中的第一个元素,当元素为Null时返回Optional.<T>absent();而不至于由于返回Null而报错。
(8). public static <T> int indexOf(Iterable<T> iterable, Predicate<? super T> predicate)
返回当前迭代的元素中,满足条件的那个元素的位置
(9). public static <F, T> Iterable<T> transform(
final Iterable<F> fromIterable, final Function<? super F, ? extends T> function)
将集合中的迭代器经过function接口滤后
示例:
List<String> views = Lists.newArrayList("wsbs","xwzx","bmfw","wshd"); Iterables.limit(views.iterator(), 3); //此处编译报错是因为,此方法接收的参数为Iterable接口,而非Iterator类。其内部实现也是通过Iterators.limit(iterable.iterator(), limitSize);来操作的 Iterators.limit(views.iterator(), 3);
四、 外部迭代 + 内部迭代的区别
外部迭代即Iterators.limit(views.iterator(), 3);形式的迭代
而内部迭代则是通过Stream的方式来进行的迭代,整个迭代过程是在内部层实现的。
views.stream().filter(view -> view.equals("xwzx")) //惰性求值 views.stream().filter(view -> view.equals("xwzx")).count(); //及早求值 views.stream().filter(view -> view.equals("xwzx")).collect(Collectors.toList()); //及早
当返回值为一个Stream时属于惰性求值,而当返回另一个值或为空时为及早求值。
五、 常用的流操作
1、 stream的创建方式:
(1). 通过Stream.of()创建,如:
Stream.of("1a", "2w", "3w", "4e");
Stream.of(Lists.newArrayList("1a", "2w", "3w", "4e")); //也可传入一个数组
(2). generator生成无限长度流
Stream.generate(Math::random).limit(5);
(3). iterate生成流,同generator一样都是无限长度的
//xiaomiXIAOMI,其中xiaomi为原始的str
Stream.iterate(str, item -> item.toUpperCase()).limit(7).distinct().
forEach(System.out::print);
(4). 也可将数组转换成stream,Arrays.stream(数组),如:
String[] numbers = {"wsbswsbs","xwzxzw","bmfwb","wshdwsh"};
Arrays.stream(numbers);
(5). 创建空的stream,如:
Stream.empty()
(6). 将集合类直接转成流,如list.stream(), list.parallelStream()用于并发
2.、stream的转换,类似于jquery的懒加载,访问时才进行动态计算。相比集合类来说,不用存储在内存中,可以少消耗资源。
(1). stream.collect(Collectors.toList()):
将流收集成一个list集合
(2). map:
用于将一种类型的值转换成另一种类型,将一个流转换成一个新的流
views.stream().map(str -> str.charAt(1)).collect(Collectors.toList())
.forEach(System.out::println);
其还有三个扩展的方法:
mapToInt: 将原始流中的每个对象转换成int类型,如:取其长度...
mapToLong: 同上类似
mapToDouble: 同上类似
(3). filter: 过滤出符合条件的元素
(4). flatMap: 当需要同时处理多个集合时可使用
List<String> together = Stream.of(views, Lists.newArrayList("1a", "2w", "3w", "4e"))
.flatMap(List::stream) //将多个集合转成流,并将底层元素抽出放在一起
.filter(str -> str.contains("w")) //此处可直接使用String是因为上一步
.collect(Collectors.toList());
together.forEach(System.out::println);
(5). distinct(),去除重复
(6). peek: 生成一个包含原Stream的所有元素的新Stream,同时会提供一个Consumer函数,
即可在不影响stream正常运行的状态下还可执行其他逻辑,如记录流中产生的数据到日志系统中。
(7). limit: 获取指定长度的流数据,同Iterators.limit原理一样,不用担心limit长度超过原始长度。
(8). skip: 即将流中的前n个元素跳过不要,若n大于原始流中数据的长度,则返回空的流(Stream.empty())
或许可以使用.skip(n).limit(n)的方式来做假分页,一般用于排序之前。
3、 Stream聚合
(1). max: 最大值; min: 最小值。
String string = Lists.newArrayList("1a7890", "2w", "3w", "4e").stream()
.max(Comparator.comparing(String::length)).get();
System.out.println(string); //1a7890
(2). reduce: 适合返回单个结果的情况
//需要两个参数:
//1. identity 即初始值
//2. accumulator 累加器,其中有需要两个参数,如下的(sum, number)
public int addUP(Stream<Integer> numbers){
return numbers.reduce(0, (sum, number) -> sum + number);
//此处为累加,在不同业务上也可直接使用sum(),如下:
// Integer totalAge = roster.stream().mapToInt(Person::getAge).sum();
}
//获取平均数
list.stream().filter(p -> p.getGender() == Person.Sex.MALE)
.mapToInt(Person::getAge).average().getAsDouble();
//sum、min、max、average、字符串拼接(String::concat)都属于reduce的范畴
//摘自官方文档
The reduce operation always returns a new value. However, the accumulator function also
returns a new value every time it processes an element of a stream. Suppose that you want
to reduce the elements of a stream to a more complex object, such as a collection. This
might hinder the performance of your application.
可知,reduce操作每处理一个元素总是创建一个新值,在处理集合时不可能每次都去创建新集合,因此在这种情况下不可取
(3). collect: 收集器,处理各种复杂的搜索
参考: http://docs.oracle.com/javase/tutorial/collections/streams/reduction.html
六、 收集器
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
包含3个参数:
supplier: 使用lambda表达式或方法引用来构造一个新的容器
accumulator: 累加器,将Stream中的元素添加到容器中,无返回值
combiner: 组合器,无返回值。Stream在执行过程中,因为是lazy模式,在最后一步聚合之前的每一步操作都是暂时放在一个map容器中,组合器就是用来将中间状态的多个结果合并成为一个。
中间过程包含如下:
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel(并行)、
sequential、 unordered
最终过程:
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、
noneMatch、 findFirst、 findAny、 iterator
1. 转换成其他集合
stream.collec(Collectors.toList())
stream.collec(Collectors.toSet())
stream.collec(Collectors.toCollection(ArrayList::new))
注:Collectors类中包含很多如下的static方法
2. 转换成值:maxBy、minBy
List<String> views = Lists.newArrayList("wsbs","xafaswzx","b8fw","ad");
Optional<String> res = views.stream().collect(Collectors
.minBy(Comparator.comparing(String::length)));
System.out.println(res.get()); //ad
minBy的最终实现: (a, b) -> comparator.compare(a, b) <= 0 ? a : b
maxBy的最终实现: (a, b) -> comparator.compare(a, b) >= 0 ? a : b
3. 数据分块: partitioningBy
List<String> views = Lists.newArrayList("wsbs","1232","b8fw","wsad");
Map<Boolean, List<String>> res = views.stream().collect(Collectors
.partitioningBy(str -> str.startsWith("ws")));
//{false=[1232, b8fw], true=[wsbs, wsad]}
4. 数据分组: groupingBy
List<String> views = Lists.newArrayList("wsbs","xafaswzx","b8fw","ad");
Map<Integer, List<String>> res = views.stream().collect(Collectors
.groupingBy(String::length));
//{2=[ad], 4=[wsbs, b8fw], 8=[xafaswzx]}
5. 字符串
views.stream().collect(Collectors.joining(",", "[", "]")); //wsbs,xwzx,bmfw,wshd
Collectors.joining中的参数为:连接符,前缀,后缀
6. 组合收集器: 同时使用多个收集器
7. Match匹配
(1). allMatch:Stream 中全部元素符合传入的 predicate,返回 true
(2). anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
(3). noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
List<String> views = Arrays.asList("wsbs","xwzx","bmfw","wshd");
views.stream().allMatch(str -> str.length()>5); //false
views.stream().anyMatch(str -> str.length()>5); //false
views.stream().noneMatch(str -> str.length()>5); //true
8. findFirst: 返回 Stream 的第一个元素,或者空
9. parallel(并行):
使用Parallelism()来转换,其本质也是构建于Fork/Join的基础之上
Fork/Join原理(JDK7):
1. 把问题分解为子问题
2. 串行解决子问题从而得到部分结果(partial result)
3. 合并部分结果合为最终结果
10. Comparator.comparing()比较器:
在一次比较完成后,还诶通过thenComparing进行再次比较。
11. mapping:
引自文档: ...mapping(Person::getLastName, toSet())
即mapping操作可将左参数(lambda表达式)产生的数据动态记录到右参数(集合)中,其内部实现也是通过
工厂容器 + 累加器 + 组合器来实现的。
12. summingInt: 将流中的数据转换成整型后求和
Integer resInt = views.stream().collect(Collectors.summingInt(String::length));
System.out.println(resInt); //15
若为summingLong,同样输出15
若为summingDouble,则输出15.0
13. averagingInt: 将流中的数据转换成整型后求平均数
Double resInt = views.stream().collect(Collectors.averagingInt(String::length));
System.out.println(resInt); //3.75
averagingDouble + averagingLong类似。
参考: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html
七、 使用stream执行某段逻辑,有时候可以逆向推导
如:找出一张专辑中所有长度大于1分钟的曲目名称。
1. 使用collect收集所有曲目名称
2. 从map中找出所有曲目名称
3. filter出所有长度大于1分钟的曲目
4. 获取专辑中所有的曲目,并转换成stream
八、 可使用lambda用作日志打印
Logger logger = new Logger();
logger.debug(() -> System.out.pringln("debug..."))
如业务需求,可使用peek方法将流中产生的数据记录在日志中
九、 基本类型
基本类型: int、long...
装箱:即将基本类型转换成对应的装箱类型(对象)。
拆箱:即将装箱类型转换成对应的基本类型。
命名规则:
(1). 若方法返回类型为基本类型,则在基本类型前加To。如:ToLongFunction -> 返回long类型
(2). 若参数为基本类型,则不需要添加前缀,直接类型名即可。如:LongFunction(long)
(3). 若高阶函数为基本类型,则需要添加后缀To再加基本类型。如:mapToInt
summaryStatistics()为对应Stream接口中对应的抽象方法,所返回的类型可以计算统计值
类似的返回类型:IntSummaryStatistics,DoubleSummaryStatistics,LongSummaryStatistics
示例:
List<String> views = Lists.newArrayList("wsbs","xwzx","bmfw","wshd");
IntSummaryStatistics intstream = views.stream().mapToInt(str -> str.length()).summaryStatistics();
System.out.println(intstream.getMax()); //4
System.out.println(intstream.getMin()); //4
System.out.println(intstream.getSum()); //16
System.out.println(intstream.getCount()); //4
System.out.println(intstream.getAverage()); //4.0
十、 接口中使用default关键字定义方法:
同时实现多个有default定义方法的接口
1. 接口中由default定义的方法没有方法体,实现类不需要重写,直接调用即可
2. 接口中非default定义的方法必须有方法体,且实现类需要重写此方法
//无论函数接口或非函数接口都可使用该形式来定义方法
default void chat(String str){
System.out.println("iphone chat..." + str);
}
public void chat(){
Iphone.super.chat("test"); //需要指定调用哪个父类的方法,此处调用Iphone类的方法
System.out.println("test...");
IMac.super.chat();
}
注意:
(1). 若子类中重写了父类的默认方法,在调用时则使用子类的重写方法,而不是父类中的默认方法。
(2). 若子接口继承了父接口,且两者都拥有同样的默认方法,则实现类(未重写)在调用时使用子接口中的默认方法
十一、 Optional 为null而生
可使用Optional.of()来创建Optional对象。
Optional obj = Optional.of("test"); //参数为null,则抛空指针异常
System.out.println(obj.get()); //test
//orElse表示当原值存在时则返回原值,若不存在则返回参数值
System.out.println(empty.orElse("t")); //t
System.out.println(obj.orElse("t")); //test
十二、 方法引用
格式:ClassName::methodName (静态方法 + 实例方法 都可使用)
super::methodName (超类上的实例方法使用)
Class::new (创建新实例时可使用)
TypeName[]::new (创建新数组时可用,但感觉guava中创建集合的方式更好些)
示例:persons.stream().forEach(Person::getName);
十三、 元素顺序
有序集合:List、LinkedList、LinkedHashMap
无序集合:Set、HashSet、Map、HashMap、TreeMap
(1). filter、map、reduce在有序流上效率执行高些。
(2). 对于在有序流中消耗内存大的操作,可调用stream().unordered()方法来消除顺序。
(3). 需要注意的是,在并行流时,foreach不能保证按顺序执行,可使用stream().forEachOrdered方式
十四、 JDK中的StringJoiner类
常用方法:
(1). public StringJoiner(CharSequence delimiter, CharSequence prefix,
CharSequence suffix) //参数:连接符,前缀,后缀
(2). public StringJoiner setEmptyValue(CharSequence emptyValue) //当出现空值时使用
(3). public StringJoiner add(CharSequence newElement) //连接
(4). public StringJoiner merge(StringJoiner other) //合并容器
【StringBuilder + StringJoiner + Joiner的对比: 各有自己可用的时候】
1. StringBuilder可逆序
new StringBuilder().append("xiaomi").reverse(); //imoaix
2. StringJoiner可直接添加前后缀
new StringJoiner(",", "[", "]").add("xiaomi").add("apple").add("yota2"); //[xiaomi,apple,yota2]
3. Joiner属于guava中的类,连接时可直接传入迭代器
Joiner.on(",").skipNulls().join(Lists.newArrayList("a",null,"c").iterator()); //a,c