`

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

                       

      

 

 

 

 

 

  

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics