前言

目前java已经更新到15了,然鹅,我还在使用8以前的特性,所以为了以后的饭碗,咱还是得自学一下新特性。

Java8新增的几个重要特性

java8中新增了非常多的特性,其中需要主要学习的我认为有以下几个

  1. lambda表达式
  2. 方法引用
  3. Stream API
  4. Optional类
  5. 函数式接口
  6. 默认方法

下面我就来着重介绍一下他们。

Lambda表达式

相信有前端 ES6基础的小伙伴都知道 lambda 是啥,没错,java8中也新增了这个特性。

总的来说lambda表达式就是一个语法糖,它可以极大的减少我们的代码量,但同时或许会给后续的维护工作带来一些困扰。

不同于 ES6 中的 => ,在 Javalambda 的语法格式如下

(parameters) -> expression
or
(parameters) ->{ statements; }

我们可以发现,在 Java 中符号变为了 ->,这个也是需要我们注意的一点。

下面我们来看一个例子,首先,声明一个接口,之后我们用 Lambda 表达式来进行使用。

/**
 * @作者: Seale
 * @时间: 2020/10/15 0:43
 * @说明: 使用lambda表达式
 * @类名: LambdaTest
 */
public class LambdaTest {
    public static void main(String[] args) {
        // 之前的隐式实现
        Itest sumBefore = new Itest() {
            @Override
            public Integer calc(int a, int b) {
                return a+b;
            }
        };
        System.out.println("before:"+sumBefore.calc(12,12));
        // 使用lambda表达式
        Itest sum = (a,b)-> a + b;
        System.out.println("now:"+sum.calc(12,12));

    }
    interface Itest{
        Integer calc(int a,int b);
    }
}

运行结果如下:

before:24
now:24

但值得注意的是,lambda 表达式只能引用标记了final的外层局部变量,反之那么就说明,在lambda表达域之内,我们不能修改在域外的局部变量(可以访问),否则会编译错误。

Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。

方法引用

方法引用是指通过一个方法的名字来指向它。

方法引用可以使得结构更紧凑和简洁,据我所知,在 BeetlSQL 这个持久化框架(类似于Mybatis)就大量使用到了方法引用来优雅的处理SQL。

方法引用使用的是 :: 一对冒号。

提供的可用来进行方法引用的例子。

/**
 * @作者: Seale
 * @时间: 2020/10/15 0:59
 * @说明: 方法引用
 * @类名: MethodReference
 */
public class MethodReference {
    public static void main(String[] args) {
        ICalc sum = Integer::sum;
        /*ICalc max = Integer::max;*/
        System.out.println(sum.sum(12,15));
    }
    interface ICalc{
        int sum(int a , int b);
        /*int max(int a, int... nums);*/
    }
}

方法引用主要有三类:

  1. 指向静态方法的方法引用,比如 Integer::parseInt
  2. 指向任意类型实例方法的方法引用,比如 String::length
  3. 指向现有对象的实例方法引用 this::hashCode

函数式接口

首先,我们需要知道函数式接口的作用,类似于我们常常使用的注解 @Override ,在我们声明的接口上方,我们需要先声明一个注解 @FunctionalInterface ,那么这个接口就可以被隐式的转换为lambda表达式。

函数式接口有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

同时,在Java8中,已经内置了几种常用的函数式接口:

  1. 消费型接口 Consumer<T>
  2. 供给型接口 Supplier<T>
  3. 函数型接口 Function<T,R>
  4. 断言型接口 Predicate<T>

消费型接口

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    /**
     * Returns a composed {@code Consumer} that performs, in sequence, this
     * operation followed by the {@code after} operation. If performing either
     * operation throws an exception, it is relayed to the caller of the
     * composed operation.  If performing this operation throws an exception,
     * the {@code after} operation will not be performed.
     *
     * @param after the operation to perform after this operation
     * @return a composed {@code Consumer} that performs in sequence this
     * operation followed by the {@code after} operation
     * @throws NullPointerException if {@code after} is null
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

接收一个参数进行消费,但是无需返回结果。

import java.util.function.Consumer;

/**
 * @作者: Seale
 * @时间: 2020/10/15 0:59
 * @说明: 方法引用
 * @类名: MethodReference
 */
public class MethodReference {
    public static void main(String[] args) {
        Consumer<String> consumer = (str)->System.out.println(str+"\t消费成功");
        consumer.accept("女盆友1号");
        consumer.accept("女盆友2号");
        consumer.accept("女盆友3号");
    }

}
女盆友1号   消费成功
女盆友2号   消费成功
女盆友3号   消费成功

供给型接口

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

返回一个结果

public static void main(String[] args) {
        Supplier<String> t1Sl = ()->"男盆友";
        Supplier<String> t2Sl = ()->"女盆友";
        System.out.print(t1Sl.get()+"->");
        System.out.println(t2Sl.get());
    }

函数型接口

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
    ...
}

同样的,函数型接口需要我们提供一个参数,之后返回一个结果。

public static void main(String[] args) {
        Function<String, Boolean> function = (s) -> {
            System.out.println("当前名字:"+s+"\t执行了工作!");
            return true;
        };
        boolean flag = function.apply("小王");
        System.out.println("执行结果:" + flag);
    }

断言型接口

@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
    ...
}

传入一个参数,返回成功与否。

下面我们看一个例子。

public static void main(String[] args) {
        Predicate<String> pre1 = String::isEmpty;
        Predicate<String> pre2 = (str)-> str.equals("测试");

        boolean result1 = pre1.test("");
        System.out.println("执行的结果:"+result1);
        boolean result2 = pre2.test("测试1");
        System.out.println("执行的结果:"+result2);
    }

执行的结果:

执行的结果:true
执行的结果:false

默认方法

默认方法是 Java 8 中新增加的特性,简单来说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其中的接口方法。

public interface ITest{
    default void print(){
        System.out.println("测试默认方法");
    }
    // 接口的静态方法
    static void staticPrint(){
        System.out.print("测试的静态方法");
    }
}

Optional 类对象

Optional类是一个可以为null的容器对象,如果存在值那么isPresent()方法会返回true,调用get()方法会返回该对象。

Optional类的引入很好的解决了空指针异常,使得我们在平时的开发中不用再去解决/处理空指针异常。

常用的方法

方法名 描述
static Optional empty() 返回空的Optional实例
boolean equals(Object obj) 判断其他对象是否和Optional中的值相同
T get() 如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException
boolean isPresent() 如果值存在则方法会返回true,否则返回 false。
static Optional of(T value) 返回一个指定非null值的Optional。否则抛出空指针异常
static Optional ofNullable(T value) 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
T orElse(T other) 如果存在该值,返回值, 否则返回 other。

表格内容来自菜鸟教程

Stream(超级重要)

Stream是 JAVA 8 中处理集合的抽象概念,它可以让我们以一种声明的方式来处理数据。它用一种类似于SQL语句的方式来操作Java集合、进行集合运算。它是一种对Java集合操作运算的高阶抽象类。

这种操作将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,进行一些集合操作。

特性

  1. 不是数据结构,它不会保存数据。
  2. 不会修改原来对象的数据,它会将操作后的数据保存到另外一个对象中去。
  3. 惰性求值,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止的时候才会进行实际的计算。
  4. Pipelining:中间操作都会返回流对象本身,这样多个操作可以串联成一个管道,如同流式风格。这样可以对操作优化,比如延迟执行等。
  5. 内部迭代:在之前对集合遍历都是通过迭代器或者foreach的方式来进行的,显示的在集合外部进行迭代,这叫做外部迭代。而Stream提供了内部迭代的方式,通过访问者模式实现。

什么是Stream

Stream(流)是一个来自数据源的元素队列并且支持聚合操作。

元素是特定类型的独享,形成一个队列,Java中的流对象并不会存储元素,而是按需计算。

数据源是流的来源,可以是集合,数组,I/O通道,产生器等等。

聚合操作类似于SQL语句一样,如filter,map,reduce,find,match,sorted。

具体使用

常用的创建方法

  1. 使用集合下的 stream()parallelStream() 方法来获取流对象。

    public class Stream1 {
       public static void main(String[] args) {
           List<String> strings = Arrays.asList("a","b","c","d","e","f","g");
           Stream<String> stringStream = strings.stream(); //获取一个顺序流
           Stream<String> parallelStream = strings.parallelStream(); //获得一个并行流
           stringStream.forEach(System.out::print);
           System.out.println();
           parallelStream.forEach((s)->{
               System.out.print(s + " ");
           });
       }
    }

    对于以上代码片段有如下结果

    abcdefg
    e d a f c g b 

    我们可以很明显的对比出来,顺序流和并行流的区别。

  2. 使用Stream中的静态方法来构建流对象

    public static void main(String[] args) {
           Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    
           Stream<Integer> stream1 = Stream.iterate(0, (x) -> x + 2).limit(10);
           stream1.forEach((i) -> {
               System.out.print(i + " ");
           }); // 0 2 4 6 8 10 12 14 16 18
    
           System.out.println();
    
           Stream<Double> stream2 = Stream.generate(()-> Math.random() * 10 +1).limit(10);
           stream2.forEach((d) -> {
               System.out.print(d.intValue() + " ");
           }); // 10个随机数
       }

