0、初尝新特性

在没有接触Java8新特性之前,要新开一个线程,可以这么做:

Thread类需要一个Runnable接口作为参数,其中的抽象方法run方法是用来指定线程任务内容的核心。为了指定run方法体,不得不需要Runnable的实现类。为了省去定义一个Runnable 的实现类,不得不使用匿名内部类。必须覆盖重写抽象的run方法,所有的方法名称,方法参数,方法返回值不得不都重写一遍,而且不能出错。而实际上,我们只在乎方法体中的代码。为了达到这一目的,Java8提供了Lambda表达式这种操作:

而实际上,Lambda表达式是一个匿名函数,可以理解为一段可以传递的代码。Lambda表达式的重点是简化了匿名内部类的使用,语法更加简单。匿名内部类语法冗余,体验了Lambda表达式后,发现Lambda表达式是简化匿名内部类的一种方式。

1、Lambda表达式

基本语法

Lambda表达式省去了面向对象的条条框框,Lambda表达式的标准格式由3个部分组成:

(参数类型 参数名称) -> {代码体;
}

格式说明:

  • (参数类型 参数名称):参数列表

  • {代码体;}:方法体

  • ->:箭头,分割参数列表和方法体

完成一个无参无返回值的Lambda表达式

完成一个有参有返回值的Lambda表达式在sort方法的第二个参数是一个Comparator接口的匿名内部类,且执行的方法有参数和返回值,那么就可以改写为Lambda表达式形式。

@FunctionalInterface注解

如果在上例的UserService接口中再定义另外一个抽象方法,那么Lambda表达式就会报错:接口中有两个抽象方法,但是通过Lambda表达式只能去实现其中一个,Lambda表达式就懵逼了,到底要实现哪一个?所以只能有一个抽象方法才可以。

那要怎么保证这个接口中只能有一个抽象方法?使用@FunctionalInterface注解来保证。这是一个标志注解,被该注解修饰的接口只能声明一个抽象方法。有了这个注解之后,定义超过一个抽象方法就会给出报错提示:

Lambda表达式的原理

匿名内部类的本质是在编译时生成一个Class文件:XXXXX$1.class

还可以通过反编译工具来查看生成的代码XJad工具来查看:

同样的也可以使用反编译工具来看Lambda表达式生成的class文件,但是发现使用XJad查看报错。其实可以通过JDK自带的一个工具:javap 对字节码进行反汇编操作:

javap -c -p 文件名.class,-c:表示对代码进行反汇编,-p:显示所有的类和成员

在这个反编译的源码中我们看到了一个静态方法 lambda$main$0(),这个方法里面做了什么事情呢?可以通过debug的方式来查看下:

可以看到,【System.out.println("通过Lambda表达式实现show方法");】这个方法是在【lambda$main$0()】中执行的,简单点可以理解为:

为了更加直观的理解这个内容,我们可以在运行的时候添加参数:
java -Djdk.internal.lambda.dumpProxyClasses 要运行的包名.类名,加上这个参数会将内部class码输出到一个文件中:

将这个文件反编译后看到:

可以看到这个匿名的内部类实现了UserService接口,并重写了show()方法。在show()方法中调用了Demo03Lambda.lambda$main$0(),也就是调用了Lambda表达式中的内容。

总结下来就是:匿名内部类在编译的时候会产生一个class文件,Lambda表达式在程序运行的时候会形成一个类。在类中新增了一个方法,这个方法的方法体就是Lambda表达式中的代码。还会形成一个匿名内部类,实现接口,重写抽象方法,在接口中重写方法会调用新生成的方法。

Lambda表达式的省略写法

在lambda表达式的标准写法基础上,可以使用省略写法的规则为:
1、小括号内的参数类型可以省略;
2、如果小括号内有且仅有一个参数,则小括号可以省略;
3、如果大括号内有且仅有一个语句,可以同时省略大括号,return 关键字及语句分号;

