java8 API (一) (String、正则、IO、集合)

前言

目的

  • java8自14年发布已经过去2年多了,越来越多的应用,开始升级到java8,升级不是目的,目的是升级之后,我们就可以使用java8新增的特性,从而提高开发效率、性能、稳定性等。所以伴随着升级,在团队内分享了java8的新特性,不过光了解新特性,知道lambda是啥,真正实践的时候还是有些迷茫(我自己现在就是这样),所以最近开始慢慢的整理java8新增的api,一方面字节在学习,另一方面通过例子的形式,希望可以帮助大家了解新增的api,以及api如何与lambda&stream等新特性应用在开发中。文章中不包含的内容:
    • java8新特性和语法讲很少,如果对java8还不太了解可以先自己查阅学习。
    • 文中例子都比较简单,代码以lambda和stream的代码为主,java8之前的代码例子基本没有,脑补一下,大家都能想出来。
  • 文中例子,都是自己敲过一遍,并且运行出的结果,如果大家发现问题,或者有更好的例子等都欢迎大家随时反馈给我@有光。

java8新增的API

  • 非官方数据
    • 195个新的文件加入到 JDK8 API
    • 93 新类, 89 新接口 , 13 新enum
    • 2699 新方法, 56 新构造函数
    • 46 个接口标记为函数式接口
    • 213 接口默认方法
    • 68 接口静态方法

基础

String

join()

  • 字符串拼接。
        String.join(":", "alibaba", "icbu", "youguang");
        String join = String.join(":", Lists.newArrayList("alibaba", "icbu", "youguang"));
        System.out.println(join);// => alibaba:icbu:youguang

chars()

  • 创建一个字符流。
  • 例子:统计去重后的字符数.
        long collect = "alibaba:icbu:youguang".chars()//
                                              .distinct()//
                                              .count();
        System.out.println(collect);// => 11

StringJoiner

  • 字符串拼接。
  • PS:String.join方法内部就是用StringJoiner实现的。
         StringJoiner sj = new StringJoiner(":");
        sj.add("alibaba");
        sj.add("icbu");
        sj.add("youguang");
        String result = sj.toString(); //alibaba:icbu:youguang

        sj = new StringJoiner(":","prefix-", "-suffix");
        sj.add("alibaba");
        sj.add("icbu");
        sj.add("youguang");
        System.out.println(sj.toString()); //prefix-alibaba:icbu:youguang-suffix

正则

Pattern.splitAsStream()

  • 正则分割后的结果,通过流来操作。
  • 例子:冒号分割,过滤掉youguang
        String bar = Pattern.compile(":")
                            .splitAsStream("alibaba:icbu:youguang")
                            .filter(s -> !s.contains("youguang"))
                            .collect(Collectors.joining(":"));
        System.out.println(bar);// => alibaba:icbu

Pattern.asPredicate()

  • 通过正则生成Predicate。
  • 例子:结合stream来过滤字符串。
      Pattern pattern = Pattern.compile(".*@gmail\\.com");
        Stream.of("tony.liw@gmail.com", "tony.liw@alibaba-inc.com")
              .filter(pattern.asPredicate())
              .count();// => 1

数学操作

输入&输出

  • java8提供了更简单的方式进行文件操作。

