(尊重劳动成果,转载请注明出处:https://blog.csdn.net/qq_25827845/article/details/87903464冷血之心的博客)

目录

背景:

正文:

Lambda表达式

定义 

语法

示例

总结:

默认方法

总结:

Stream API

什么是 Stream?

生成流

总结:

总结:


背景:

       JDK8发布于2014年3月18号,而我入坑Java是在2015年1月的本科毕业设计期间。大学本科最后一学期以及整个研究生期间,都在不断的入门学习Java相关基础知识和相关热门框架的使用,一直没有深入了解JDK8中出现的新特性(一直感觉好像用不到的样子>_<),只是为了应对校招面试简单的进行了一个了解。写这篇博客的原因是:同事在实际项目中使用JDK8新特性写了一段代码,使我感觉到使用JDK8新特性确实可以写出简洁而又高效的代码,这与我的追求(致力于写出优雅而又高效的代码)保持一致。所以,好记性不如烂笔头,我们一起来总结一下吧~

正文:

        Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。其中,比较重要的新特性如下:

  • Lambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中
  • 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与Lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码
  • 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法
  • 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps
  • Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中
  • Date Time API − 加强对日期与时间的处理
  • Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常
  • Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用
  • JVM方面 − 由元空间代替了永久代
  • HashMap − 底层实现增加了红黑树
  • 注解 − 引入了重复注解,使用@Repeatable,注解的使用场景拓宽
  • 更好的类型判断 − Value.defaultValue()

这篇博客,我们主要来总结学习Lambda表达式、默认方法以及Stream API的使用。

Lambda表达式

定义 

Lambda表达式(也称为闭包),允许我们将函数当成参数传递给某个方法,或者把代码本身当做数据处理;

语法

Lambda 表达式的语法格式如下:

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

lambda表达式的重要特征如下:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

示例

Lambda表达式的简单使用如下:

// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

Demo

package com.pak2;


public class Test {
    public static void main(String args[]){
        Test tester = new Test();

        // 类型声明
        MathOperation addition = (int a, int b) -> a + b;

        // 不用类型声明
        MathOperation subtraction = (a, b) -> a - b;

        // 大括号中的返回语句
        MathOperation multiplication = (int a, int b) -> { return a * b; };

        // 没有大括号及返回语句
        MathOperation division = (int a, int b) -> a / b;

        System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
        System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
        System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
        System.out.println("10 / 5 = " + tester.operate(10, 5, division));

        // 不用括号
        GreetingService greetService1 = message -> System.out.println("Hello " + message);

        // 用括号
        GreetingService greetService2 = (message) -> System.out.println("Hello " + message);

        greetService1.sayMessage("Runoob");
        greetService2.sayMessage("Google");
    }

    interface MathOperation {
        int operation(int a, int b);
    }

    interface GreetingService {
        void sayMessage(String message);
    }

    private int operate(int a, int b, MathOperation mathOperation){
        return mathOperation.operation(a, b);
    }
}

上述代码的输出如下所示:

总结:

  • Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。
  • Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。

注意:在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。如下图所示,编译会提示出错。

String first = "";  
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length());  //编译会出错 

 

接下来,我们接着看看默认方法的特性。

默认方法

       在面试的时候,你可能被问过这么一个问题,那就是:“Java中抽象类和接口的区别有哪些?” 也许你曾经这么回答过。 “抽象类中可以有已经实现的普通方法, 但是接口中都是抽象方法”。恭喜你,你的这一条区别在JDK8之前是没有问题的。但是,在JDK8发布之后就有待商榷了。因为JDK8中允许接口有默认方法的存在。

      简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。我们只需在方法名前面加个 default 关键字即可实现默认方法。

我们来看一个例子:

package com.pak2;

public interface MyInterface {
    // 定义一个已经实现的方法,使用default表明
    default void say(String message){
        System.out.println("Hello "+message);
    }

    // 普通的抽象方法
    void test();
}

那么JDK8中为什么要有这个特性?

首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 java 8 之前的集合框架没有 foreach 方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题

当一个类实现多个接口时,可以继承到每一个接口中定义的默认方法;

package com.pak2;

public interface MyInterface {
    // 定义一个已经实现的方法,使用default表明
    default void say(String message){
        System.out.println("Hello "+message);
    }

    // 普通的抽象方法
    void test();
}

class MyClass implements MyInterface{

    @Override
    public void test() {
        System.out.println("test...");
    }
}