Lambda表达式的使用前提

Lambda表达式的语法是非常简洁的,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意:
1、方法的参数或局部变量类型必须为接口才能使用Lambda
2、接口中有且仅有一个抽象方法(@FunctionalInterface)

Lambda和匿名内部类的对比

1、所需类型不一样
    - 匿名内部类的类型可以是 类,抽象类,接口
    - Lambda表达式需要的类型必须是接口

2、抽象方法的数量不一样
    - 匿名内部类所需的接口中的抽象方法的数量是随意的
    - Lambda表达式所需的接口中只能有一个抽象方法

3、实现原理不一样
    - 匿名内部类是在编译后形成一个class
    - Lambda表达式是在程序运行的时候动态生成class

2、接口增强

在Java8之前:
interface 接口名{
    静态常量;
    抽象方法;
}

在Java8之后:
interface 接口名{
    静态常量;
    抽象方法;
    默认方法;
    静态方法;

}

默认方法

在JDK8以前接口中只能有抽象方法和静态常量,会存在一个问题:如果接口中新增抽象方法,那么实现类都必须要抽象这个抽象方法,非常不利于接口的扩展的。

接口默认方法的语法

interface 接口名{
    修饰符 default 返回值类型 方法名{
        方法体;
    }
}

接口中的默认方法有两种使用方式:
1、实现类直接调用接口的默认方法
2、实现类重写接口的默认方法

静态方法

接口中的静态方法在实现类中是不能被重写的,调用的话只能通过接口类型来实现: 
接口名.静态方法名();

接口静态方法的语法

interface 接口名{
    修饰符 static 返回值类型 方法名{
        方法体;
    }
}

默认方法和静态方法的区别

1、默认方法通过实例调用,静态方法通过接口名调用;
2、默认方法可以被继承,实现类可以直接调用接口默认方法,也可以重写接口默认方法;
3、静态方法不能被继承,实现类不能重写接口的静态方法,只能使用接口名调用;

3、函数式接口

Supplier接口

无参有返回值的接口,对于的Lambda表达式需要提供一个返回数据的类型:

Consumer接口

有参无返回值得接口。前面介绍的Supplier接口是用来生产数据的,而Consumer接口是用来消费数据的,使用的时候需要指定一个泛型来定义参数类型:

除了抽象方法以外,还有一个默认方法:andThen。如果一个方法的参数和返回值全部是Consumer类型,那么就可以实现消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合,而这个方法就是Consumer接口中的default andThen方法:

Function接口

有参有返回值的接口,Function接口是根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。

default andThen方法:

这个接口中还包含另外两个方法,默认的compose方法的作用顺序和andThen方法刚好相反,而静态方法identity则是,输入什么参数就返回什么参数。

Predicate接口

有参且返回值为Boolean的接口。

在Predicate中的默认方法提供了逻辑关系操作 and、or、negate、isEquals等方法

4、方法引用

在使用Lambda表达式的时候,也会出现代码冗余的情况,比如:用Lambda表达式求一个数组的和。

因为在Lambda表达式中要执行的代码和另一个方法中的代码是一样的,这时就没有必要重写一份逻辑了,可以直接 “引用” 重复代码。

方法引用的语法

符号表示:【::

符号说明:双冒号为方法引用运算符,而它所在的表达式被称为方法引用

应用场景:如果Lambda表达式所要实现的方案,已经有其他方法存在相同的方案,那么则可以使用方法引用。

方法引用在Java8中使用是相当灵活的,有以下几种形式:
1、instanceName::methodName  对象::方法名
2、ClassName::staticMethodName  类名::静态方法
3、ClassName::methodName  类名::普通方法
4、ClassName::new  类名::new 调用的构造器
5、TypeName[]::new String[]::new  调用数组的构造器

instanceName::methodName  对象::方法名
这是最常见的一种用法。如果一个类中的已经存在了一个成员方法,则可以通过对象名引用成员方法。方法引用的注意事项:
1、被引用的方法,参数要和接口中的抽象方法的参数一样;
2、当接口抽象方法有返回值时,被引用的方法也必须有返回值;

ClassName::staticMethodName  类名::静态方法

ClassName::methodName 类名::普通方法
Java面向对象中,类名只能调用静态方法,类名引用实例方法是用前提的,实际上是拿第一个参数作为方法的调用者。

ClassName::new   类名::new 调用的构造器
由于构造器的名称和类名完全一致,所以构造器引用使用"::new"的格式使用。

TypeName[]::new  String[]::new 调用数组的构造器

方法引用是对Lambda表达式符合特定情况下的一种缩写方式,它使得Lambda表达式更加的精简,也可以理解为lambda表达式的缩写形式,不过要注意的是方法引用只能引用已经存在的方法。

5、Stream API

当需要对集合中的元素进行操作的时候,除了必需的添加,删除,获取外,最典型的操作就是集合遍历。很多时候针对不同的需求总是一次次的循环循环循环,这时就非常希望有更加高效的处理方式,现在可以通过JDK8中提供的Stream API来解决这个问题。

Stream流式思想

首先先明确一个概念:Stream和IO流(InputStream/OutputStream)没有任何关系,请暂时忘记对传统IO流的固有印象。Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。

Stream API能让我们快速完成许多复杂的操作,如筛选、切片、映射、查找、去重、统计、匹配和归约。

Stream流获取方式

Collection接口获取

java.util.Collection 接口中加入了default方法stream,也就是说Collection接口下的所有的实现都可以通过steam方法来获取Stream流。

但是Map接口别没有实现Collection接口,那这时怎么办呢?可以根据Map获取对应的key-value的集合,在通过集合去获取Stream流。

Stream的of方法获取

在实际开发中不可避免的还是会操作到数组中的数据,由于数组对象不可能添加默认方法,所有Stream接口中提供了of静态方法。

Stream流常用方法

Stream流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:

终结方法:返回值类型不再是 Stream 类型的方法,不再支持链式调用。终结方法包括 countforEach 方法;
非终结方法:返回值类型仍然是 Stream 类型的方法,支持链式调用。除了终结方法外,其余方法均为非终结方法;

Stream流需要注意:
1、Stream只能操作一次;
2、Stream方法返回的是新的流;
3、Stream不调用终结方法,中间的操作不会执行

方法名 方法作用 返回值类型 方法种类
count 统计个数 long 终结
forEach 逐一处理 void 终结
filter 过滤 Stream 函数拼接
limit 取用前几个 Stream 函数拼接
skip 跳过前几个 Stream 函数拼接
map 映射 Stream 函数拼接
concat 组合 Stream 函数拼接

forEach用来遍历流中的数据的,该方法接受一个Consumer接口,会将每一个流元素交给函数处理。

count

Stream流中的count方法用来统计其中的元素个数的,该方法返回一个long值,代表元素的个数。

filter

filter方法的作用是用来过滤数据的,返回符合条件的数据,可以通过filter方法将一个流转换成另一个子集流。

可以看到,该接口接收一个Predicate函数式接口参数作为筛选条件。

limit

limit方法可以对流进行截取处理,只取前n个数据。

参数是一个long类型的数值,如果集合当前长度大于参数就进行截取,否则不操作。

skip

如果希望跳过前面几个元素,可以使用skip方法获取一个截取之后的新流。

map

如果我们需要将流中的元素映射到另一个流中,可以使用map方法。

该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的数据。

sorted

如果需要将数据排序,可以使用sorted方法。

在使用的时候可以根据自然规则排序,也可以通过比较强来指定对应的排序规则。

distinct

如果要去掉重复数据,可以使用distinct方法。

Stream流中的distinct方法对于基本数据类型是可以直接出重的,但是对于自定义类型,需要重写hashCode和equals方法来移除重复元素。

match

如果需要判断数据是否匹配指定的条件,可以使用match相关的方法。

anyMatch:元素是否有任意一个满足条件

allMatch:元素是否都满足条件

noneMatch:元素是否都不满足条件

find

如果我们需要找到某些数据,可以使用find方法来实现。

max / min

reduce

如果需要将所有数据归纳得到一个数据,可以使用reduce方法。

而在实际开发中,经常会将map和reduce一块来使用。

mapToInt

如果需要将Stream中的Integer类型转换成int类型,可以使用mapToInt方法来实现。

concat

如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat。

Stream流综合案例

定义两个集合,然后在集合中存储多个用户名称。然后完成如下的操作:
1、第一个队伍只保留姓名长度为3的成员;
2、第一个队伍筛选之后只要前3个人;
3、第二个队伍只要姓张的成员;
4、第二个队伍筛选之后不要前一个人;
5、将两个队伍合并为一个队伍;
6、根据姓名创建Person对象;
7、打印整个队伍的Person信息;

Stream结果收集

结果收集到集合中

结果收集到数组中

Stream流中数据进行聚合计算

在使用Stream流处理数据后,可以像数据库的聚合函数一样对某个字段进行操作,比如:获得最大值,最小值,求和,平均值,统计数量。

Stream流中数据进行分组

在使用Stream流处理数据后,可以根据某个属性将数据分组。

还可以根据多个字段进行多级组合分组。

Stream流中数据进行分区

Collectors.partitioningBy会根据值是否为true,把集合中的数据分割为两个列表,一个true列表,一个false列表。

Stream流中数据进行拼接

Collectors.joining会根据指定的连接符,将所有的元素连接成一个字符串。

Stream并行流

并行流与串行流

上面提到的都是Stream串行流,也就是在一个线程上面执行。为了提高在大数据量方面的效率,引入了并行流的概念。parallelStream其实就是一个并行执行的流,它通过默认的ForkJoinPool,可以提高多线程任务的速度。可以通过两种方式来获取并行流:
1、通过List接口中的parallelStream方法来获取;
2、通过已有的串行流转换为并行流(parallel);

并行流的操作和串行流没有任何区别

通过for循环,串行Stream流,并行Stream流来对500000000亿个数字求和,来看消耗时间

可以看到parallelStream的效率是最高的,Stream并行处理的过程会分而治之,也就是将一个大的任务切分成了多个小任务,这表示每个任务都是一个线程操作。但需要注意的是,使用并行流是在数据量较大的情况下才会占据优势,如果是较小的数据量情况下,并行流的效率可能还不如串行流。

并行流的线程安全问题

加锁

使用线程安全容器

使用Stream中的toArray/collect

6、Optional

Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象,主要就是解决空指针的问题,防止NullpointerException。

不使用Optional对null的处理显得代码非常的繁琐和臃肿

Optional对象的三种创建方式

Optional常用方法

get

如果Optional有值则返回,否则抛出NoSuchElementException异常

isPresent

判断是否包含值,包含值返回true,不包含值返回false,常和get方法一起搭配使用

orElse

如果调用对象包含值,就返回该值,否则返回t

orElseGet

如果调用对象包含值,就返回该值,否则返回Lambda表达式的返回值

7、LocalDateTime

为什么引入LocalDateTime

在旧版本中JDK对于日期和时间这块的支持是非常差的:
1、设计不合理,在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间的,而java.sql.Date仅仅包含日期,此外用于格式化和解析的类在java.text包下;
2、非线程安全,java.util.Date是非线程安全的,所有的日期类都是可变的,这是java日期类最大的问题之一;
3、时区处理麻烦,日期类并不提供国际化,没有时区支持;

针对以上种种问题,Java8中增加了一套全新的日期时间API,这套API设计合理,并且支持线程安全。新的日期及时间API位于 java.time 包
中,下面是一些关键类:
- LocalDate:表示日期,包含年月日,格式为【2019-10-16】;
- LocalTime:表示时间,包含时分秒,格式为【16:38:54.158549300】;
- LocalDateTime:表示日期时间,包含年月日,时分秒,格式为【2018-09-06T15:33:56.750】;
- DateTimeFormatter:日期时间格式化类;
- Instant:时间戳,表示一个特定的时间瞬间;
- Duration:用于计算2个时间(LocalTime,时分秒)的距离;
- Period:用于计算2个日期(LocalDate,年月日)的距离;
- ZonedDateTime :包含时区的时间;

Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是所说的公历。平年有365天,闰年是366天。此外Java8还提供了4套其他历法,分别是:
- ThaiBuddhistDate:泰国佛教历
- MinguoDate:中华民国历
- JapaneseDate:日本历
- HijrahDate:伊斯兰历

LocalDate 常用方法

LocalTime常用方法

LocalDateTime常用方法

日期时间的修改和比较

LocalDateTime中对日期时间的修改,对已存在的对象创建了它模板,并不会修改原来对象本身的时间,自然也就不会牵涉到数据安全的问题。

日期时间的格式化和解析

在JDK8中可以通过java.time.format.DateTimeFormatter类可以进行日期的解析和格式化操作

日期时间差

Java8中提供了两个工具类来计算日期时间差:
1、Duration:用来计算两个时间差(LocalTime);
2、Period:用来计算两个日期差(LocalDate);

时间矫正器

有时候对时间可能需要如下调整:将日期调整到"下个月的第一天"这种操作,虽然通过前面介绍的plus和with也可以实现,不过使用时间校正器效果可能会更好。

- TemporalAdjuster:时间校正器
- TemporalAdjusters:通过该类静态方法提供了大量的常用TemporalAdjuster的实现

时区

Java8 中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别为:ZonedDate、ZonedTime、ZonedDateTime。其中每个时区都对应着 ID,ID的格式为 “区域/城市” 。例如 :Asia/Shanghai 等。 ZoneId:该类中包含了所有的时区信息。

JDK新的日期和时间API的优势:
1、新版日期时间API中,日期和时间对象是不可变,操作日期不会影响原来的值,而是生成一个新的实例;
2、提供不同的两种方式,有效的区分了人和机器的操作;
3、TemporalAdjuster可以更精确的操作日期,还可以自定义日期调整期;
4、线程安全;

8、重复注解和类型注解

重复注解

自从Java5中引入注解以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是,在同一个地方不能多次使用同一个注解。Java8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在Java8中使用@Repeatable注解定义重复注解。

定义一个可以重复的注解

定义一个重复注解的容器

使用重复注解

解析得到指定的注解

类型注解

Java8为@Target元注解新增了两种类型: TYPE_PARAMETER、TYPE_USE 
- TYPE_PARAMETER:表示该注解能写在类型参数的声明语句中。 类型参数声明如:<T> 
- TYPE_USE:表示注解可以再任何用到类型的地方使用。

TYPE_PARAMETER

TYPE_USE

