欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

jdk8 + guava杂记

程序员文章站 2022-04-08 18:41:51
...

一. 函数式接口

所谓函数式接口,是指有且仅有一个抽象方法的接口,可包含其他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

                       

      

 

 

 

 

 

  

 

 

 

 

 

 

 

 

相关标签: jdk8 guava