class Main{
    public static void main(String[] args) {
        MyClass client = new MyClass();
        client.test();
        client.say("World...");

    }
}

我们再来看一下,当多个接口中的默认方法相同时,接口的实现类该如何处理?

public interface MyInterface {
    // 定义一个已经实现的方法,使用default表明
    default void say(String message){
        System.out.println("Hello "+message);
    }

    // 普通的抽象方法
    void test();
}
interface MyInterface2{
    // 定义一个已经实现的方法,使用default表明
    default void say(String message){
        System.out.println("[2]-Hello "+message);
    }
}
// 此处会编译错误
class MyClass implements MyInterface, MyInterface2{

    @Override
    public void test() {
        System.out.println("test...");
    }
}

会出现编译错误,如图所示:

意思就是说,有两个相同的方法,编译器不知道该如何选择了,所以我们需要处理:

  • 重写多个接口中的相同的默认方法
  • 在实现类中指定要使用哪个接口中的默认方法

(1)重写多个接口中的相同的默认方法

class MyClass implements MyInterface, MyInterface2{

    @Override
    public void say(String message) {
        System.out.println("[Client]-Hello "+message);
    }

    @Override
    public void test() {
        System.out.println("test...");
    }
}

(2)在实现类中指定要使用哪个接口中的默认方法

class MyClass implements MyInterface, MyInterface2{

    // 手动指定哪个默认方法生效
    public void say(String message) {
        MyInterface.super.say(message);
    }

    @Override
    public void test() {
        System.out.println("test...");
    }
}

总结:

由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()parallelStream()forEach()removeIf()等等。尽管默认方法有这么多好处,但在实际开发中应该谨慎使用:在复杂的继承体系中,默认方法可能引起歧义和编译错误。

也就是说,默认接口的出现是为了解决接口的修改与现有的实现不兼容的问题。 我们来看下JDK8中新增的Stream流,因为接口默认方法的实现,才可以在Collection接口中新增了方法stream()等。

 

Stream API

      其实,这篇博客的灵感就来自于同事在处理List集合的时候写了几行简洁而又优雅的代码,勾起了我想要学习的欲望。

      Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

将以上的图片转换为代码那就是:

List<Integer> transactionsIds = 
widgets.stream()
             .filter(b -> b.getColor() == RED)
             .sorted((x,y) -> x.getWeight() - y.getWeight())
             .mapToInt(Widget::getWeight)
             .sum();

什么是 Stream?

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

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

和以前的Collection操作不同, Stream操作还有两个基础的特征:

  • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

生成流

在 Java 8 中, 集合接口有两个方法来生成流:

  • stream() − 为集合创建串行流。

  • parallelStream() − 为集合创建并行流。

        Stream可以帮助我们在处理集合(Collection)的时候写出优雅而又简洁的代码(当然需要结合前面提到的Lambda表达式),这是一个孰能生巧的过程,此处我们直接引用别人总结好的案例来看一下:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.Map;
 
public class Java8Tester {
   public static void main(String args[]){
      System.out.println("使用 Java 7: ");
        
      // 计算空字符串
      List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
      System.out.println("列表: " +strings);
      long count = getCountEmptyStringUsingJava7(strings);
        
      System.out.println("空字符数量为: " + count);
      count = getCountLength3UsingJava7(strings);
        
      System.out.println("字符串长度为 3 的数量为: " + count);
        
      // 删除空字符串
      List<String> filtered = deleteEmptyStringsUsingJava7(strings);
      System.out.println("筛选后的列表: " + filtered);
        
      // 删除空字符串,并使用逗号把它们合并起来
      String mergedString = getMergedStringUsingJava7(strings,", ");
      System.out.println("合并字符串: " + mergedString);
      List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
        
      // 获取列表元素平方数
      List<Integer> squaresList = getSquares(numbers);
      System.out.println("平方数列表: " + squaresList);
      List<Integer> integers = Arrays.asList(1,2,13,4,15,6,17,8,19);
        
      System.out.println("列表: " +integers);
      System.out.println("列表中最大的数 : " + getMax(integers));
      System.out.println("列表中最小的数 : " + getMin(integers));
      System.out.println("所有数之和 : " + getSum(integers));
      System.out.println("平均数 : " + getAverage(integers));
      System.out.println("随机数: ");
        
      // 输出10个随机数
      Random random = new Random();
        
      for(int i=0; i < 10; i++){
         System.out.println(random.nextInt());
      }
        
      System.out.println("使用 Java 8: ");
      System.out.println("列表: " +strings);
        
      count = strings.stream().filter(string->string.isEmpty()).count();
      System.out.println("空字符串数量为: " + count);
        
      count = strings.stream().filter(string -> string.length() == 3).count();
      System.out.println("字符串长度为 3 的数量为: " + count);
        
      filtered = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.toList());
      System.out.println("筛选后的列表: " + filtered);
        
      mergedString = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.joining(", "));
      System.out.println("合并字符串: " + mergedString);
        
      squaresList = numbers.stream().map( i ->i*i).distinct().collect(Collectors.toList());
      System.out.println("Squares List: " + squaresList);
      System.out.println("列表: " +integers);
        
      IntSummaryStatistics stats = integers.stream().mapToInt((x) ->x).summaryStatistics();
        
      System.out.println("列表中最大的数 : " + stats.getMax());
      System.out.println("列表中最小的数 : " + stats.getMin());
      System.out.println("所有数之和 : " + stats.getSum());
      System.out.println("平均数 : " + stats.getAverage());
      System.out.println("随机数: ");
        
      random.ints().limit(10).sorted().forEach(System.out::println);
        
      // 并行处理
      count = strings.parallelStream().filter(string -> string.isEmpty()).count();
      System.out.println("空字符串的数量为: " + count);
   }
    
   private static int getCountEmptyStringUsingJava7(List<String> strings){
      int count = 0;
        
      for(String string: strings){
        
         if(string.isEmpty()){
            count++;
         }
      }
      return count;
   }
    
   private static int getCountLength3UsingJava7(List<String> strings){
      int count = 0;
        
      for(String string: strings){
        
         if(string.length() == 3){
            count++;
         }
      }
      return count;
   }
    
   private static List<String> deleteEmptyStringsUsingJava7(List<String> strings){
      List<String> filteredList = new ArrayList<String>();
        
      for(String string: strings){
        
         if(!string.isEmpty()){
             filteredList.add(string);
         }
      }
      return filteredList;
   }
    
   private static String getMergedStringUsingJava7(List<String> strings, String separator){
      StringBuilder stringBuilder = new StringBuilder();
        
      for(String string: strings){
        
         if(!string.isEmpty()){
            stringBuilder.append(string);
            stringBuilder.append(separator);
         }
      }
      String mergedString = stringBuilder.toString();
      return mergedString.substring(0, mergedString.length()-2);
   }
    
   private static List<Integer> getSquares(List<Integer> numbers){
      List<Integer> squaresList = new ArrayList<Integer>();
        
      for(Integer number: numbers){
         Integer square = new Integer(number.intValue() * number.intValue());
            
         if(!squaresList.contains(square)){
            squaresList.add(square);
         }
      }
      return squaresList;
   }
    
   private static int getMax(List<Integer> numbers){
      int max = numbers.get(0);
        
      for(int i=1;i < numbers.size();i++){
        
         Integer number = numbers.get(i);
            
         if(number.intValue() > max){
            max = number.intValue();
         }
      }
      return max;
   }
    
   private static int getMin(List<Integer> numbers){
      int min = numbers.get(0);
        
      for(int i=1;i < numbers.size();i++){
         Integer number = numbers.get(i);
        
         if(number.intValue() < min){
            min = number.intValue();
         }
      }
      return min;
   }
    
   private static int getSum(List numbers){
      int sum = (int)(numbers.get(0));
        
      for(int i=1;i < numbers.size();i++){
         sum += (int)numbers.get(i);
      }
      return sum;
   }
    
   private static int getAverage(List<Integer> numbers){
      return getSum(numbers) / numbers.size();
   }
}

输出结果如下:

使用 Java 7: 
列表: [abc, , bc, efg, abcd, , jkl]
空字符数量为: 2
字符串长度为 3 的数量为: 3
筛选后的列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc, bc, efg, abcd, jkl
平方数列表: [9, 4, 49, 25]
列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]
列表中最大的数 : 19
列表中最小的数 : 1
所有数之和 : 85
平均数 : 9
随机数: 
-393170844
-963842252
447036679
-1043163142
-881079698
221586850
-1101570113
576190039
-1045184578
1647841045
使用 Java 8: 
列表: [abc, , bc, efg, abcd, , jkl]
空字符串数量为: 2
字符串长度为 3 的数量为: 3
筛选后的列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc, bc, efg, abcd, jkl
Squares List: [9, 4, 49, 25]
列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]
列表中最大的数 : 19
列表中最小的数 : 1
所有数之和 : 85
平均数 : 9.444444444444445
随机数: 
-1743813696
-1301974944
-1299484995
-779981186
136544902
555792023
1243315896
1264920849
1472077135
1706423674
空字符串的数量为: 2

这篇主要讲述的是stream的 count,anyMatch,allMatch,noneMatch操作,我们先看下函数的定义

    long count();  
  
    boolean anyMatch(Predicate<? super T> predicate);  
  
    boolean allMatch(Predicate<? super T> predicate);  
  
    boolean noneMatch(Predicate<? super T> predicate);
  • count方法,跟List接口的size一样,返回的都是这个集合流的元素的长度,不同的是,流是集合的一个高级工厂,中间操作是工厂里的每一道工序,我们对这个流操作完成后,可以进行元素的数量的和;
  • anyMatch表示,判断的条件里,任意一个元素成功,返回true
  • allMatch表示,判断条件里的元素,所有的都是,返回true
  • noneMatch跟allMatch相反,判断条件里的元素,所有的都不是,返回true

我们来看下代码:

package com.pak2;

import java.util.Arrays;
import java.util.List;

public class StreamTest {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("张三", "李四", "王五", "马六9");

        long count = list.stream().filter(m -> m.length() == 2).count();
        boolean allMatch = list.stream().allMatch(m -> m.equals("张三"));
        boolean allMatch1 = list.stream().allMatch(m -> m.length() == 2); // 长度是否都等于2
        System.out.println("count = "+count);
        System.out.println("allMatch = "+allMatch);
        System.out.println("allMatch1 = "+allMatch1);

        boolean anyMatch = list.stream().anyMatch(m -> m.equals("张三"));
        boolean anyMatch1 = list.stream().anyMatch(m -> m.equals("张0"));
        System.out.println("anyMatch = "+anyMatch);
        System.out.println("anyMatch1 = "+anyMatch1);

        boolean noneMatch = list.stream().noneMatch(m -> m.equals("张三"));
        boolean noneMatch1 = list.stream().noneMatch(m -> m.equals("张0"));
        System.out.println("noneMatch = "+noneMatch);
        System.out.println("noneMatch1 = "+noneMatch1);
    }
}

输出如下:

Stream流都是一些熟能生巧的处理,有时间多瞄瞄JDK8的帮助文档,看看API介绍就会用了。此处说一个小坑。

package com.pak2;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamTest {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("张三", "李四", "王五", "马六9");

        // 此处我们统一创建一个stream
        Stream<String> stream = list.stream();

        long count = stream.filter(m -> m.length() == 2).count();
        boolean allMatch = stream.allMatch(m -> m.equals("张三"));
        boolean allMatch1 = stream.allMatch(m -> m.length() == 2); // 长度是否都等于2
        System.out.println("count = "+count);
        System.out.println("allMatch = "+allMatch);
        System.out.println("allMatch1 = "+allMatch1);

    }
}

运行程序,报错如下:

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
    at java.util.stream.ReferencePipeline.allMatch(ReferencePipeline.java:454)
    at com.pak2.StreamTest.main(StreamTest.java:15)

Process finished with exit code 1
 

这段报错的意思就是说一个stream只可以使用一次,不可以重复使用。

总结:

Stream API的出现,使得我们可以使用流来处理集合,代码优雅而又整洁。Stream API功能强大,拥有一系列的功能丰富,可以通过阅读JDK帮助文档多加学习。

 

总结:

JDK8提供了很多新特性,限于篇幅,我们只总结了相关性比较大的三个特性,其余新特性在合适的时候,另行起一篇博客来学习总结吧。

由于本文是一个总结性质的文章,所以相关的文章已经由多位大神详细总结,本博客的总结站在了巨人的肩膀上。参考文献如下:

http://www.runoob.com/java/java8-new-features.html

https://www.cnblogs.com/pikachu-zhaof/p/9724826.html

https://www.cnblogs.com/wenbronk/p/7300544.html

https://blog.csdn.net/qq_28410283/article/details/80783946

https://www.cnblogs.com/xingzc/p/6002873.html

再次鸣谢....                                                                                         2019.2.24夜. 北京

 

如果对你有帮助,记得点赞哦~欢迎大家关注我的博客,我会持续更新,如果有什么问题,可以进群366533258一起交流学习哦~

 

 

 

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