Files.list

  • Files类从1.7引入,1.8增加list方法生成stream来操作文件/目录。
  • PS:此处使用了1.7中的try-with-resource语法(Stream实现了AutoCloseable接口)
        try (Stream<Path> stream = Files.list(Paths.get("/opt"))) {
            String joined = stream.map(String::valueOf)//
                    .filter(path -> !path.startsWith("."))//
                     .sorted().collect(Collectors.joining("; "));
            System.out.println("List: " + joined);// => List: /opt/cisco; /opt/local

Files.find

  • 查找文件
  • 例子: 找到目录下的所有JavaScript文件(目录查找深度不超过5)。
        Path start = Paths.get("/home/admin/");
        int maxDepth = 5;//目录树层数
        try (Stream<Path> stream = Files.find(start, maxDepth, (path, attr) ->
                String.valueOf(path).endsWith(".js"))) {

            String joined = stream
                    .sorted()
                    .map(String::valueOf)
                    .collect(Collectors.joining("; "));
            System.out.println("Found: " + joined);
        }

Files.walk

  • 遍历文件,下面例子实现与上面的find同样的功能,不同的是过滤JavaScript文件通过stream的filter实现。
        Path start = Paths.get("/home/admin/");
        int maxDepth = 5;//目录树层数
        try (Stream<Path> stream = Files.walk(start, maxDepth)) {
            String joined = stream
                    .map(String::valueOf)
                    .filter(path -> path.endsWith(".js"))
                    .sorted()
                    .collect(Collectors.joining("; "));
            System.out.println("walk(): " + joined);
        }

Files.readAllLines

  • 读取文件的所有内容
List<String> lines = Files.readAllLines(Paths.get("res/nashorn.js"));
lines.add("print('foobar');");
Files.write(Paths.get("res/nashorn1-modified.js"), lines);

PS:使用该方法的时候要考虑到文件的大小,该方法会将所有内容一次读入内存。

Files.lines

  • 将文件内容转成stream,元素是每行的内容。
       try (Stream<String> stream = Files.lines(Paths.get("res/nashorn.js"))) {
            stream
                    .filter(line -> line.contains("print"))
                    .map(String::trim)
                    .forEach(System.out::println);
        }

Files.newBufferedReader&Files.newBufferedWriter

  • 带buffer的读写操作
Path path = Paths.get("res/nashorn1.js");
try (BufferedReader reader = Files.newBufferedReader(path)) {
    System.out.println(reader.readLine());
}
Path path = Paths.get("res/output.js");
try (BufferedWriter writer = Files.newBufferedWriter(path)) {
    writer.write("print('Hello World');");
}

NULL(空指针)

  • 如何避免NullPointerException,为了避免NPE,写代码过程!=null检查会出现在各种地方,java8引入了更好的解决方案(Optional+Lambda)。其他语言解决NPE的语法糖

Optional基本

    //不要这样,这与!=null没什么区别
    if(stringOptional.isPresent()){
         System.out.println(stringOptional.get().length());
    }
    //下面是推荐的常用操作

    optionalValue.ifPresent(s -> System.out.println(s + " contains red"));
    //增加到集合汇总
    optionalValue.ifPresent(results::add);
    //增加到集合中,并返回操作结果
    Optional<Boolean> added = optionalValue.map(results::add);

    //无值的optional
    Optional<String> optionalString = Optional.empty();
    //不存在值,返回“No word”
    String result=optionalValue.orElse("No word");
    //没值,计算一个默认值
    result = optionalString.orElseGet(() -> System.getProperty("user.dir"));
    //无值,抛一个异常
    try {
        result = optionalString.orElseThrow(NoSuchElementException::new);
    } catch (Throwable t) {
    }   

Optional.map

  • 例子:看代码应该秒懂了,就是取foo值,但是为了取这个值,正常的逻辑里面需要增加一串!=null检查。java8中可以通过map函数避免。
//老的写法
Outer outer = new Outer();
if (outer != null && outer.nested != null && outer.nested.inner != null) {
    System.out.println(outer.nested.inner.foo);
}
//新的写法
Optional.of(new Outer())
    .map(Outer::getNested)
    .map(Nested::getInner)
    .map(Inner::getFoo)
    .ifPresent(System.out::println);

//第二种lambda的方式
resolve(() -> obj.getNested().getInner().getFoo())
    .ifPresent(System.out::println);

public static <T> Optional<T> resolve(Supplier<T> resolver) {
    try {
        T result = resolver.get();
        return Optional.ofNullable(result);
    }
    catch (NullPointerException e) {
        return Optional.empty();
    }
}

集合

Collection

removeIf

  • 通过传入Predicate删除集合里面符合条件的元素。
        Collection<String> c = new HashSet<>();
        c.add("Content 1");
        c.add("Content 2");
        c.add("Content 3");
        c.removeIf(s -> s.contains("2"));
        System.out.println(c);// => [Content 3, Content 1]
  • PS:如果这个接口可以满足过滤的要求,就没必要使用stream了,这个效率更好。
  • 扩展:充分利用Predicate,直接看例子
//基本操作
 list =new ArrayList(Arrays.asList(1,2,3,4,5,6,7,8,9,10,11));
 list.removeIf(a->{ return a%3==0;});

 //OR 操作
 Predicate<Integer> predicate2 = a->{ return a % 3 == 0;};
 Predicate<Integer> predicate3 = a->{ return a % 5 == 0;};
 list =new ArrayList(Arrays.asList(1,2,3,4,5,6,7,8,9,10,11));
 list.removeIf(predicate2.or(predicate3));

 //AND 操作
 Predicate<Integer> predicate2 = a->{ return a % 3 == 0;};
 Predicate<Integer> predicate3 = a->{ return a % 5 == 0;};
 list =new ArrayList(Arrays.asList(1,2,3,4,5,6,7,8,9,10,11));
 list.removeIf(predicate2.and(predicate3));

stream、parallelStream

  • 大家都知道了,就不说了,记住所有集合类都是通过Collection接口继承来即可。

Iterable

forEach

  • 遍历每个元素,继承了Iterable接口的都可以使用。
 List<String> stringList = Arrays.asList("a", "b");
        stringList.forEach(System.out::println);

iterator

forEachRemaining

  • 遍历iterator,并根据指定的action进行处理。开发中可能会出现,需要对集合中的第一个或者前几个元素进行特别的操作,然后继续遍历剩余的元素执行另一个操作(action),这个时候使用forEachRemaining非常合适。例如下面字符串拼接的例子。
        if (!iterator.hasNext()) {
            return;
        }
        StringBuilder builder = new StringBuilder();
        builder.append(iterator.next());
        iterator.forEachRemaining( element -> {
            builder.append(", ").append(element);
        });
  • PS:这里只是说明一下用法,字符串拼接,当然用String.join就够啦。

List

replaceAll

  • 传入UnaryOperator,将元素替换为一元操作之后的值
   List<String> stringList = Arrays.asList("a", "b", "c");
        stringList.replaceAll(String::toUpperCase);
        System.out.println(stringList);//[A, B, C]

sort

  • 通过指定Comparator进行排序,参数为空则根据自然排序。
        List<String> stringList = Arrays.asList("a", "b", "c");
        stringList.sort(String::compareTo);
  • PS: 可能会有人问这个和Collections.sort()的区别。下面是Collections的sort方法代码。内部调用的就是list.sort
  public static <T extends Comparable<? super T>> void sort(List<T> list) {
        list.sort(null);
    }

Set

  • 接口没什么变化。

Map

foreach

       Map<String, Integer> map = new HashMap<>();
        map.put("A", 10);
        map.put("B", 20);
        map.put("C", 30);
        map.forEach((k, v) -> System.out.println("Item : " + k + " Count : " + v));

getOrDefault

  • 根据key取value,没有返回一个默认值(这个一直是很多人想要的方法)。
System.out.println(map.getOrDefault("D",40));// => 40
  • PS:如果还在使用Apache Commons Collections包中的DefaultedMap类,更换了。

putIfAbsent

  • javadoc中提供了与putIfAbsent的等价方法,
  • 特别说明put方法返回值:对应的key曾经有值返回老的value,否则返回null
    //The default implementation is equivalent to, for this map:
       V v = map.get(key);
      if (v == null)
          v = map.put(key, value);
    
      return v;
    
  • 例子:
        System.out.println(map.putIfAbsent("B",40));// => 20
        System.out.println(map.putIfAbsent("D",40));// => null
    

remove(Object.Object)

  • key和valu都相等的时候才删除,下面是javadoc中原来等价方法实现。
      if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
          map.remove(key);
          return true;
      } else
          return false;
  • 例子:
 System.out.println(map.remove("D",40));// => true