流的中间操作

  1. 筛选与切片操作

    熟悉Python的同学就知道,在Python中可以对字典,集合,元组等数据结构进行一些pythonic风格的操作,其中切片操作最为经典。Java8 也支持类似的操作了。

    filter:过滤流中的某些元素

    limit(n):获取n个元素

    skip(n):跳过n元素

    distinct:通过流中元素的hashCode()和equals()去除重复元素

    public static void main(String[] args) {
           Stream<Integer> stream = Stream.of(6, 15, 100, 258, 150, 0, 1, 2, 5, 4, 8, 9, 6, 3, 6, 9, 6, 165, 4189, 1, 911, 5614, 9, 1, 9, 4, 98, 4, 156, 16, 1);
           Stream<Integer> newStream = stream.filter(i -> i > 5) //获取大于5的元素流对象
                   .distinct();//去重
           newStream.forEach(i-> System.out.print(i + " "));
    
       }

    运行结果

    6 15 100 258 150 8 9 165 4189 911 5614 98 156 16 
  2. 映射

    map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。

    flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

    public static void main(String[] args) {
           List<String> list = Arrays.asList("a,b,c","1,2,3");
    
           Stream<String> s1 = list.stream().map(s -> s.replaceAll(",",""));
           s1.forEach(s -> System.out.print(s+" "));
    
           Stream<String> s3 = list.stream().flatMap(s ->{
              // 将每个元素转换成一个stream
              String[] split = s.split(",");
               return Arrays.stream(split);
           });
           s3.forEach(s->System.out.print(s+" "));
       }
  3. 排序

    sort:自然排序,流中元素需要实现Comparable接口

    sorted(Comparator com):自定义排序

    public static void main(String[] args) {
           List<String> list = Arrays.asList("a","v","b");
           list.stream().sorted().forEach(s -> System.out.print(s+" "));
    
           Student s1 = new Student("张三",18);
           Student s2 = new Student("李四",19);
           Student s3 = new Student("王五",20);
           Student s4 = new Student("陈麻子",17);
           List<Student> students = Arrays.asList(s1,s2,s3,s4);
           students.stream().sorted(Comparator.comparingInt(Student::getAge)).forEach(System.out::println);
       }
  4. 消费

    peek:接收Consumer表达式,没有返回值。

    public static void main(String[] args) {
           List<String> list = Arrays.asList("a","v","b");
           list.stream().sorted().forEach(s -> System.out.print(s+" "));
    
           Student s1 = new Student("张三",18);
           Student s2 = new Student("李四",19);
           Student s3 = new Student("王五",20);
           Student s4 = new Student("陈麻子",17);
           List<Student> students = Arrays.asList(s1,s2,s3,s4);
           students.stream()
                   .peek(o -> o.setAge(o.getAge()+20))
                   .sorted(Comparator.comparingInt(Student::getAge).reversed())
                   .forEach(System.out::println);
           // 在原有的基础上自增20,并进行倒序
       }

流的终止操作

  1. 匹配/聚合

    allMatch:接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false

    noneMatch:接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false

    anyMatch:接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false

    findFirst:返回流中第一个元素

    findAny:返回流中的任意元素

    count:返回流中元素的总个数

    max:返回流中元素最大值

    min:返回流中元素最小值

  2. 规约(reduce)

    从一组值中生成一个新的值,reduce 函数其实用途非常广泛,作用也比较大,我们举一个累加的例子。

    public static void main(String[] args) {
           List<Integer> list = Arrays.asList(1,2,3,4,5);
           int sum = list.stream().reduce((before,now)->before+now).get();
           System.out.println(sum);
       }
  3. 收集

    collect:接收一个Collector实例,将流中元素收集成为另外一个数据结构。


本作品采用知识共享署名 4.0 国际许可协议进行许可。

如果可以的话,请给我钱请给我点赞赏,小小心意即可!

Last modification:October 18, 2020
If you think my article is useful to you, please feel free to appreciate