Java8 新特性

Java 8 新特性

Lambda Expressions

1. Lambda 表达式是什么?什么时候可以使用?

Lambda 表达式式Java8的新特性,它支持了Java简单的“函数式编程”。根据 官方文档表示,当一个匿名类有且只有一个抽象方法时,就可以使用Lambda表达式。而可以作为一个匿名类的前提是存在这样的一个接口或者抽象类,但是该表达式只能够针对有 “@FunctionalInterface” 注解的接口,所以抽象类并不能够使用该表达式。在java中,Lambda表达式是SAM(singleabstract method)类型,SAM类型是一个具有单一抽象方法的接口。

2. Lambda表达式如何使用?

基本语法:(parameters) -> expression 或 (parameters) -> {statements ;}

(参数) -> 带返回值的表达式 / 无返回值的操作(有无返回值都可以按照后面的方式进行表达,两种表达方式的区别在于表达式有多少条语句,当且只有一条时才可以用第一种方式)

注意:

  1. 如果形参列表是空的,只需要保留()即可S

  2. 如果没有返回值。只需要在{}写执行语句即可

  3. 如果接口的抽象方法只有一个形参,()可以省略,只需要参数的名称即可

  4. 如果执行语句只有一行,可以省略{},但是如果有返回值时,情况特殊。

  5. 如果函数式接口的方法有返回值,必须给定返回值,如果执行语句只有一句,还可以简写,即省去大括号和return以及最后的“;”号。

  6. 形参列表的数据类型会自动推断,只需要参数名称。

举例如下:

  • 例如返回两个值的和:

  • #第一种方式:表示返回值为 x + y 的值,但是不用写return 
    (x ,y ) ->  x + y 
    #第二种方式:
    (x ,y ) -> {return x + y;}

该表达式只能替代那些 有且只有一个抽象方法并有FunctionalInterface注解的接口。比如Runnable、Comparator等。

  • 再举个例子,使用Lambda替换Runnable匿名内部类

    //表达式 “() -> System.out.println("I am running")” 替换了new Runnable(){}这个匿名内部类
    new Thread(() -> System.out.println("I am running")).start();
  • Lambda 表达可以作为参数也可以作为返回值,如果参数或者返回值是一个ASM,则可以使用Lambda表达式替代。

3. java8提供的函数接口

在Java8中,新增了一个包 java.util.function,这个包里有一些专门给新增的API使用的函数接口,例如:

  • Consumer – 在T上执行一个操作,无返回结果

  • Supplier –无输入参数,返回T的实例

  • Predicate –输入参数为T的实例,返回boolean值

  • Function –输入参数为T的实例,返回R的实例

4.方法引用

一共有四种类型的方法引用:

类型 示例
类静态方法引用 ContainingClass::staticMethodName,例如:Person::getName
某个对象的方法引用 ContainingObject::instanceMethodName,例如:person::getName
特定类的任意对象的方法引用 ContainingType::methodName ,例如:String:: compareToIgnoreCase
构造方法引用 ClassName::new ,例如:HashSet::new

那什么时候可以使用方法引用呢?

  • 就是在可以使用Lambda表达式的地方并且当前有现成的方法能够表达出Lambda表达式中的expression,则可以使用方法引用。它们(Lambda中的expression & 对应的方法)应该具有相同的特征:形参相同,语句表达的功能相同。也就是说,可以使用方法引用来替代FunctionalInterface。以下例子只是用来说明,并没有实质性作用。

    public class Test {
        public static void main(String[] args){
          MyLambdaNum myLambdaNum =  new MyLambdaNum(MyLambdaNum::add);
        }
    }
    
    /**SAM **/
    @FunctionalInterface
     interface MyLambda{
         int add(int x , int y );
    }
    
    
    class MyLambdaNum{
        private int myLambdaNum ;
        public MyLambdaNum(MyLambda myLambda){
          myLambdaNum =  myLambda.add(2,4);
        }
    //目标引用方法
        public static int add (int x ,int y ){
            return x + y ;
        }
    }
    

Default 方法

Java8 中,在接口中引入了default方法。而default方法就是在接口中用default修饰的方法,并且含有方法体。那为什么需要default方法呢?default方法是为了解决在不破坏java现有实现架构的情况下能往接口里增加新方法,即优化接口的同时,避免跟现有实现架构的兼容问题。

public interface MyDefault{
    default void doSomething(){
        System.out.println("myDefault");
    }
}

public class MyImplement implements MyDefault{
    
}

实现了含有default方法接口的类不用再实现default方法了,那如果一个类实现了两个接口,并且这两个接口里含有相同的default方法,那实现类在调用的时候,会调用哪一个类呢?答案是:这种情况是编译不通过的,为了解决这种问题,则实现类必须对相同的方法进行重写。在进行调用的时候,则会调用实现类复写的方法。如果在实现类中需要调用指定父接口的方法,则可以用 指定父接口名.super.方法名 进行调用。