replace(K,V)

  • map中包含这个key,替换对应的value。javadoc中原来等价的方法实现
      if (map.containsKey(key)) {
          return map.put(key, value);
      } else
          return null;
  • 例子:Map<String, Integer> map = new HashMap<>(); map.put("A", 10); System.out.println(map.replace("A",20));//=> 10
  • PS:注意与putIfAbsent区别

replace(K,V,V)

  • map中包含这个key,并且value相等时,替换对应的value。javadoc中原来等价的方法实现
      if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
          map.put(key, newValue);
          return true;
      } else
          return false;
  • 例子:
        Map<String, Integer> map = new HashMap<>();
        map.put("A", 10);
        map.put("B", 20);
        System.out.println(map.replace("B",10,100));//=>false
        System.out.println(map.replace("B",20,100));//=> true

compute(K,remappingFunction)

  • 通过remappingFunction函数,根据指定的key和对应的value,通过计算生成新的value。javadoc中原来等价的方法实现
      V oldValue = map.get(key);
      V newValue = remappingFunction.apply(key, oldValue);
      if (oldValue != null ) {
         if (newValue != null)
            map.put(key, newValue);
         else
            map.remove(key);
      } else {
         if (newValue != null)
            map.put(key, newValue);
         else
            return null;
      }
  • 例子:统计单词出现次数/统计图书借阅次数/遇到同样的key,创建或者append一个msg信息等。
        Map<String, Integer> countMap = Maps.newHashMap();
        List<String> bookList= Arrays.asList("Book-A", "Book-B", "Book-A");
        for (String book : bookList) {
            countMap.compute(book, (k, v) -> v == null ? 1 : v + 1);
        }
        System.out.println(countMap);
  • PS:要注意如果remappingFunction计算结果为空,会从map中remove