【闲聊杂谈】深入剖析Java8新特性相关推荐

  1. 【Java8新特性】关于Java8的Stream API,看这一篇就够了!!

    写在前面 Java8中有两大最为重要的改变.第一个是 Lambda 表达式:另外一个则是 Stream API(java.util.stream.*)  ,那什么是Stream API呢?Java8中 ...

  2. 【Java8新特性】浅谈方法引用和构造器引用

    写在前面 Java8中一个很牛逼的新特性就是方法引用和构造器引用,为什么说它很牛逼呢?往下看! 方法引用 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!这里需要注意的是:实现抽 ...

  3. java8新特性_Java8新特性之Date API|乐字节

    大家好,我是乐字节的小乐,上篇文章讲述了<Java8新特性之Optional>,接下来,小乐将接着讲述Java8新特性之Date API 2019日历 Java8之Date API Jav ...

  4. Java8 新特性之流式数据处理(转)

    转自:https://www.cnblogs.com/shenlanzhizun/p/6027042.html 一. 流式处理简介 在我接触到java8流式处理的时候,我的第一感觉是流式处理让集合操作 ...

  5. java8新特性_乐字节-Java8新特性-接口默认方法

    总概 JAVA8 已经发布很久,而且毫无疑问,java8是自java5(2004年发布)之后的最重要的版本.其中包括语言.编译器.库.工具和JVM等诸多方面的新特性. Java8 新特性列表如下: 接 ...

  6. java8新特性: lambda表达式:直接获得某个list/array/对象里面的字段集合

    java8新特性: lambda表达式:直接获得某个list/array/对象里面的字段集合 比如,我有一张表: entity Category.java service CategoryServic ...

  7. java8新特性_乐字节-Java8新特性-函数式接口

    上一篇小乐带大家学过 Java8新特性-Lambda表达式,那什么时候可以使用Lambda?通常Lambda表达式是用在函数式接口上使用的.从Java8开始引入了函数式接口,其说明比较简单:函数式接口 ...

  8. 使用Java8新特性(stream流、Lambda表达式)实现多个List 的笛卡尔乘积 返回需要的List<JavaBean>

    需求分析: 有两个Long类型的集合 : List<Long> tagsIds; List<Long> attributesIds; 现在需要将这两个Long类型的集合进行组合 ...

  9. java8新特性简述

    Java8发布时间是2014年3月19日,距离今日已经很久了,那么Java8新特性你了解吗? java8是Java的一次重大升级,巨大的里程碑式的改进!! Java语言新特性: 1.与传统结合 -- ...

最新文章

  1. UpdateData使用简介
  2. 如何用php新增税金一列_PHP计算个人所得税步骤详解(附代码)
  3. 一道题,最小操作次数使数组元素相等引发的思考
  4. 三星sec.android.soagent,3.0降级2.5教程
  5. 【HDU - 1561】The more, The Better(树形背包,dp,依赖背包问题与空间优化,tricks)
  6. TabHost和ActivityGroup用法
  7. 最牛逼android上的图表库MpChart(二) 折线图
  8. 【图像处理基础】基于matlab GUI图片浏览器【含Matlab源码 1015期】
  9. 点集凸包算法python实现
  10. 所有文件夹都变成1KB文件夹快捷方式病毒的手动清除方法
  11. 计算机制作贺卡教案,三八爱心节贺卡教案
  12. 计算机excel感叹号,excel的文件上有个的感叹号是什么意思?
  13. 浅层砂过滤器的原理是什么,滤料是什么,需要不需要定期?
  14. Android内存泄漏leakcanary2.7
  15. Android屏幕适配概论:
  16. swift 设置 pickerView 为黑底白字
  17. 华为硬件工程师手册_华为,英飞凌,中兴硬件工程师面试题
  18. NOMAO软件测试工资,基于混合遗传算法的测试数据自动生成研究
  19. node中全局对象一 --- __dirname和__filename
  20. EXCEL---一个简单的查询--对比大佬的方法,学习几个函数 mmult sign find subtitute lookup( , 0/) 等用法

热门文章

  1. ATI FirePro V3800显卡试用
  2. Python大作业——爬虫+可视化+数据分析+数据库(数据分析篇)
  3. Sublime编辑LaTex
  4. php网站统计浏览量,PHP简单实现记录网站访问量功能示例
  5. 操作系统——进程同步
  6. 出租车计费程序php,一个出租车计费的程序
  7. 荣耀笔记本(预装deepin版)的正确使用姿势
  8. 项目一~美食达人图册
  9. html5 drawimage,HTML5中drawImage用法分析
  10. 基于神经网络的手写汉字提取与书写评分模型研究