Stream

1. Stream是什么?

stream就是 Java8 提供给我们的对于元素集合统一、快速、并行操作的一种方式,它支持顺序和并行对元素集合进行批量操作。它提供了一种操作大数据接口,让数据操作更容易和更快 。使用stream,我们能够对collection的元素进行过滤、映射、排序、去重等许多操作。

在Java8中,新增了一个包java.util.stramjava.util.stream,使我们能够使用Java8集合类库执行类似filter/map/reduce的批量操作。批量数据操作有串行(在当前线程上)和并行(使用多线程)两种操作模式,因此我们可以更加高效地利用底层平台的并行特性。

//串行操作
Stream stream = lists.stream();
//并行操作
Stream parallelStream=persons.parallelStream();

2. 该如何使用Stream

1.通过Stream接口的静态工厂方法(注意:Java8里接口可以带静态方法)

Random random=new Random();
Stream randomNumbers=Stream.generate(random::nextInt);

2.通过Collection接口的默认方法(默认方法:Default method,也是Java8中的一个新特性,就是接口中的一个带有实现的方法)–stream(),把一个Collection对象转换成Stream。如:list.stream();则可以获得一个Stream对象。

一般情况下,我们都使用Collection接口的 .stream()方法得到stream.

中间方法与终点方法

Stream具有过滤、映射以及减少遍历数等方法,这些方法分两种:中间方法和终端方法,  “流”抽象天生就该是持续的,中间方法永远返回的是Stream,因此如果我们要获取最终结果的话,  必须使用终点操作才能收集流产生的最终结果。区分这两个方法是看他的返回值,  如果是Stream则是中间方法,否则是终点方法

中间操作

中间操作用来描述在数据流之上执行的转换操作(可以理解为一种映射操作)。filter() 和 map()是不错的中间操作的例子,它们的返回值是Stream类型,因此可以允许链式执行多个中间操作。

  • filter 排除所有不满足条件的元素,具体条件通过Predicate接口来定义;

  • map 执行元素的映射转换,具体的映射方式使用Function接口定义;

  • flatMap 通过另外一种 Stream接口将每个流元素转换成零个或者更多流元素

  • peek 对遇到的每个流元素执行一些操作。

  • distinct 根据流元素的equals(..)结果排除所有重复的元素

  • sorted 使后续操作中的流元素强制按Comparator定义的比较逻辑排列。

  • limit 使后续操作只能看到有限数量的元素。

  • substream 使后续操作只能看到某个范围内的元素(使用索引)。

//filter
Stream personsOver18 =persons.stream().filter(p ->p.getAge()>18);
//map 转换
Stream map = persons.stream().filter(p -> p.getAge() > 18).map(person -> new Adult(person)); 

终结操作

中间方法得到的是一个stream,若想把它转为新的集合,则需要使用终点方法。数据流的处理过程包含以下几个步骤:

  1. 从某个数据源头获取到数据流;

  2. 执行像filter,map等等这样的一个或者多个中间操作;

  3. 执行一个终结操作.

注意:终结操作必须是最后一个在数据流上执行的操作。一旦执行了终结操作,数据流就“消耗完了”,不可再用了。

  • reducers ,如reduce(..), count(..), findAny(..), findFirst(..),可以终结数据流处理过程。根据意图,终结操作可以是“短路”操作(不用完整的遍历所有数据流)。例如,findFirst(..)在一遇到匹配的元素就会马上终结数据流的处理过程。

  • collectors,就像其名字表示的,用来把处理过的元素收集到一个结果集中。

  • forEach 对数据流中的每一个元素执行某个操作。

  • iterators ,如果上面的操作都不能满足我们的需求,那么还是采用iterators这种传统的集合操作方式。

  • count,可使流的结果最终统计,返回int

  • collect,收集最终结果。

收集器

虽然抽象数据流本质上是连续的,而且我们可以定义数据流上的操作,但是要获得最终的结果,我们需要以某种方式收集到数据。数据流API提供了一些所谓的“终结”操作,而collect()方法就是终结操作的其中一个,它使我们能够收集结果数据。

Stream stream = ...;
List<T> lists = stream.collect(T,List(){...});
/************************分界线**************************/
//由于已经提供了Collectors工具类,则不需要自己实现一个Collector了
List<T> lists = stream.collect(Collectors.toList());
//或者
List<T> lists = stream.collect(Collectors.toCollection(ArrayList::new));

并行和串行

其实并行的原理就是把数据分成几个部分,然后各个部分新起一个线程进行处理,最终再把结果合在一起,并且不需要我们来处理并发问题。如果是多核机器,理论上会更快

参考资料:

https://blog.csdn.net/wwwsssaaaddd/article/details/24212693

https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

https://blog.csdn.net/zymx14/article/details/70175746

你可能感兴趣的:(Java,/,Java虚拟机,/,javaweb,Java)