computeIfAbsent(K,mappingFunction)vs computeIfPresent(K,remappingFunction)

  • 这两个方法比较相近容易混淆,对比一下,下面是javadoc中两个新方法等同老代码。左侧:computeIfAbsent,右侧:computeIfPresent screenshot
  • 区别:
    • computeIfAbsent value根据key计算。computeIfPresent value基于key和oldValue计算(参数名也可以看出点意思mappingFunction/remappingFunction)
    • newValue为空,computeIfPresent会删除map中的元素。
  • 例子:这两个方法很容易想到的一个场景是本地缓存(考虑到多线程场景可以使用ConcurrentHashMap),下面以computeIfAbsent方法为例,同时与java8之前的版本代码做一下对比。
    
    static Map<String, Integer> CACHE = new ConcurrentHashMap<>();
    
    public Integer  getJAVA8(String key){
        //java8会使用thread-safe的方式从cache中存取记录
       return CACHE.computeIfAbsent(key, s -> {
           int rt=0;
           //xxx 计算 rt=
           return rt;
       });
    }
    
    public Integer  getJAVA7(String key){
    
        Integer rt = CACHE.get(key);
    
        if (rt == null) {
            //java8 中ConcurrentHashMap复写了computeIfAbsent方法做了线程安全控制
            synchronized (CACHE) {
                rt = CACHE.get(key);
                if (rt == null) {
                    //xxxx计算 rt=
                    CACHE.put(key, rt);
                }
            }
        }
        return rt;
    }
    

merge(K,V,remappingFunction)

  • 看名字已经理解80%了,直接看javadoc中与老版本等价的代码好了。
      V oldValue = map.get(key);
      V newValue = (oldValue == null) ? value :
                   remappingFunction.apply(oldValue, value);
      if (newValue == null)
          map.remove(key);
      else
          map.put(key, newValue);
  • 是不是一眼看过去和computeIfPresent很像。此处diff一下就清晰了,computeIfPresent是在key对应的value存在时才有效。 screenshot

replaceAll(function)

  • 根据function计算结果,替换map所有entry的value

Comparator

  • Comparator在平时开发中用的比较多,java8针对该接口,增加了16个默认方法和静态方法,下面挑一些跟大家分享。

comparing(keyExtractor)

  • 先看例子,生成根据firstName对Person进行排序的Comparator:
        //老写法
        Comparator<Person> comparator=new Comparator<Person>() {
    
            @Override
            public int compare(final Person o1, final Person o2) {
                return o1.name.compareTo(o2.name);
            }
        };
        //lambda 写法
        comparator= (o1, o2) -> o1.name.compareTo(o2.name);
        //Comparator.comparing方法,提供提取比较key的function即可
        comparator= Comparator.comparing(Person::getName);
    
  • PS:该方法,要求提取的key实现了 Comparable接口。key没有Comparable接口可以使用重载实现comparing(keyExtractor,keyComparator)
  • 重载方法:
    • comparing(keyExtractor,keyComparator)
    • comparingInt(keyExtractor) 提取的key是Integer类型
    • comparingLong(keyExtractor)
    • comparingDouble(keyExtractor)

reversed()

  • 反转比较规则
        List<Integer> integerList = Arrays.asList(1, 3, 2, 5, 4);
        //正序
        integerList.sort(Integer::compareTo);
        System.out.println(integerList); // => [1, 2, 3, 4, 5]
        //倒序
        integerList.sort(Comparator.comparing(Integer::intValue).reversed()); // => [5, 4, 3, 2, 1]

thenComparing(other)

  • 链式比较,以后不用在使用第三方的ComparisonChain类了。
  • 例子:先按字符串长度,再按字符默认顺序排序(忽略大小写)。
 Comparator<String> cmp = Comparator.comparingInt(String::length)
                                              .thenComparing(String.CASE_INSENSITIVE_ORDER);
  • 重载方法
    • thenComparing(keyExtractor,keyComparator)
    • thenComparing(keyExtractor)
    • thenComparingInt(keyExtractor)
    • thenComparingLong(keyExtractor)
    • thenComparingDouble(keyExtractor)

reverseOrder

  • 静态方法,返回与自然排序相反的Comparator(比较元素要实现Comparator接口)
        List<Integer> integerList2 = Arrays.asList(1, 3, 2, 5, 4);
        integerList.sort(Comparator.reverseOrder());
        System.out.println(integerList);// => [5, 4, 3, 2, 1]

naturalOrder

  • 与reverseOrder相反,默认的自然排序器。等价//c1.compareTo(c2);

nullsFirst(comparator) & nullsLast(comparator)

  • 针对null值指定策略,排在前面(nullsFirst),还是后面(nullsLast)
        List<Integer> integerList3 = Lists.newArrayList(1, 3, null, 5, 4);
        integerList3.sort(Comparator.nullsFirst(Integer::compareTo));
        System.out.println(integerList3);// => [null, 1, 3, 4, 5]
        integerList3.sort(Comparator.nullsLast(Integer::compareTo));
        System.out.println(integerList3);// => [1, 3, 4, 5, null]
  • 这两个比较器一个很大的作用是可以处理结合中有null值的情况,一般默认的比较器遇到null都会抛出NPE,如果不想NPE可以使用nullsFirst/nullsLast包装一下。上面例子:如果不通过Comparator.nullsFirst(Integer::compareTo)生成新的比较器,直接使用Integer::compareTo就会抛NPE异常。

Arrays

  • 工具类中增加了一些对数组相关的流和并行处理的操作。

Stream

  • 将数组转成流进行操作,这样Stream Api就都可以用了stream api
//数组内容求和
int[] array = new int[]{1,2,3,4,5};
//求和
Arrays.stream(array).sum()
//过滤
int[] ms = Arrays.stream(ns).map(n -> n * 2).filter(n -> n % 4 == 0).toArray();

setAll

  • setAll方法提供一个 int -> T的函数接口做参数,int是数组的索引,T是数组的新值。
  • 例子:通过该接口对数组的索引进行操作,然后将指定数组当前索引位置的值赋值为操作后的值。
        int[] array = new int[10];
        Arrays.setAll(array, i -> i * 10);
        System.out.println(Arrays.toString(array));// => 输出 [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]        

spliterator

Collections

  • emptySortedSet、emptySortedMap、emptyNavigableSet、emptyNavigableMap、unmodifiableNavigableSet、unmodifiableNavigableMap、synchronizedNavigableSet、synchronizedNavigableMap等。

集合&数组转化

数组转集合

  • 没发现java8有什么新增的方式,如果有同学知道的求推荐。老的方式(Arrays/Guava包):
        List<Integer> integerList = Arrays.asList(1, 2, 3, 4);
        // guava
        integerList = Lists.newArrayList(1, 2, 3, 4);

集合转数组

  • 看例子
        //原始方式
        Integer[] integerArray = integerList.toArray(new Integer[integerList.size()]);
        // guava
        int[] intArray = Ints.toArray(integerList);
        // java8-基本类型
        intArray = integerList.stream().mapToInt(Integer::intValue).toArray();
         // java8-对象类型
        stringArray = stringList.stream().toArray(String[]::new);
Character[] characterArray = stringList.stream().map(s -> s.charAt(0)).toArray(Character[]::new);

未完待续

  • java8 API (二) stream整理中

发表评论